/***************************************************************************
 *   Copyright (C) 2006 by Frank Simon   *
 *   frank@linux   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <linux/input.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>
#include "debug.h"
#include "setup.h"

#include <vdr/remote.h>
#include <vdr/tools.h>
#include "touchdriver.h"
#include "touch.h"

#define BUFFER_SIZE 32
#define UINT64_MAX 0xFFFFFFFFFFFFFFFFULL

#ifndef MSC_PULSELED
#define MSC_PULSELED 0x01
#endif

int64_t GetTime()
{
  struct timeval tv;
  struct timezone tz;
  gettimeofday(&tv,&tz);

  return tv.tv_sec*1000000+tv.tv_usec;
};

cTouchDriver::cTouchDriver(cTouchScreenEnhancer * callback):
  cThread("Touchdriver(eGalax)"),
     active(false)
{
  mCallback = callback;
  OpenDevice();

  //
  d(4,"TouchDriver: constructor\n");
};

cTouchDriver::~cTouchDriver()
{
  d(4,"TouchDriver: destructor\n");
  Stop();
}

void cTouchDriver::OpenDevice()
{
  hDevice = 0;
 // How to open a touchscreen-device?
 // open(_testdevice);
  hDevice = open(TouchTFTSetup.TouchDevice, O_RDWR);
  if (hDevice < 0)
    {
      esyslog("TouchDriver: unable to open %s: %s\n", TouchTFTSetup.TouchDevice, strerror(errno));
      hDevice = -1;
    }
  d(1,"TouchDriver: OpenDevice: %s: %d\n",TouchTFTSetup.TouchDevice, hDevice);
}

bool cTouchDriver::IsOk()
{
  return(hDevice >= 0);
}


void cTouchDriver::Stop()
{
  /// wait for Thread exit
  if (hDevice>=0)
  {
   close(hDevice);
  }
  hDevice = -1;
  active = false;
  isyslog("TouchTFT: eGalax Thread STOPPED");
}

void cTouchDriver::Action(void)
{
  // Der TouchDriver läuft in einem eigenen thread
  struct input_event touchdata[BUFFER_SIZE];
  active = true;
  active = true;
  int x = 0;
  int y = 0;
  int r = 0;
  int num = 0;
  int i = 0;
  bool touched = false;

  // Dieser LOCK_THREAD führt die gesamte Procedure unter
  // LOCK aus. Wenn der cThreadLock wieder aufegöst wird, 
  // wird der Thread wieder entlocked
  active = true;
  d(1,"egalax touch thread started: hdevice=%d\n",hDevice);

  while (hDevice >= 0 && active)
    {
      LOCK_THREAD;

      /// Hauptthreadschleife.
      /// Wir warten entweder auf Fileevents oder
      /// Timerevents. Das Zyklusraster entspricht dem Entprellzyklus
      /// des Flip-o-Flop
      mTouchFlipFlop.CheckRelease();

      ///
      while (cFile::FileReady(hDevice, 100))
	{
	  /// Lesen des Input-Events
	  r = safe_read(hDevice, touchdata, sizeof(input_event) * BUFFER_SIZE);
	  if (r > 0)
	    {
	      num = r / sizeof(input_event);
	      for (i = 0; i < num; i++)
		{
		  switch (touchdata[i].type)
		    {
		    case EV_ABS:
		      // received a koordinate
		      if (touchdata[i].code == 0) // ABS_Y
			{
			  y = touchdata[i].value;
			} else
			  {
			    x = touchdata[i].value;	 /// ABS_X
			  }
		    case EV_KEY:
		      if (touchdata[i].code == 330)  /// BTN_TOUCHED
			{
			  touched = (touchdata[i].value != 0);
			};
		      break;
		    case EV_SYN:
		      // data complete
		      {
			ProcessInputEvent(touched, x, y);
		      };
		      break;
		    default:
		      /// unknown data
		      break;
		    }
		}
	    }
	  else
	    {
	      LOG_ERROR;
	    }
	}
      // Ende der Event-Einleseprocedure.
      mTouchFlipFlop.CheckRelease();
      
    }
  d(1,"TouchDriver thread ended\n");
}

void cTouchDriver::ProcessInputEvent(bool touch, int x, int y)
{
  if (mCallback == 0) return;

  // enptprellen berücksichtigen
  bool oldstate = mTouchFlipFlop.GetState();
  
  if (touch) 
    {
      mTouchFlipFlop.SignalPress();
    }
  else
    {
      mTouchFlipFlop.SignalRelease();
    };

  /// 
  if ((oldstate != mTouchFlipFlop.GetState()) )
    {
      mCallback->TouchEvent(x,y,mTouchFlipFlop.GetState()); 
    }
}

/**
 * Wie soll der Flip_O_Flop arbeiten:
 * Das Problem ist, dass der Touch einen Meldungsschwall absetzt,
 * wenn er bettigt ist. 
 * Es kommt also zu Prelleffekten.
 * Daher soll der Flip-o-Flop neben dem Hauptzustand (Touched)
 * zwei Vorzustnde verwalten (pretouched und predetouched)
 * Diese Zustaende zeigen Prelleffekte an.
 * Wenn das erste Touch-Signal eintrifft,
 * wird zunchst pretouched auf true gesetzt.
 * wird ein Detouch-Signal erhalten, bleibt pretouched auf true,
 * es wird aber preuntouched gesetzt.
 * 
 * Preuntouched wird immer dann zurueckgesetzt, wenn ein Touchsignal kommt
 * Werden n Touchsignale nacheinander empfangen, ohne einen Detouch,
 * so wird Touched gesetzt.
 * 
 * Wenn Preuntouched gesetzt wird, wird ein Timer aufgezogen.
 * Dieser Timer laeuft nach n ms ab. Wenn er ablaeuft ohne ein neues Touch,
 * Geht der Zustand des Flip-o-Flop auf Untouched.
 */

