// $Source: /usr/local/SimSoft/Sfx3D/src/gsl/drehpuff.cpp,v $
// $Revision: 1.2 $
// $Author: baum $
// $Date: 2001/11/29 15:18:09 $
//
// Login name of the user who locked the revision
// $Locker:  $
// Tag name used to checkout the file
// $Name:  $
// State assigned to revision
// $State: Exp $
//
//
// $Log: drehpuff.cpp,v $
// Revision 1.2  2001/11/29 15:18:09  baum
// -Realzeitsteuerung eingebaut
// -Handsteuerung eingebaut
//

#include <stdlib.h>
#include <float.h>
#include "MathDefs.h"
#include "Messung.h"
#include "XGraph/Wrap.h"
#include <iostream.h>

#define _DREHPUFF_CPP
#include "modell.h"
#include "drehpuff.h"

#include "sfx.h"
#include "transp.h"
#include "route.h"
#include "ereignis.h"
#include "regal.h"
#include "platz.h"
#include "realzeit.h"
#include "arbplan.h"

//-----------------------------------------------------
//   Klasse typDrehpuffer 
//-----------------------------------------------------                      

typDrehpuffer::typDrehpuffer (unsigned n, unsigned b)
 : typBaustein            (n, b),
   ObjDrehGeschwindigkeit (0.0),
   ObjDrehSinn            (0),
   ObjZielWinkel          (),
   ObjAngeforderteTE     ()
 { }

typDrehpuffer::~typDrehpuffer ()
 { }

/*virtual*/ void typDrehpuffer::typTGSlesen (InputFile& file) {
  typBaustein::typTGSlesen (file);
  if (ObjPalPlaetze.Count () < 1)
    sim_error ("DREHPUFF", "typDrehpuffer::typTGSlesen",
               "Drehpuffervariante mit < 2 Palettenplaetze", ObjVariante, FATAL);

  TGBPermPattern* myTGBPattern = MySFXModel->TGBLibrary ()->findPerm (ObjBasistyp, ObjVariante);
  const DynArray<GeoObject>& go (myTGBPattern->AllGeometries ());
  go.First ();
  if (go.Next ()) {
    WrapBox box;
    ObjDrehGeometry = new GeoObject ();
    GeoObject* geo = new GeoObject ();
    geo->copyContents (*(go ()));
    geo->setParent (*ObjDrehGeometry);
    geo->setSelectionToMum ();
    ObjDrehGeometry->setParent (ObjGeometry);
    ObjDrehGeometry->setSelectionToMum ();
    // Count ist immer min 1, siehe Ueberpruefung oben!!
    unsigned lastIndex = ObjPalPlaetze.Count () - 1;
    for (unsigned i = 0; i <= lastIndex; i++)
      box.adjust (ObjPalPlaetze[i]->Position ());
    Vec3D v (box.Min + ((box.Max - box.Min) * 0.5)); // center!!
    ObjDrehGeometry->setPos (ObjGeometry.PtToLocal (v), 0);
    ObjKorrekturWinkel = ObjWinkel;
  }
}

/*virtual*/ void typDrehpuffer::typTTPlesen (InputFile& file) {
  SFXToken tok;

  tok.nextToken (file);
  ObjAufnahmezeit = SimTime (atof (tok ()) * SimTime::SecondsPerMinute ());

  tok.nextToken (file);
  ObjDrehGeschwindigkeit = atof (tok ());
  if (ObjDrehGeschwindigkeit <= 0.0)
    sim_error ("DREHPUFF", "typDrehpuffer::typTTPlesen",
               "Drehpuffer mit unzulaessiger DrehGeschwindigkeit", ObjNummer, FATAL);

  tok.nextToken (file);
  ObjPhysKapazitaet = ObjKapazitaet = atoi (tok ());
  if (ObjKapazitaet > ObjPalPlaetze.Count ())
    sim_error ("DREHPUFF", "typDrehpuffer::typTTPlesen",
               "Kapazitaet groesser als Anzahl PalPlaetze", ObjNummer, FATAL);

  file.jump (100); //EOL
}

/*virtual*/ const double& typDrehpuffer::setGeschwindigkeit (const double& wert) {
  if (wert <= 0.0)
    sim_error ("BAUSTEIN", "typDrehpuffer::setGeschwindigkeit",
               "negative Drehgeschwindigkeit", ObjNummer, FATAL);
  return ObjDrehGeschwindigkeit = wert;
}

