#ifndef MICROCONTROLLER_H
#define MICROCONTROLLER_H

#include "seemicro.h"
#include <iostream>
#include <list>
#include <iomanip>
extern "C"
{
#include <sys/time.h>
}

using namespace std;

namespace seemicro
{

/**
  * 0 = fatal
  * 1 = warnings
  * 2 = info
  * 3 = flood
  */
int DEBUGLEVEL = 0;

struct MicroController
{
  /**
    * wird die erkannten Parameter enthalten
    */
  Micro *ergebnis;

  /**
    * wird für Korrelation gebraucht
    */
  Micro *kern;

  ImageSource *src;

  /**
    * NULL, wenn src nicht vom Typ videoDevice ist
    */
  videoDevice *v;

  GrayModule* g;
  CannyModule *ca;
  CorrelationModule *co;
  SearchRadiusModule *sr;
  SearchAnglesAdam *se;
  FindLEDSModule *sleds;
  HSVRange *range;

  /**
    * Liste der Erkennungsmodule.
    * Sie verwendet XModule* statt XModule, damit Polymorphismus funktioniert -
    * Siehe B. Stroustrup "The C++ Programming Language" §12.2.6, S. 312
    */
  list<RecognitionModule*> frameRecognitionList;
  list<OutputModule*> outputList;

  /**
    * Whether currently processed frame is a keyframe
    */
  bool keyframe;
  struct timeval timerStart, timerStop;

  long timeForFrame, timeForKeyFrame, timeForFrameOut, timeForKeyFrameOut;

  void startTimer()
  {
    if(0 != gettimeofday(&timerStart, 0))
      perror("startTimer(): gettimeofday() failed");
  }

  void stopTimer()
  {
    if(0 != gettimeofday(&timerStop, 0))
      perror("stopTimer(): gettimeofday() failed");
  }

  long getTimer()
  {
    int dseconds = timerStop.tv_sec-timerStart.tv_sec;
    long diff = 1000000*(dseconds) + timerStop.tv_usec - timerStart.tv_usec;
    if(diff<0) cerr << "start: " <<timerStart.tv_sec << " " << timerStart.tv_usec
      << " stop: " << timerStop.tv_sec << " " << timerStop.tv_usec << endl;
    return diff;
  }

  void modulesProcessFrame(void)
  {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << " ProcessFrame: ";
#endif
    myrect *ROI, ROI2;

    if(se) ROI=&se->rect;
    else if(co && co->micro.radiusValid && co->micro.mitteValid)
    {
      mypoint p = mypoint(int(co->micro.radius), int(co->micro.radius));
      ROI2 = myrect(co->micro.mitte - p, co->micro.mitte+p);
      ROI2.crop(co->src.img->width, co->src.img->height);
      ROI = &ROI2;
    }
    else ROI=NULL;

    list<RecognitionModule*>::iterator i;
    for(i = frameRecognitionList.begin();
        i != frameRecognitionList.end(); i++)
    {
#ifdef DEBUG
      if(DEBUGLEVEL>=3) cerr << "+";
#endif
      (*i)->processFrame(ROI);
    }
  }

  void modulesProcessKeyFrame(void)
  {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << " key: ";
#endif
    list<RecognitionModule*>::iterator i;
    for(i = frameRecognitionList.begin();
        i != frameRecognitionList.end(); i++)
    {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << "+";
#endif
      (*i)->processKeyFrame();
    }
  }

  void callOutputModules(bool keyframe)
  {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << " out: ";
#endif
    list<OutputModule*>::iterator i;
    for(i = outputList.begin();
        i != outputList.end(); i++)
    {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << "#";
#endif
      (*i)->output(keyframe);
    }
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << ".\n";
#endif
  }

  /**
    * Check für paramChanged/src.outputChanged
    * könnte per default in processKeyFrame() erfolgen
    */
  void reEvalIfParamChanged(void)
  {
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << "Reeval: ";
#endif
    bool cascade = false;
    list<RecognitionModule*>::iterator i;
    for(i = frameRecognitionList.begin();
        i != frameRecognitionList.end(); i++)
    {
#ifdef DEBUG
      if(DEBUGLEVEL>=3) cerr << (cascade ? "+" : "-");
#endif
      if((!cascade) && ((*i)->paramChanged)) cascade = true;
      if(!cascade) continue;
      (*i)->processKeyFrame();
    }
#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << ".";
#endif

    if(cascade) callOutputModules(true);
  }

