/**
 * Worry about non-TV types later
 */
package org.bucksch.zeipis.core;
import org.bucksch.zeipis.all.*;
import org.bucksch.zeipis.recorder.*;
import org.bucksch.zeipis.store.*;
import java.sql.*;
import java.util.*;

public class PlannedRecording extends ScheduledRecording
{
  public PlannedRecording(int recordingid)
  	throws Misconfiguration, SQLException
  {
    super(recordingid);
  }
  protected PlannedRecording(ResultSet rs)
    throws Misconfiguration, SQLException
  {
    super(rs);
  }
  protected void readDatabaseResult(ResultSet rs)
    throws SQLException, Misconfiguration
  {
    super.readDatabaseResult(rs);
    myPriority = rs.getInt("priority");
    mySearchType = rs.getInt("searchtype");
    myWantedStationID = rs.getInt("rec_stationid");
    myDevice = null;
    myConflicts = false;
    myUserDisabled = false;
  }
  /**
   * Find an alternative time to record this same "film",
   * and set the internal vars (starttime, station etc.) to that.
   * @return false, if no other found
   * @throws Exception
   */
  protected boolean findLaterBroadcast()
    throws SQLException
  {
    if (mySearchType == 1)
      return false;
    PreparedStatement query = DB.me.database().prepareStatement(
        "SELECT broadcastid, starttime, endtime, stationid FROM broadcast " +
        "WHERE filmid = ? AND starttime > ? " +
        (mySearchType >= 4 && mySearchType <= 6 ? " AND stationid = " + myWantedStationID + " ": " ") +
        "ORDER BY starttime ASC");
    query.setInt(1, myFilmID);
    query.setTimestamp(2, new java.sql.Timestamp(myStarttime.getTime()));
    //Core.me.debug(query.toString());
    ResultSet rs = query.executeQuery();
    if (!rs.first())
      return false;
    myStarttime = rs.getTimestamp("starttime");
    myEndtime = rs.getTimestamp("endtime");
    myStationID = rs.getInt("stationid");
    myBroadcastID = rs.getInt("broadcastid");
    return true;
  }

  /**
   * TODO Send msg to RecorderServer
   */
  public void recordNow()
    throws Exception
  {
    if (!(myDevice instanceof TVCard))
      throw new Misconfiguration("recordNow is only implemented for TVCard", null); // XXX makes sense? TV vs. IP Recorder

    // Find Store
    myStore = StorageAllocator.findStorage(this);
    // Get Target from Store
    myTarget = myStore.saveFile(this);

    Util.debug("Starting recording of " + this.toString() + " on " + myChannel.toString() + " to " + myTarget.toString());
    myRecorder = myDevice.newRecording();
    myRecorder.startRecording(myChannel);
    myStreamCopy = new StreamCopy();
    myStreamCopy.copy(myRecorder, myTarget, true, "Recording " + myChannel.stationLongName());
    // XXX That's nonsense for remote source + remote target

    myEndTimer = new Timer(title(), false);
    myEndTimer.schedule(new TimerTask() {
      public void run() {
        try {
          Util.debug("Stopping recording of " + myChannel.stationLongName());
          done();
        } catch (Exception e) {
          Util.error(e);
        }
      }
    }, endtime());
  }
  protected void done()
  {
    try {
      if (myStreamCopy == null || myRecorder == null)
        throw new RuntimeException("Recording has not started yet, can't stop it");
      myStreamCopy.stop();
      myRecorder.stopRecording();
      Util.debug("Done recording " + myTitle + " on channel " + myChannel.stationLongName());
      if (myBroadcastID > 3) // TODO for testing
        CoreServer.me.scheduler().recordingDone(this);
    } catch (CantUseTVCard e) { // unexpected
      CoreServer.me.scheduler().recordingError(this, e);
    }
  }
  public void cancel()
  {
    try {
      if (myStreamCopy == null || myRecorder == null)
        throw new RuntimeException("Recording has not started yet, can't stop it");
      myStreamCopy.stop();
      myRecorder.stopRecording();
    } catch (CantUseTVCard e) { Util.error(e); } // we were supposed to cancel anyways - not sure if good idea to suppress. Do not log in recording, would overwrite reason for cancel.
    myEndTimer.cancel();
    CoreServer.me.scheduler().recordingError(this, new Exception("Cancelled")); // TODO translate
  }


  // getters

  public Device device() { return myDevice; }
  public TVChannel channel() { return myChannel; }
  public int priority() { return myPriority; }
  public boolean conflicts() { return myConflicts; }
  public boolean disabled() { return myConflicts || myUserDisabled; }
  public Target target() { return myTarget; }
  public Store store() { return myStore; }
  
  // setters
  /**
   * You should set channel before device, so that device matches channel
   */
  public void device(Device newDevice)
    throws Misconfiguration
  {
    // should I go to DB just to check that dev matches channel?
    if (newDevice instanceof TVCard &&
        !((TVCard) newDevice).canRecord(myChannel)) // may throw Misconfiguration, too
      throw new Misconfiguration("Device " + newDevice.id() + " cannot record channel " + myChannel.id());

    myDevice = newDevice;
  }
  /**
   * @param newChannel
   * @throws Misconfiguration
   */
  public void channel(TVChannel newChannel)
    throws Misconfiguration
  {
    if (newChannel.stationid() != myStationID)
      throw new Misconfiguration("Channel doesn't match station");
    myChannel = newChannel;
  }
  public void conflicts(boolean newValue)
  {
    myConflicts = newValue;
  }


  // Helpers

  public String toString()
  {
    if (myConflicts)
      return super.toString() + " conflicts";
    if (disabled())
      return super.toString() + " disabled";
    return super.toString() + " " + myChannel + " " + myDevice +
        " priority " + myPriority + " " + this.hashCode();
  }


  // Data
  protected Device myDevice;
  protected TVChannel myChannel;
  protected TVRecorder myRecorder = null;
  protected Target myTarget = null;
  protected Store myStore = null;
  protected StreamCopy myStreamCopy = null;
  protected Timer myEndTimer = null;
  protected int myPriority;
  protected int myWantedStationID;
  protected int mySearchType;
  protected boolean myConflicts;
  protected boolean myUserDisabled;
}
