/*
 * File    : ActiveMediaPlayer.java
 * Created : 27-may-2002 18:43
 * By      : fbusquets
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details (see the LICENSE file).
 */

package edu.xtec.jclic.media;

import edu.xtec.jclic.*;
import edu.xtec.jclic.bags.MediaBag;
import edu.xtec.jclic.boxes.ActiveBox;
import edu.xtec.util.GlobalMouseAdapter;
import edu.xtec.util.Options;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;

/**
 * @author Francesc Busquets (fbusquets@xtec.cat)
 * @version 13.08.28
 */
public abstract class ActiveMediaPlayer {

  public static final int AUDIO_BUFFERS = 10;
  protected static final AudioBuffer[] audioBuffer = new AudioBuffer[AUDIO_BUFFERS];

  MediaContent mc;
  PlayStation ps;
  ActiveBox bx;
  Component visualComponent;
  GlobalMouseAdapter mouseAdapter;
  boolean useAudioBuffer;

  public static ActiveMediaPlayer createActiveMediaPlayer(MediaContent mc, MediaBag mb, PlayStation ps) {
    ActiveMediaPlayer result = null;
    String playerClassName = null;
    String ms = ps.getOptions().getString(Constants.MEDIA_SYSTEM);

    switch (mc.mediaType) {
    case MediaContent.RECORD_AUDIO:
    case MediaContent.PLAY_RECORDED_AUDIO:
      if (ps.getOptions().getBoolean(Options.MAC)) {
        if (Constants.QT61.equals(ms)) {
          playerClassName = "edu.xtec.jclic.media.QT61ActiveMediaPlayer";
          break;
        }
      }
    case MediaContent.PLAY_AUDIO:
    case MediaContent.PLAY_MIDI:
      playerClassName = "edu.xtec.jclic.media.JavaSoundActiveMediaPlayer";
      break;
    default:
      if (Constants.QT61.equals(ms))
        playerClassName = "edu.xtec.jclic.media.QT61ActiveMediaPlayer";
      else if (Constants.JMF.equals(ms))
        playerClassName = "edu.xtec.jclic.media.JMFActiveMediaPlayer";
      else
        CheckMediaSystem.warn(ps.getOptions());
      break;
    }

    if (playerClassName != null) {
      try {
        Class<?> c = Class.forName(playerClassName);
        java.lang.reflect.Constructor<?> cons = c
            .getConstructor(new Class<?>[] { edu.xtec.jclic.media.MediaContent.class,
                edu.xtec.jclic.bags.MediaBag.class, edu.xtec.jclic.PlayStation.class });
        result = (ActiveMediaPlayer) cons.newInstance(new Object[] { mc, mb, ps });
      } catch (Exception ex) {
        System.err.println("Error building media player:\n" + ex);
      }
    }
    return result;
  }

  /** Creates new MediaContentPlayer */
  protected ActiveMediaPlayer(MediaContent mc, MediaBag mb, PlayStation ps) {
    this.mc = mc;
    this.ps = ps;
    bx = null;
    visualComponent = null;
    mouseAdapter = null;
    useAudioBuffer = false;
    try {
      switch (mc.mediaType) {
      case MediaContent.RECORD_AUDIO:
        clearAudioBuffer(mc.recBuffer);
        audioBuffer[mc.recBuffer] = createAudioBuffer(mc.length);
      case MediaContent.PLAY_RECORDED_AUDIO:
        useAudioBuffer = true;
        break;
      default:
        break;
      }
    } catch (Exception ex) {
      System.err.println("Error:\n" + ex);
    }
  }

  public abstract AudioBuffer createAudioBuffer(int seconds) throws Exception;

  public abstract void realize();

