import sale.*;
import sale.stdforms.*;
import data.*;
import data.ooimpl.*;
import data.stdforms.*;
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;



/**
 * Verkaufs- bzw. Verleihprozess, der von einem registrierten Kunden
 * am Automaten durchgeführt werden kann.
 */
public class RentProcess extends SaleProcess
{
   
  //// attributes ////////////////////////////////////////////////////////////
   
  // Gates
  protected UIGate capabilityGate;
  protected UIGate selectionGate;
  protected UIGate rentGate;
  protected Gate   decisionGate;
  protected UIGate getChangeGate;
   
  // Transitions
  protected Transition toSelectionTransition;
  protected Transition toPayingTransition;
  protected Transition toDecisionTransition;
  protected Transition toGetChangeTransition;
  
  // verwendete Waehrung
  private Currency myCurrency;
  
  // zu zahlender Betrag
  private IntegerValue toPayValue;
   
  // gezahlter Betrag
  private IntegerValue paidValue;
   
  // Bewertung der Relation zwischen zu zahlendem und gezahltem Betrag
  private int payAssessment;
   
  // Kunde, der ausleihen moechte
  private Customer customer;
  
  // der Iterator ueber die geliehenen Videos
  private transient Iterator videoIterator;
   
   
  //// constructor ///////////////////////////////////////////////////////////
   
  /**
   * Erzeugt ein neues Objekt der Klasse RentProcess.
   */
  public RentProcess()
  {
    super ("RentProcess");
  }
  
  //// protected methods /////////////////////////////////////////////////////
   