/*virtual*/ ostream& typDrehpuffer::typTTPschreiben (ostream& ostrm) const {
  ostrm.precision (2);
  ostrm << "TTP> ";
  Id_Schreiben (ostrm);
  ostrm << " ";
  ostrm.width (6);
  ostrm << ObjAufnahmezeit.inMinutes () << " ";
  ostrm.width (6);
  ostrm << ObjDrehGeschwindigkeit << " ";
  ostrm.width (2);
  ostrm << ObjKapazitaet << endl;
  return ostrm;
}

/*virtual*/ typTransportEinheit *typDrehpuffer::gibTransportEinheit (typMeldung* zM) {
  typTransportEinheit* zTE = zM->TransportEinheit ();
  if (!ObjAngeforderteTE.remove (*zTE))
    sim_error ("BAUSTEIN", "typDrehpuffer::gibTransportEinheit",
               "falsches TransportEinheit angefordert", ObjNummer, FATAL);
  decBelegung ();
  zTE->AktPlatz ()->setTransportEinheit (NULL);
  setZustand (AKTIVIERT);
  planesofort (ETaabgeben, ObjNummer, zM->NachBaustein ()->Nummer (), zTE->AktPlatz ()->Nummer ());
  return zTE;
}

/*virtual*/ void typDrehpuffer::a_meldewarten () {
  sim_error ("DREHPUFF", "typDrehpuffer::a_meldewarten",
             "Unzulaessiger Aufruf fuer Drehpuffer", ObjNummer, FATAL);
}

/*virtual*/ void typDrehpuffer::a_aufnehmen () {
  typMeldung *zM = holeMeldung ();
  ObjZielBaustein->gibTransportEinheit (zM);
  RzsSenden (ObjNummer, ETaaufnehmen, ObjTransportEinheit->ZielPlatz ()->Nummer ());
  aufnehmenEinleiten (ObjTransportEinheit->ZielPlatz ()->Position (), ObjAufnahmezeit);
}

/*virtual*/ void typDrehpuffer::e_aufnehmen () {
  typPlatz *p (ObjTransportEinheit->AktPlatz ());
  if (p->Buchung () != ObjTransportEinheit)
    sim_error ("DREHPUFF", "typDrehpuffer::e_aufnehmen",
               "TransportEinheit ist nicht auf Platz gebucht", ObjNummer, FATAL);
  p->setBuchung (NULL);
  ObjBuchung--;
  ObjTransportEinheit->positionieren (p->Position ());
//  ObjTransportEinheit->faerbeGestalt (default);

  if (ObjStoerDauer > SimTime (0.0))
    planesofort (ETastoerung, ObjNummer);
  else {
    setZustand (RUHE);
    ObjTransportEinheit->Anmelden ();
    ObjTransportEinheit = NULL;
    if (!(ObjMeldungen.isEmpty () && ObjAngeforderteTE.isEmpty ()) 
        || ObjStoerDauer > SimTime (0.0)) 
      planesofort (ETaktivieren, ObjNummer, ObjNummer);
  }  
  planesofort (ETeabgeben, ObjZielBaustein->Nummer (), ObjNummer);
}

/*virtual*/ void typDrehpuffer::a_drehen () {
  double drehWinkel = (ObjZielWinkel - ObjWinkel).deg();
  ObjDrehSinn = Sign(drehWinkel);
  double drehZeit = fabs(drehWinkel) / ObjDrehGeschwindigkeit;
  setZustand (DREHEN);
  if (MySFXModel->RealTime ())
    RzsSenden (ObjNummer, ETadrehen, ((int) Round (drehWinkel)) / 90);
  planedelta (drehZeit, ETedrehen, ObjNummer, ObjZielBaustein->Nummer (), ((int) Round (drehWinkel)));
  if (MySFXModel->Cycle() && drehZeit) {
    ObjTaktDrehGeschwindigkeit = fabs(drehWinkel)/drehZeit;
    if (ObjTaktDrehGeschwindigkeit) {
      ObjTaktZeit = (ClassDrehSchritt * SimTime::SecondsPerMinute ()) / ObjTaktDrehGeschwindigkeit;
      if (MySFXModel->Debug ()) {
        dprintf ("drehZeit=%8.5f TaktZeit=%8.5f\n", drehZeit, ObjTaktZeit.inMinutes ());
      }
      if (ObjTaktZeit > SimTime(0.0)) {
         ObjTaktSchritte = (unsigned) ((drehZeit) / ObjTaktZeit.inMinutes ());
         if (MySFXModel->Debug ()) {
            dprintf ("Drehen %3d: drehWinkel=%8.5f Schritte=%8.5f\n", ObjNummer, drehWinkel, ObjTaktSchritte);
         }
         if (ObjTaktSchritte > 0) {
            dreh_takt();
         }
      }
    }
  }
  if (((!MySFXModel->Cycle ()) || (ObjTaktSchritte == 0)) && drehWinkel != 0.0)
    drehAufWinkel (ObjZielWinkel);
}