  public void play(final edu.xtec.jclic.boxes.ActiveBox setBx) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        stopAllAudioBuffers();
        playNow(setBx);
      }
    });
  }

  protected void playNow(edu.xtec.jclic.boxes.ActiveBox setBx) {
    try {
      switch (mc.mediaType) {
      case MediaContent.RECORD_AUDIO:
        if (audioBuffer[mc.recBuffer] != null) {
          audioBuffer[mc.recBuffer].record(ps, setBx);
        }
        break;
      case MediaContent.PLAY_RECORDED_AUDIO:
        if (audioBuffer[mc.recBuffer] != null)
          audioBuffer[mc.recBuffer].play();
        break;
      default:
        break;
      }
    } catch (Exception e) {
      System.err.println("Error playing media \"" + mc.mediaFileName + "\":\n" + e);
    }
  }

  public void stop() {
    if (useAudioBuffer)
      stopAudioBuffer(mc.recBuffer);
  }

  @Override
  protected void finalize() throws Throwable {
    clear();
    super.finalize();
  }

  public void clear() {
    stop();
    if (useAudioBuffer)
      clearAudioBuffer(mc.recBuffer);
  }

  protected abstract void setTimeRanges();

  public static void clearAudioBuffer(int buffer) {
    if (buffer >= 0 && buffer < AUDIO_BUFFERS && audioBuffer[buffer] != null) {
      audioBuffer[buffer].clear();
      audioBuffer[buffer] = null;
    }
  }

  public static void clearAllAudioBuffers() {
    for (int i = 0; i < AUDIO_BUFFERS; i++)
      clearAudioBuffer(i);
  }

  public static int countActiveBuffers() {
    int c = 0;
    for (AudioBuffer ab : audioBuffer)
      if (ab != null)
        c++;
    return c;
  }

  public static void stopAllAudioBuffers() {
    for (AudioBuffer ab : audioBuffer)
      if (ab != null)
        ab.stop();
  }

  public static void stopAudioBuffer(int buffer) {
    if (buffer >= 0 && buffer < AUDIO_BUFFERS && audioBuffer[buffer] != null)
      audioBuffer[buffer].stop();
  }

  public void checkVisualComponentBounds(ActiveBox bxi) {
    if (visualComponent == null)
      return;

    Rectangle enclosingRect = new Rectangle();
    if (!mc.free)
      enclosingRect.setBounds(bxi.getBounds());
    else
      enclosingRect.setBounds(ps.getComponent().getBounds());

    Point offset = new Point();
    Dimension dim = new Dimension(visualComponent.getPreferredSize());
    if (mc.absLocation != null) {
      offset.setLocation(mc.absLocation);
      if (offset.x + dim.width > enclosingRect.width)
        offset.x = enclosingRect.width - dim.width;
      if (offset.y + dim.height > enclosingRect.height)
        offset.y = enclosingRect.height - dim.height;
    }
    if (mc.stretch) {
      int extraW = enclosingRect.width - offset.x - dim.width;
      if (extraW < 0) {
        dim.width = enclosingRect.width - offset.x;
        extraW = 0;
      }
      int extraH = enclosingRect.height - offset.y - dim.height;
      if (extraH < 0) {
        dim.height = enclosingRect.height - offset.y;
        extraH = 0;
      }
      if (mc.absLocation == null) {
        offset.x += extraW / 2;
        offset.y += extraH / 2;
      }
    }
    Rectangle vRect = new Rectangle(enclosingRect.x + offset.x, enclosingRect.y + offset.y, dim.width, dim.height);
    visualComponent.setSize(dim);
    visualComponent.setLocation(vRect.getLocation());
    visualComponent.setBounds(vRect);
  }

  public void setVisualComponentVisible(boolean state) {
    if (visualComponent != null)
      visualComponent.setVisible(state);
  }

  protected abstract java.awt.Component getVisualComponent();

  public void attachVisualComponent() {
    if (mc.mediaType != MediaContent.PLAY_VIDEO || bx == null)
      return;

    visualComponent = getVisualComponent();
    if (visualComponent == null)
      return;
    visualComponent.setVisible(false);

    Container cnt = bx.getContainerResolve();
    if (mc.free)
      cnt = ps.getComponent();

    if (cnt != visualComponent.getParent()) {
      cnt.add(visualComponent);
    }

    if (mouseAdapter == null && mc.catchMouseEvents == false) {
      mouseAdapter = new GlobalMouseAdapter(cnt);
      mouseAdapter.attachTo(visualComponent, true);
    }

    checkVisualComponentBounds(bx);
    visualComponent.setVisible(true);
  }

  protected void destroyVisualComponent() {
    if (mouseAdapter != null) {
      mouseAdapter.detach(visualComponent);
      mouseAdapter = null;
    }
    if (visualComponent != null) {
      visualComponent.setVisible(false);
      stop();
      visualComponent = null;
    }
  }

  public void linkTo(ActiveBox setBx) {
    if (bx != null && bx != setBx) {
      bx.setHostedMediaPlayer(null);
      destroyVisualComponent();
    }
    bx = setBx;
    if (bx != null)
      bx.setHostedMediaPlayer(this);
    else {
      destroyVisualComponent();
    }
  }

  public MediaContent getMediaContent() {
    return mc;
  }
}
