import sale.*;
import sale.stdforms.*;
import data.*;
import data.ooimpl.*;
import data.stdforms.*;
import data.events.*;
import users.*;
import log.*;

import java.util.*;
import java.text.*;
import java.lang.*;
import java.io.*;

import javax.swing.JTextField;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import javax.swing.BoxLayout;
import javax.swing.JLabel;



/**
 * Rückgabeprocess, der von einem registrierten Kunden
 * am Automaten durchgeführt werden kann.
 */
public class GiveBackProcess extends SaleProcess
{

  //// attributes ////////////////////////////////////////////////////////////

  // Gates
  protected UIGate capabilityGate;
  protected UIGate selectionGate;
  protected UIGate giveRestGate;

  // Transitions
  protected Transition toSelectionTransition;
  protected Transition toGetMoneyTransition;

  // verwendete Waehrung
  protected data.Currency myCurrency;

  // rueckzuzahlender Betrag
  private int toPayBackValue;

  // gezahlter Betrag (Verkaufspreis des Videos)
  private IntegerValue paidValue;

  // Kunde, der zurueckgeben moechte
  private Customer customer;

  // Verleihpreis eines Videos pro Tag
  private int rentPrice = 300;


  //// constructor ///////////////////////////////////////////////////////////

  /**
   * Erzeugt ein neues Objekt der Klasse GiveBackProcess.
   */
  public GiveBackProcess()
  {
    super ("GiveBackProcesss");
  }

  //// protected methods /////////////////////////////////////////////////////