  /**
    * Zuviele Logmeldungen brauchen viel Zeit,
    * denn das Log-xterm wird häufig neu gezeichnet.
    *
    * Möglich sind 13 Frames/s auf einem 700MHz Athlon,
    * bei paralleler Videowiedergabe mit MPlayer
    * skaliert von 720x576 (Videodatei) auf
    * 280x210 (Verarbeitung hier).
    */
  void handleFrame()
  {
#ifdef DEBUG
    if(DEBUGLEVEL>=3)
      cerr << "->MicroController::handleFrame\n";
#endif
    assert(ergebnis);
    keyframe = !v || // kein videomodus
      !ergebnis->mitteValid || // mittelpunkt noch nicht bekannt
      !(v->framesCaptured % v->keyFrameRate);

    startTimer();

    if((DEBUGLEVEL>=2) && (v!=0))
    {
      cout << "\rframe: " << setw(2) <<
        (v->framesCaptured % v->keyFrameRate);
      cout << (keyframe ? " keyframe" : "          ") << flush;
    }

    if(keyframe) modulesProcessKeyFrame();
    else modulesProcessFrame();

    stopTimer();
    if(keyframe) timeForKeyFrame = getTimer();
    else timeForFrame = getTimer();

    startTimer();
    callOutputModules(keyframe);
    stopTimer();
    if(keyframe) timeForKeyFrameOut = getTimer();
    else timeForFrameOut = getTimer();

#ifdef DEBUG
    if(DEBUGLEVEL>=3) cerr << "<-MicroController::handleFrame\n";
#endif
  }

  /**
    * Alternativer Konstruktor zum Chainselberbauen.
    * Es müssen src und ergebnis initialisiert werden, sowie
    * module zur Erkennung und Ausgabe registriert werden.
    */
  MicroController()
  {
    range = NULL;
    sleds = NULL;
    g = NULL;
    se = NULL;
    sr = NULL;
    co = NULL;
    ca = NULL;
    kern = NULL;
  }

  MicroController(ImageSource *quelle, Micro *Amicro) : ergebnis(Amicro), src(quelle)
  {
    range = NULL;
    sleds = NULL;
    g = NULL;
    se = NULL;
    sr = NULL;
    co = NULL;
    ca = NULL;
    kern = NULL;

    // clone ergebnis to create kern of exactly same type
    kern = ergebnis->clone();

    // klappt immer. liefert NULL, wenn cast nicht möglich
    v = dynamic_cast<videoDevice*>(src);

    if(DEBUGLEVEL>=2) cerr << "Creating Recognition Modules...";
    src->registerMe(frameRecognitionList);

    if(src->img->nChannels>1)
    {
      g = new GrayModule(src);
      g->registerMe(frameRecognitionList);
      ca = new CannyModule(*g);
    }
    else
    {
      ca = new CannyModule(*src);
    }
    ca->registerMe(frameRecognitionList);

    co = new CorrelationModule(*ergebnis, *ca, kern);
    co->registerMe(frameRecognitionList);

    sr = new SearchRadiusModule(*co);
    sr->registerMe(frameRecognitionList);

    range = new HSVRange();
    range->registerMe(frameRecognitionList);
    // Dieses Modul verhält sich verschieden,
    // je nachdem ob es auf Adam oder Eva schaut
    sleds = new FindLEDSModule(*src, *ergebnis, *co, *range);
    sleds->registerMe(frameRecognitionList);

    // Only if we try to recognize a micro.Adam
    MicroAdam *adam = dynamic_cast<MicroAdam*>(ergebnis);
    if(adam)
    {
      se = new SearchAnglesAdam(*adam, *co, *ca);
      se->registerMe(frameRecognitionList);
    }

  }

  /**
    * Destroys things allocated with new
    */
  ~MicroController()
  {
    if(DEBUGLEVEL>=2) cerr << "sleds ";
    if(sleds)
    {
      delete sleds; sleds = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "se ";
    if(se)
    {
      delete se; se = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "sr ";
    if(sr)
    {
      delete sr; sr = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "co ";
    if(co)
    {
      delete co; co = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "ca ";
    if(ca)
    {
      delete ca;ca = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "g ";
    if(g)
    {
      delete g; g = NULL;
    }
    if(DEBUGLEVEL>=2) cerr << "kern ";
    if(kern)
    {
      delete kern; kern = NULL;
    }

  }

}; // MicroController

} // seemicro

#endif