cTouchFlipFlop::cTouchFlipFlop()
{
  state = false;
  pretouched = false;
  preuntouched = false;
  startuntouchtime = GetTime();
  starttouchtime = GetTime();
}

void cTouchFlipFlop::SignalPress()
{
  if (preuntouched)
  {
    d(4,"SignalPress: Loesche PREUNTOUCHED\n");
    preuntouched = false;
  };
  // Ist der Zustand gerade "UNTOUCHED"
  if (GetState())
  {
    // Touchereignis in TOUCHSTATE
    // wird ignoriert
    d(4,"SignalPress: Ignored (Zustand ist bereits TOUCHED)\n");
    // Aber wenn der touch lange wiederholt wurde: FIXME
    // Dann muss der Touch als stehende Taste angesehen werden
  }
  else
  {
    // in jedem Fall geht jetzt pretouch auf true
    if (!pretouched)
    {
	// ruecksetzen des Zaehlers
	touchcount = 1;
	pretouched = true;
	starttouchtime = GetTime();
    }
    else
    {
	touchcount += 1;
	d(4,"SignalPress: PRETOUCH=%d\n",touchcount);
    };
    /// Wenn der touchcounter jetzt gross genug ist,
    /// wird das Touchereignis gesetzt
    if (touchcount>0)
    {
	SetState(true);
    }
  }
}

void cTouchFlipFlop::SignalRelease()
{
  // ist detouched
  // Ist der Zustrand gerade TOUCHED?
  d(4,"FLIP-O-FLOP: Signal Release\n");
  if (preuntouched == false)
    {
	d(4,"FLIP-O-FLOP: Signal Release started UNTOUCH\n");
	preuntouched = true;
	startuntouchtime = GetTime();
	// Touchzaehler zuruecksetzen
	touchcount = 0;
    };
}

void cTouchFlipFlop::CheckRelease()
{
  if (GetState() && preuntouched)
  {
    uint64_t dt = deltatime(startuntouchtime);
    d(4,"FLIP-O-FLOP: CheckRelease in TOUCH: dt=%d\n", dt);    
    if (dt>(TouchTFTSetup.entprell_ms*1000u))
    {
      d(1,"FLIP-O-FLOP: RELEASE in TOUCH: dt=%d\n", dt);    
      SetState(false);
    }
  }
}

uint64_t cTouchFlipFlop::deltatime(uint64_t t0) 
{
    uint64_t dt = 0;
    uint64_t t = GetTime();
    if (t < t0)
    {
    /// Ueberlauf testen
	dt = t + (UINT64_MAX - t0);
	d(4,"deltatime: Overrun : %d - %d = %d\n", t, t0 , dt);
    }
    else
    {
        dt = t - t0;
    };
return(dt);
}