  /**
   * Baut die Oberfläche für den Rückgabevorgang auf.
   */
  protected void setupMachine()
  {
    // verwendete Waehrung ermitteln
    myCurrency = (Currency)Shop.getTheShop().getCatalog("DM");

    // Videokatalog
    final CatalogImpl videoCatalog =
      (CatalogImpl)Shop.getTheShop().getCatalog("Video-Catalog");

    // Gate zur Kundennummer-Abfrage anlegen
    capabilityGate = new UIGate(null, null);

    // Formsheet fuer Eingabe der Kundennummer
    final TextInputForm tif = new TextInputForm("Customer-ID",
						"Customer-ID",
						"");

    // Auswahl-Gate anlegen
    selectionGate = new UIGate(null, null);

    // zu verwendenden Datenkorb ermitteln
    final DataBasket db = getBasket();

    // Transition zum Selection Gate
    toSelectionTransition = new Transition()
    {
      public Gate perform(SaleProcess pOwner, User usr)
      {
	// zu verwendenden Bestand ermitteln
	StoringStock ss = customer.getStoringStock();

	// am Gate darzustellendes FormSheet
	TwoTableFormSheet ttfs = TwoTableFormSheet.create(
          "Give Back",           // Titel des FormSheets
	  ss,                    // Quell-StoringStock
	  db,                    // Ziel-DataBasket
	  selectionGate          // Gate, an dem das FormSheet darzustellen ist
	);

	// FormSheetContentCreator am Auswahl-Gate anmelden    
	ttfs.addContentCreator(new FormSheetContentCreator()
	{
	  protected void createFormSheetContent(FormSheet fs)
	  {
	    // alle vorhandenen Buttons entfernen
	    fs.removeAllButtons();

	    // neuen "OK"-Button einbauen
	    fs.addButton ("Ok", 100, new sale.Action()
	    {
	      public void doAction (SaleProcess p, SalesPoint sp)
	      {
		// Transition zum Bezahlen als naechste Transition setzen
		selectionGate.setNextTransition(toGetMoneyTransition);
	      }
	    });

	    // "Cancel"-Button einbauen
	    fs.addButton ("Cancel", 101, new sale.Action()
	    {
	      public void doAction (SaleProcess p, SalesPoint sp)
	      {
		// Transition zum Rollback-Gate als naechste Transition setzen
		selectionGate.setNextTransition(
		  GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
	      }
	    });
	  }
	});

	// erstelltes FormSheet am zu betretenden Gate setzen
	selectionGate.setFormSheet(ttfs);

	// als naechstes zu betretendes Gate zurueckgeben
	return selectionGate;
      }
    };

    // Gate zum Ausgeben des Restgeldes anlegen
    giveRestGate = new UIGate(null, null);

    // Transition zum Gate, an dem das Restgeld gegeben wird
    toGetMoneyTransition = new Transition()
    {
      public Gate perform(SaleProcess pOwner, User usr)
      {
	// Parameter zum Aufsummieren des Datenkorbes festlegen
	DataBasketCondition dbc =
	  DataBasketConditionImpl.ALL_STOCK_ITEMS;
	int sellValue = 0;
	int rentValue = 0;

	// Videobestand des Automaten
	CountingStockImpl videoStock =
	  (CountingStockImpl)Shop.getTheShop().getStock("Video-Countingstock");

	// aktuelle Zeit ermitteln
	Object date = Shop.getTheShop().getTimer().getTime();

	// rueckzuzahlenden Betrag ermitteln
	Iterator i = db.iterator(dbc);
	while (i.hasNext()) {

	  // spezielle Kassette ermitteln
	  StoringStockItemDBEntry cassetteItem =
	    (StoringStockItemDBEntry)i.next();
	  CassetteStoringStockItem cassette =
	    (CassetteStoringStockItem)cassetteItem.getValue();

	    // Verkaufspreis bestimmen
	    try {
	      sellValue =
		((NumberValue)((QuoteValue)(videoCatalog.get(
                cassette.getName(), null, false).getValue())).getBid()
	        ).getValue().intValue();
	    }
	    catch (VetoException ve) {}

	  // mind. einen Tag ausgeliehen?
	  if ((((Long)date).intValue()
	       - ((Long)cassette.getDate()).intValue()) > 0) {

	    // Verleihgebuehr bestimmen
	    rentValue = (((Long)date).intValue()
	      - ((Long)cassette.getDate()).intValue())
	      * rentPrice;


	    // Leihgebuehr kleiner als Verkaufspreis?
	    if (rentValue < sellValue) {
	      //rueckzuzahlenden Betrag erhoehen
	      toPayBackValue = toPayBackValue + (sellValue - rentValue);

	      // Video in Automatenbestand zuruecklegen
	      videoStock.add(cassette.getName(), 1, null);

	      // Vorgang in Logdatei eintragen
	      try {
		Log.getGlobalLog().log(new GiveBackLoggable(
                  cassetteItem.getSecondaryKey(),
		  customer.getCustomerID(), date));
	      }

	      catch (LogNoOutputStreamException lnose) {}
	      catch (IOException ioe) {}

	    }
	  }

	  // keinen Tag ausgeliehen (trotzdem 3,00 DM Leihgebuehr)
	  if ((((Long)date).intValue()
	    - ((Long)cassette.getDate()).intValue()) == 0) {

	    //rueckzuzahlenden Betrag erhoehen
	    toPayBackValue = toPayBackValue + (sellValue - rentPrice);
	    
	    // Video in Automatenbestand zuruecklegen
	    videoStock.add(cassette.getName(), 1, null);

	    // Vorgang in Logdatei eintragen
	    try {
	      Log.getGlobalLog().log(new GiveBackLoggable(
	        cassetteItem.getSecondaryKey(),
		customer.getCustomerID(), date));
	      }

	      catch (LogNoOutputStreamException lnose) {}
	      catch (IOException ioe) {}


	  }
	}

	// rueckzugebenden Betrag vom Geldbestand abziehen
	try {
	    if (toPayBackValue > 0 ) {
		((CountingStock)Shop.getTheShop().getStock(
		  "coin slot")).remove("1-Pfennig-Stueck",
				       toPayBackValue, 
				       pOwner.getBasket());
	    }
	}
	catch (VetoException ve) {}

	// am Gate darzustellendes FormSheet
	MsgForm mf = new MsgForm(
	  "Give Rest",
	  "You get " +
	  myCurrency.toString(new IntegerValue(toPayBackValue)) +
	  " back");

	// ContentCreator zur Neubelegung des "OK"-Buttons hinzufuegen 
	mf.addContentCreator(new FormSheetContentCreator()
        {
	  public void createFormSheetContent(FormSheet fs)
	  {
	    // neue Aktion setzen
	    fs.getButton(FormSheet.BTNID_OK).setAction(new Action()
	    {
	      public void doAction(SaleProcess p, SalesPoint sp)
	      {
		// zum Commit-Gate fuehrende Transition als
		// naechste Transition setzen
		giveRestGate.setNextTransition(
	          GateChangeTransition.CHANGE_TO_COMMIT_GATE);
	      }
	    });
	  }
	});

	// erstelltes FormSheet am zu betretenden Gate setzen
	giveRestGate.setFormSheet(mf);

	// als naechstes zu betretendes Gate zurueckgeben
	return giveRestGate;
      }
    };

    // FormSheetContentCreator am Kundennummer-Eingabe-Gate anmelden    
    tif.addContentCreator(new FormSheetContentCreator()
    {
      protected void createFormSheetContent(FormSheet fs)
      {
	// alle vorhandenen Buttons entfernen
	fs.removeAllButtons();

	// neuen "OK"-Button einbauen
	fs.addButton ("Ok", 100, new sale.Action()
	{
	  public void doAction (SaleProcess p, SalesPoint sp)
	  {
	    // Eingabe ueberpruefen
	    String customerID = tif.getText();
	    boolean isNotAnInteger = true;

	    try {
	      new Integer(customerID);
	      isNotAnInteger = false;
	    }
	    catch (NumberFormatException nfe) {}

	    // Eingabe korrekt -> selectionGate
	    if (!isNotAnInteger && (new Integer(customerID)).intValue() > 0) {

	      // existiert der Kunde?
	      try {
		boolean customerExists = false;
		Set customerSet = VideoMachine.getAllCustomer();
		Iterator i = customerSet.iterator();
		while (i.hasNext() && !customerExists){
		  Customer myCustomer = (Customer)i.next();
		  if (myCustomer.getCustomerID().equals(customerID)) {
		    customer = myCustomer;
		    customerExists = true;
		  }
		}
		
		if (customerExists) {
		  capabilityGate.setNextTransition(
		    toSelectionTransition);
		}

		else {
		  JOptionPane.showMessageDialog(null,
     	            "This CustomerID doesn't exist!");
		  capabilityGate.setNextTransition(
		    new GateChangeTransition(capabilityGate));
		}
	      }

	      catch (NullPointerException ne) {
		JOptionPane.showMessageDialog(null,
		  "This CustomerID doesn't exist!");
		capabilityGate.setNextTransition(
		  new GateChangeTransition(capabilityGate));
	      }
	    }

	    // Eingabe falsch -> Kunden informieren und
	    // Eingabe wiederholen
	    else {
		JOptionPane.showMessageDialog(null,
		  "CustomerID must be a positive number!");
		capabilityGate.setNextTransition(
		  new GateChangeTransition(capabilityGate));
	    }
	  }
	});

	// "Cancel"-Button einbauen
	fs.addButton ("Cancel", 101, new sale.Action()
	{
	  public void doAction (SaleProcess p, SalesPoint sp)
	  {
	    // Transition zum Rollback-Gate als naechste Transition setzen
	    capabilityGate.setNextTransition(
	      GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
	  }
	});
      }
    });

    // FormSheet am entsprechenden Gate setzen
    capabilityGate.setFormSheet(tif);

  }

  /**
   * Beinhaltet den Logeintrag, der beim schlie&szlig;en des SalesPoints
   * aufgerufen wird.
   */
  protected void logSalesPointClosed()
  {
  }


  //// public methods ////////////////////////////////////////////////////////

  /**
   * Gibt das Startgate des Prozesses zur&uuml;ck.
   */
  public Gate getInitialGate()
  {  
    // Automat aufbauen
    setupMachine();
 
    // ...und Auswahl-Gate als Startgate zurueckgeben
    return capabilityGate;
  }

  /**
   * &Uuml;bergibt das Log-Gate. Hier das Stop-Gate, da beim Beenden des
   * Prozesses kein Log-Eintrag geschrieben werden soll.
   */
  public Gate getLogGate()
  {
    return getStopGate();
  }

}


/**
 * Definiert Die Schnittstelle <Code>Loggable</CODE>.
 */
class GiveBackLoggable implements Loggable
{

  //// attributes ////////////////////////////////////////////////////////////

  String name;  
  String customerID;
  Object date;


  //// constructor ///////////////////////////////////////////////////////////

  /**
   * Der Konstruktor legt ein neues Objekt der Klasse <CODE>MyLoggable</CODE>
   * an, wobei der Konstruktor von <CODE>Loggable</CODE> aufgerufen wird,
   * und die &uuml;bergebenen Variablen zugewiesen werden. Diese sind
   * wichtig f&uuml;r die Erstellung des Log-Eintrags.
   */
  public GiveBackLoggable(String name,
		    String customerID, Object date)
  {
    super();
    this.name = name;
    this.customerID = customerID;
    this.date = date;
  }


  public GiveBackLoggable(StoringStockItemDBEntry cassetteItem,
		    Customer customer, Object date)
  {
    super();
    name = cassetteItem.getSecondaryKey();
    this.customerID = customerID;
    this.date = date;
  }


  //// public methods ////////////////////////////////////////////////////////

  /**
   * Holt sich den Log-Eintrag, der aus den &uuml;bergebenen Daten
   * zusammengestellt wird. Dazu wird die extra implementierte
   * Klasse <CODE>MyLogEntry</CODE> genutzt.
   */
  public LogEntry getLogData()
  {
    return new GiveBackLogEntry(name, customerID, date);
  }
}



/**
 * &Uuml;berschreibt die Klasse <Code>LogEntry</CODE> um einen
 * selbstdefinierten Log-Eintrag zu erm&ouml;glichen.
 */
class GiveBackLogEntry extends LogEntry
{

  //// attributes ////////////////////////////////////////////////////////////

  String name;
  String customerID;
  Object date;


  //// constructor ///////////////////////////////////////////////////////////

  /**
   * Der Konstruktor legt ein neues Objekt der Klasse <CODE>MyLogEntry</CODE>
   * an, wobei der Konstruktor von <CODE>LogEntry</CODE> aufgerufen wird,
   * und die &uuml;bergebenen Variablen zugewiesen werden. Diese sind
   * wichtig f&uuml;r die Erstellung des Log-Eintrags.
   */
  public GiveBackLogEntry(String name, String customerID, Object date)
  {
    super();
    this.name = name;
    this.customerID = customerID;
    this.date = date;
  }


  //// public methods ////////////////////////////////////////////////////////

  /**
   * Spezifiziert das Aussehen des Log-Eintrags.
   */
  public String toString()
  {
    return name +
      " gave back by customer " + customerID +
      " (ID) at turn " + date;
  }
}