/*virtual*/ void typDrehpuffer::dreh_takt () {
  if (ObjZustand != DREHEN) return;
  if (ObjTaktSchritte > 0) {
    ObjTaktSchritte--;
    drehEinStueck (Angle (ObjDrehSinn * ClassDrehSchritt));
    planedelta (ObjTaktZeit, ETdrehTakt, ObjNummer, ObjZielBaustein->Nummer (), ObjTaktSchritte);
  }
  else
    drehAufWinkel (ObjZielWinkel);
  if (MySFXModel->Debug ())
    dprintf ("Drehtakt %1d: w=%5.2f\n", ObjNummer, ObjWinkel.deg ());
}

/*virtual*/ void typDrehpuffer::e_drehen () {
  if (MySFXModel->Cycle ()) drehAufWinkel (ObjZielWinkel);

  if (ObjStoerDauer > SimTime (0.0))
    planesofort (ETastoerung, ObjNummer);
  else {
    if (ObjTransportEinheit)  wartenaufnehmen (ObjTransportEinheit, ObjZielBaustein);
    else if (!ObjAngeforderteTE.isEmpty ())  {
        setZustand (WARTEN);
        planesofort (ETaktivieren, ObjZielBaustein->Nummer (), ObjNummer);
    }
    else {
      setZustand (RUHE);
      aktiviereEingaenge ();
    }
  }
}

/*virtual*/ void typDrehpuffer::a_abgeben () {
   setZustand (ABGEBEN);
   RzsSenden (ObjNummer, ETaabgeben, -notiz::AktInfo3 ());
}

/*virtual*/ void typDrehpuffer::e_abgeben () {
  setZustand (RUHE);
  ObjZielBaustein = NULL;
  if ((!ObjMeldungen.isEmpty ()) || (!ObjAngeforderteTE.isEmpty ()))
    planesofort (ETaktivieren, ObjNummer, ObjNummer);
  aktiviereEingaenge ();
}

/*virtual*/ void typDrehpuffer::eStoeren () {
  ObjStoerEnde = NULL;
  if (ObjStoerGeschichte) ObjStoerGeschichte->erfassen (0.0);
  switch (ObjAlterZustand) {
    case AUFNEHMEN :  setZustand (RUHE);
                      ObjTransportEinheit->Anmelden ();
                      ObjTransportEinheit = NULL;
                      if (!(ObjMeldungen.isEmpty () && ObjAngeforderteTE.isEmpty ()) 
                          || ObjStoerDauer > SimTime (0.0)) 
                        planesofort (ETaktivieren, ObjNummer, ObjNummer);
                      break;

    case DREHEN    :  if (ObjTransportEinheit)
                        wartenaufnehmen (ObjTransportEinheit, ObjZielBaustein);
                      else if (!ObjAngeforderteTE.isEmpty ())  {
                        setZustand (WARTEN);
                        planesofort (ETaktivieren, ObjZielBaustein->Nummer (), ObjNummer);
                      }
                      else  setZustand (RUHE);
                      break;

    default        :  setZustand (ObjAlterZustand); // RUHE or POLLING!!!
                      planesofort (ETaktivieren, ObjNummer, ObjNummer);
                      aktiviereEingaenge ();
                      aktiviereAusgaenge ();
                      break;
  }
}

/*virtual*/ bool typDrehpuffer::ausrichtenAufBaustein (typBaustein* zielBst) {
  if (ObjZustand == RUHE) {
    ObjZielBaustein = zielBst;
    Angle a = DrehWinkel (*(ObjPalPlaetze[0]), ObjZielBaustein->UebernahmePos (*this));
    if (a) {
      ObjZielWinkel = ObjWinkel + a;
      setZustand (AKTIVIERT);
      planesofort (ETadrehen, ObjNummer, ObjZielBaustein->Nummer (), (int) Round (a.deg ()));
    }
    return true;
  }
  return false;
}

/*virtual*/ bool typDrehpuffer::hasKapazitaet (typTransportEinheit*) const {
  return (ObjBelegung + ObjBuchung) < (ObjKapazitaet - 1);
}