  /**
   * Baut die Oberfläche für den Verleihvorgang auf.
   */
  protected void setupMachine()
  {
    // verwendete Waehrung ermitteln
    myCurrency = (Currency)Shop.getTheShop().getCatalog("DM");
      
    // zu verwendenden Datenkorb ermitteln
    final DataBasket db = getBasket();
      
    // zu verwendenden Bestand ermitteln
    final CountingStockImpl cs =
      (CountingStockImpl)Shop.getTheShop().getStock("Video-Countingstock");
      
      
    //////////////////////////////////////////////////
    /// Capability-Gate //////////////////////////////
    //////////////////////////////////////////////////
      
      
    // Formsheet fuer Eingabe der Kundennummer
    // tif ist final, damit in der anonymen Klasse des FSCC auf Attribute des tif zugegriffen werden kann
    final TextInputForm tif = new TextInputForm("Customer-ID",
                                                "Customer-ID",
                                                "");
      
    // FormSheetContentCreator fuer Capability-Gate implementieren    
    tif.addContentCreator(
      new FormSheetContentCreator()
      {
        protected void createFormSheetContent(FormSheet fs)
        {
          // "OK"-Button neu definieren
          fs.getButton(FormSheet.BTNID_OK).
            setAction(
              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) {
                    isNotAnInteger = true;
                  }
                                                         
                  // Eingabe korrekt -> selectionGate
                  if (!isNotAnInteger && (new Integer(customerID)).intValue() > 0) 
                  {                                                           
                    // existiert der Kunde?
                    try {
                      customer = new Customer(customerID);
                      // nein, dann neu anlegen und der Liste reg. Kunden hinzufuegen
                      VideoMachine.addCustomer(customer);
                    }
                    // ja, dann diesen holen
                    catch (DuplicateKeyException dke) {
                      customer = VideoMachine.getCustomerByID(customerID);
                    }
                    capabilityGate.setNextTransition(toSelectionTransition);
                  }
                  // Eingabe falsch -> Kunden informieren und
                  // Eingabe wiederholen
                  else {
                    JOptionPane.showMessageDialog(null,
                      "CustomerID must be a positive Number!");
                    capabilityGate.setNextTransition(
                      new GateChangeTransition(capabilityGate));
                  }
                }
              }
            );
          // "Cancel"-Button neu definieren
          fs.getButton(FormSheet.BTNID_CANCEL).
            setAction( 
              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);
                }
              }
            );
        }
      }
    );
      
    // Gate zur Kundennummer-Abfrage mit erstellter TextInputForm anlegen
    capabilityGate = new UIGate((FormSheet) tif, 
                                (MenuSheet) null);
      
      
    //////////////////////////////////////////////////
    /// Selection-Gate ///////////////////////////////
    //////////////////////////////////////////////////
      
      
    // Auswahl-Gate anlegen
    // Das FormSheet wird durch die Transition waehrend der Laufzeit erzeugt
    selectionGate = new UIGate((FormSheet) null, (MenuSheet) null);
      
    // Transition zum Selection Gate
    toSelectionTransition = 
      new Transition()
      {
        public Gate perform(SaleProcess pOwner, User usr)
        {
          // am Gate darzustellendes FormSheet
          TwoTableFormSheet ttfs = 
            TwoTableFormSheet.create("Make your selection", // Titel des FormSheets
                                     cs,                    // Quell-CountingStock
                                     db,                    // Ziel-DataBasket
                                     selectionGate,         // Gate, an dem das FormSheet darzustellen ist
                                     null,                  // Comparator fuer Quelltabelle
                                     null,                  // Comparator furr Ziel-Tabelle
                                     false,        // Zeilen mit Anzahl == 0 in der Quelltabelle anzeigen?
                                     new OfferTED(true),    // TableEntryDescriptor furr Quelltabelle
                                     null,                  // TableEntryDescriptor furr Zieltabelle
                                     null                   // Transferstrategie
                                     );
                  
          // FormSheetContentCreator am Auswahl-Gate anmelden    
          ttfs.addContentCreator(
            new FormSheetContentCreator()
            {
              protected void createFormSheetContent(FormSheet fs)
              {
                // "OK"-Button neu definieren
                fs.getButton(FormSheet.BTNID_OK).
                  setAction( 
                    new sale.Action()
                    {
                      public void doAction (SaleProcess p, SalesPoint sp)
                      {
                        // Transition zum Bezahlen als naechste Transition setzen
                        selectionGate.setNextTransition(toPayingTransition);
                      }
                    }
                  );
                                               
                // "Cancel"-Button neu definieren
                fs.getButton(FormSheet.BTNID_CANCEL).
                  setAction(  
                    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;
        }
      };            
      
      
    //////////////////////////////////////////////////
    /// Rent-Gate ////////////////////////////////////
    //////////////////////////////////////////////////
      
      
    // Gate zum Ausleihen/Bezahlen anlegen
    // Das FormSheet wird durch die Transition waehrend der Laufzeit erzeugt
    rentGate = new UIGate(null, null);
      
    // Transition zum Gate, an dem zu bezahlen ist
    toPayingTransition = 
      new Transition()
      {
        public Gate perform(SaleProcess pOwner, User usr)
        {
          // Parameter zum Aufsummieren des Datenkorbes festlegen
          final DataBasketCondition dbc =
            DataBasketConditionImpl.allStockItemsWithSource(cs);
          BasketEntryValue bev = BasketEntryValues.ONLY_STOCK_ITEMS;
          QuoteValue qvSum = new QuoteValue(new IntegerValue(0), new IntegerValue(0));
                  
          // ...und Datenkorb damit aufsummieren
          pOwner.getBasket().sumBasket (dbc, bev, qvSum);
                  
          // zu zahlenden Betrag ermitteln
          toPayValue = (IntegerValue)qvSum.getBid();
                  
          // FormSheet für das naechste Gate erstellen
          FormSheet tif = new TextInputForm("Paying",
                                            "You have to pay " +
                                               myCurrency.toString (toPayValue) + ".",
                                            myCurrency.toString (toPayValue)
                                           );
                  
          // FormSheetContentCreator zum Einbauen der Buttons anmelden
          tif.addContentCreator(
            new FormSheetContentCreator()
            {
              protected void createFormSheetContent(FormSheet fs)
              {
                // als 'final' markierte Version des FormSheets anlegen
                final TextInputForm tifFinal = (TextInputForm)fs;
                                               
                // der Cancel-Button steht im Weg und wird zunaechst entfernt
                fs.removeButton(FormSheet.BTNID_CANCEL);
                                                
                // "OK"-Button neu definieren
                fs.getButton(FormSheet.BTNID_OK).
                  setAction( 
                    new sale.Action()
                    {
                      public void doAction (SaleProcess p, SalesPoint sp)
                      {
                        try {
                          // eingegebenen Text verarbeiten
                          paidValue = (IntegerValue)myCurrency.parse(tifFinal.getText());
                                                                        
                          // Der Iterator ueber die zu leihenden Videos wird initialisiert
                          videoIterator = db.iterator(dbc);
                                                                        
                          // ...und naechste Transition setzen
                          rentGate.setNextTransition(toDecisionTransition);
                        }
                        catch(ParseException pexc) {
                          // eingegebener Text konnte nicht verarbeitet werden
                          // Meldung erzeugen und ausgeben
                          MsgForm mf = new MsgForm ("Error",
                                                    "The specified amount does " +
                                                       "not have an appropriate format.");
                                                                           
                          try {
                            p.getContext().popUpFormSheet (p, mf);
                          }
                          catch(InterruptedException iexc) {
                          }
                        }
                      }
                    }
                  );
                                                
                // "Back"-Button einbauen
                fs.addButton ("Back", 101, 
                  new sale.Action()
                  {
                    public void doAction (SaleProcess p, SalesPoint sp)
                    {  
                      // naechste Transition setzen, fuehrt ohne weitere Aktionen
                      // zum Auswahl-Gate
                      rentGate.setNextTransition(
                        new GateChangeTransition(selectionGate));
                    }
                  }
                );
                                                
                // "Cancel"-Button wieder einbauen
                fs.addButton ("Cancel", 102, 
                  new sale.Action()
                  {
                    public void doAction (SaleProcess p, SalesPoint sp)
                    {
                      // naechste Transition setzen, fuehrt zum Rollback-Gate
                      rentGate.setNextTransition(
                        GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
                    }
                  }
                );
                                                
              }
            }
          );
                  
          // FormSheet am entsprechenden Gate setzen
          rentGate.setFormSheet(tif);
           
          // ...und naechstes zu betretendes Gate zurueckgeben
          return rentGate;
        }
      };
      
      
    //////////////////////////////////////////////////
    /// Decision-Gate ////////////////////////////////
    //////////////////////////////////////////////////
      
      
    // Transition zum Entscheidungsgate
    toDecisionTransition = 
      new Transition()
      {
        public Gate perform (SaleProcess pOwner, User usr)
        {
          // ausreichend Geld gegeben?
          if (paidValue.compareTo (toPayValue) >= 0) 
          {
            // zu zahlenden Betrag im Safe der Videomaschine ablegen, wenn
            // ueberhaupt etwas zu bezahlen ist
            if (toPayValue.getValue().intValue() > 0)
              ((CountingStock)Shop.getTheShop().
                getStock("coin slot")).add(CurrencyImpl.PFENNIG_STCK_1,
                  toPayValue.getValue().intValue(), pOwner.getBasket());
                     
            // Genau richtig gezahlt? - payAssessment entsprechend belegen
            if (paidValue.compareTo(toPayValue) == 0)
              payAssessment = 0;
            else
              payAssessment = 1;
          }                     
          // nein, zuwenig bezahlt - payAssessment entsprechend belegen
          else
            payAssessment = -1;
                 
          // Entscheidungsgate als naechstes zu betretendes Gate zurueckgeben
          return decisionGate;
        }
      };
      
      
    // Gate anlegen, das entscheidet, ob Wechselgeld gegeben, zum Bezahlen
    // oder zum Commit-Gate gegangen wird
    decisionGate = 
      new Gate()
      {
        public Transition getNextTransition(SaleProcess pOwner, User usr)
          throws InterruptedException
        {
          switch(payAssessment) 
          {
            // zuwenig bezahlt - Message anzeigen und zurueck zum Bezahlen
            case -1:
            // FehlerFormSheet erzeugen
              FormSheet mf = new MsgForm("Error",
                                         "You have to pay more!",
                                         false);
              // mit Hilfe des ProcessContext das FormSheet anzeigen
              pOwner.getContext().popUpFormSheet(pOwner, mf);
                        
              return new GateChangeTransition(rentGate);
                        
            // genau richtig bezahlt - Transaktion ausfuehren - zum Commit-Gate
            case  0:
              // Ausleihvorgang ausfuehren
              commitTransfer();

              return GateChangeTransition.CHANGE_TO_COMMIT_GATE;
                        
            // zuviel bezahlt - zur Wechselgeldausgabe
            case  1:
              // Ausleihvorgang ausfuehren
              commitTransfer();
 
              return toGetChangeTransition;
                        
            // falscher Wert in payAssessment - Fehlermeldung und Abbruch
            default:
              FormSheet mf2 = new MsgForm("Error",
                                          "Internal error at Decision Gate. " +
                                          "Will quit process.",
                                          false);
              pOwner.getContext().popUpFormSheet (pOwner, mf2);
              return GateChangeTransition.CHANGE_TO_QUIT_GATE;
          }
        }
               
        public void commitTransfer()
        {
          // aktuelles Datum holen
          Object date = Shop.getTheShop().getTimer().getTime();
          while (videoIterator.hasNext())
          {
            CountingStockItemDBEntry cassetteItem =
              (CountingStockItemDBEntry)videoIterator.next();
            int number = cassetteItem.count();
            for (; number > 0; number --)
            {
              // Videos in den Bestand des Kunden uebernehmen
              customer.addVideoCassette(new 
                CassetteStoringStockItem(cassetteItem.getSecondaryKey(), date));
                       
              // Vorgang in Logdatei eintragen
              try {
                Log.getGlobalLog().log(new MyLoggable(cassetteItem.getSecondaryKey(),
                                                      customer.getCustomerID(), 
                                                      date));
              }
              catch (LogNoOutputStreamException lnose) {
              }
              catch (IOException ioe) {
              }
            }
          }
        }
      };
      
      
    //////////////////////////////////////////////////
    /// GetChange-Gate ///////////////////////////////
    //////////////////////////////////////////////////
      
      
    // Gate zum Ausgeben des Wechselgeldes anlegen
    // Das FormSheet wird durch die Transition waehrend der Laufzeit erzeugt
    getChangeGate = new UIGate(null, null);
      
    // Transition zum Wechselgeld-Gate
    toGetChangeTransition = 
      new Transition()
      {
        public Gate perform(SaleProcess pOwner, User usr)
        {
          // am Gate darzustellendes FormSheet
          MsgForm mf = new MsgForm("Get Change",
                                   "You get " +
                                      myCurrency.toString((IntegerValue)paidValue.subtract(toPayValue)) +
                                        " Change.");
                  
          // 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
                      getChangeGate.setNextTransition(
                        GateChangeTransition.CHANGE_TO_COMMIT_GATE);
                    }
                  }
                ); 
              }
            }
          );
                  
          // erstelltes FormSheet am zu betretenden Gate setzen
          getChangeGate.setFormSheet(mf);
                 
          // als naechstes zu betretendes Gate zurueckgeben
          return getChangeGate;
        }
      };      
  }
   
  //// public methods ////////////////////////////////////////////////////////
   
  /**
   * Gibt das Startgate des Prozesses zurück.
   */
  public Gate getInitialGate()
  {  
    // Automat aufbauen
    setupMachine();
      
    // ...und Capability-Gate als Startgate zurueckgeben
    return capabilityGate;
  }
   
  /**
   * Übergibt das Log-Gate. Hier das Stop-Gate, da beim Beenden des
   * Prozesses kein Log-Eintrag geschrieben werden soll.
   */
  public Gate getLogGate()
  {
    return getStopGate();
  }
}