/*virtual*/ typPlatz* typDrehpuffer::waehlePlatz (typTransportEinheit* zTE) {
  unsigned i;
  for (i = 0; i < ObjKapazitaet; i++)
    if (ObjPalPlaetze [i]->Buchung () == zTE)  return ObjPalPlaetze [i];
  for (i = 0; i < ObjKapazitaet; i++)
    if (ObjPalPlaetze [i]->isVerfuegbar ())  break;
  if (i < ObjKapazitaet) {
    ObjPalPlaetze [i]->setBuchung (zTE);
    ObjBuchung++;
    return ObjPalPlaetze [i];
  }
  return NULL;
}

/*virtual*/ Vec3D typDrehpuffer::UebernahmePos (const typBaustein&) const {
  return (ObjGeometry.Pos ());
}

/*virtual*/ Vec3D typDrehpuffer::UebergabePos (const typBaustein&) const {
  return (ObjGeometry.Pos ());
}

/*virtual*/ bool typDrehpuffer::isUebergabeBereit (typTransportEinheit* zTE, typBaustein* fuerWen) const {
  return (((ObjZustand == RUHE) || (ObjZustand == WARTEN))
          && !DrehWinkel (*zTE->AktPlatz (), fuerWen->UebernahmePos (*this))
          && ObjAngeforderteTE.find (*zTE));
}

/*virtual*/ void typDrehpuffer::UebergabeVorbereiten (typTransportEinheit* zTE) {
  if (!ObjAngeforderteTE.find (*zTE))  {
    ObjAngeforderteTE.append (*zTE);
    planesofort (ETaktivieren, ObjNummer, ObjNummer);
  }
}

/*virtual*/ unsigned typDrehpuffer::AnzahlArbeitsplanVorhanden (unsigned arbNr, unsigned istv /*= 0*/) const {
  unsigned anzTE = 0;
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit *w = ObjPalPlaetze[i]->TransportEinheit ();
    if (w && w->hatArbeitsplan (arbNr, istv))  anzTE++;
  }
  return anzTE;
}

/*virtual*/ unsigned typDrehpuffer::AnzahlArtikelVorhanden (unsigned artNr, unsigned istv /*= 0*/) const {
  unsigned anzTE = 0;
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit *w = ObjPalPlaetze[i]->TransportEinheit ();
    if (w && w->hatArtikel (artNr, istv))  anzTE++;
  }
  return anzTE;
}

/*virtual*/ double typDrehpuffer::MengeArbeitsplanVorhanden (unsigned arbNr, unsigned istv /*= 0*/) const {
  double mengeTE = 0;
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit *w = ObjPalPlaetze[i]->TransportEinheit ();
    if (w && w->hatArbeitsplan (arbNr, istv))  mengeTE += w->Menge ();
  }
  return mengeTE;
}

/*virtual*/ double typDrehpuffer::MengeArtikelVorhanden (unsigned artNr, unsigned istv /*= 0*/) const {
  double mengeTE = 0;
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit *w = ObjPalPlaetze[i]->TransportEinheit ();
    if (w && w->hatArtikel (artNr, istv))  mengeTE += w->Menge ();
  }
  return mengeTE;
}

/*virtual*/ void typDrehpuffer::individuellAktivieren (typBaustein* vonWem) {
  if (((ObjZustand == RUHE) || (ObjZustand == POLLING))
      && !ObjAngeforderteTE.isEmpty ()) {
    typTransportEinheit* zTE = ObjAngeforderteTE.First ();
    ObjZielBaustein = zTE->Meldung ()->NachBaustein ();
    Angle a = DrehWinkel (*zTE->AktPlatz (), ObjZielBaustein->UebernahmePos (*this));
    if (a)  {
      ObjZielWinkel = ObjWinkel + a;
      setZustand (AKTIVIERT);
      planesofort (ETadrehen, ObjNummer, ObjZielBaustein->Nummer (), (int) Round (a.deg ()));
    }
    else  {
      setZustand (WARTEN);
      planesofort (ETaktivieren, ObjZielBaustein->Nummer (), ObjNummer);
    }
    return;
  }

  if ((ObjZustand == WARTEDP) && (ObjZielBaustein == vonWem))  {
    setZustand (AKTIVIERT);
    planesofort (ETaaufnehmen, ObjNummer, ObjZielBaustein->Nummer ());
    return;
  }

  if (!((ObjZustand == RUHE) || (ObjZustand == POLLING)))  return;

  typMeldung* zM = findeAusfuehrbareMeldung ();
  if (zM) {
    ObjZielBaustein = zM->VonBaustein ();
    ObjTransportEinheit = zM->TransportEinheit ();
    Angle a = DrehWinkel (*ObjTransportEinheit->ZielPlatz (), ObjZielBaustein->UebergabePos (*this));
    if (a)  {
      ObjZielWinkel = ObjWinkel + a;
      setZustand (AKTIVIERT);
      planesofort (ETadrehen, ObjNummer, ObjZielBaustein->Nummer (), (int) Round (a.deg ()));
    }
    else  wartenaufnehmen (ObjTransportEinheit, ObjZielBaustein);
  }
  else
    if (!(ObjMeldungen.isEmpty ()) && ((ObjBelegung + ObjBuchung) < ObjKapazitaet)) {
      setZustand (POLLING);
//      planedelta (0.1, ETaktivieren, ObjNummer, ObjNummer);
    }
}

typMeldung* typDrehpuffer::findeAusfuehrbareMeldung () {
  for (LCursor<typMeldung> c (ObjMeldungen); c; c++)
    if (platzsuchen (c ()->TransportEinheit ()))
      return &(ObjMeldungen.prepend (*(ObjMeldungen.remove (c))));
  return NULL;
}

typPlatz* typDrehpuffer::platzsuchen (typTransportEinheit* zTE) {
  typPlatz *pBuch = NULL, *pZiel = NULL, *pFrei = NULL;
  double len = DBL_MAX;
  for (register unsigned i = 0; i < ObjKapazitaet; i++)
    if (!ObjPalPlaetze [i]->TransportEinheit ()) {
      if (ObjPalPlaetze [i]->Buchung () == zTE) pBuch = ObjPalPlaetze [i];
      if (!ObjPalPlaetze [i]->Buchung ())      pFrei = ObjPalPlaetze [i];
      double aktLen = (zTE->Position () - ObjPalPlaetze[i]->Position ()).len ();
      if (aktLen < len) {
        len = aktLen;
        pZiel = ObjPalPlaetze[i];
      }
    }
  if (!pZiel)  return NULL;
  if (pBuch) {
    pBuch->setBuchung (pZiel->Buchung ());
    pZiel->setBuchung (zTE);
    return zTE->setZielPlatz (pZiel);
  }
  typBaustein* zNext = zTE->nextBaustein ();
  if (hasKapazitaet (zTE)
      || (pFrei && (ObjEingaenge.Count () == 1)) 
      || (pFrei &&
          ((zNext->Basistyp() == tMaschine)
           ? zNext->hasKapazitaet (zTE) && zTE->setBuchPlatz (zNext->waehlePlatz (zTE))
           : true)
         )) {
    typTransportEinheit* zBuch = pZiel->Buchung ();
    if (zBuch) {
      pFrei->setBuchung (zBuch);
      if (zBuch->ZielPlatz () == pZiel)  zBuch->setZielPlatz (pFrei);
      else                               zBuch->setBuchPlatz (pFrei);
    }
    pZiel->setBuchung (zTE);
    ObjBuchung++;
    return zTE->setZielPlatz (pZiel);
  }
  return NULL;
}

void typDrehpuffer::drehEinStueck (const Angle& deltaw) {
  drehen (deltaw);
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit* zTE = ObjPalPlaetze [i]->TransportEinheit ();
    if (zTE) {
      zTE->positionieren (ObjPalPlaetze [i]->Position ());
      zTE->drehen (deltaw);
    }
  }
}

void typDrehpuffer::drehAufWinkel (const Angle& absAngl) {
  Angle oldWinkel (ObjWinkel);
  ausrichten (absAngl);
  for (unsigned i = 0; i < ObjKapazitaet; i++) {
    typTransportEinheit* zTE = ObjPalPlaetze [i]->TransportEinheit ();
    if (zTE) {
      zTE->positionieren (ObjPalPlaetze [i]->Position ());
      zTE->ausrichten (absAngl + zTE->Winkel () - oldWinkel);
    }
  }
}

Angle typDrehpuffer::DrehWinkel (const typPlatz& p, const Vec3D& nachPos) const {
  Vec3D vPlatz (p.Position ());
  Vec3D vZiel  (nachPos.X (), nachPos.Y (), p.Position ().Z ());
  ObjGeometry.PtToLocal (vPlatz);
  ObjGeometry.PtToLocal (vZiel);
  Angle a (vPlatz.angle (vZiel));
  return (vPlatz.cross (vZiel).Z () < 0.0) ? -a : a;
}

