//   rdgpio.cpp
//
//   A driver for General-Purpose I/O devices.
//
//   (C) Copyright 2002-2003,2016 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU Library General Public License 
//   version 2 as published by the Free Software Foundation.
//
//   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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <qobject.h>

#include <rdgpio.h>

RDGpio::RDGpio(QObject *parent)
  : QObject(parent)
{
  Clear();

  //
  // Input Timer
  //
  gpio_input_timer=new QTimer(this,"input_timer");
  connect(gpio_input_timer,SIGNAL(timeout()),this,SLOT(inputTimerData()));
  gpio_revert_mapper=NULL;
  for(int i=0;i<GPIO_MAX_LINES;i++) {
    gpio_revert_timer[i]=NULL;
  }
  for(int i=0;i<KEY_MAX;i++) {
    gpio_key_map[i]=-1;
  }
}


QString RDGpio::device() const
{
  return gpio_device;
}


void RDGpio::setDevice(QString dev)
{
  gpio_device=dev;
}


QString RDGpio::description() const
{
  return gpio_description;
}


RDGpio::Mode RDGpio::mode()
{
  struct gpio_info info;

  switch(gpio_api) {
      case RDGpio::ApiGpio:
	ioctl(gpio_fd,GPIO_GETINFO,&info);
	return (RDGpio::Mode)info.mode;

      case RDGpio::ApiInput:
	return RDGpio::Auto;
  }
  return RDGpio::Auto;
}


void RDGpio::setMode(RDGpio::Mode mode)
{
  unsigned umode=(unsigned)mode;

  switch(gpio_api) {
      case RDGpio::ApiGpio:
	ioctl(gpio_fd,GPIO_SETMODE,&umode);
	RemapTimers();
	break;

      default:
	break;
  }
}


bool RDGpio::open()
{
  int ver;
  
  if(gpio_open) {
    return false;
  }
  if((gpio_fd=::open((const char *)gpio_device,O_RDONLY|O_NONBLOCK))<0) {
    return false;
  }
  if(ioctl(gpio_fd,GPIO_GETINFO,&gpio_info)==0) {
    gpio_api=RDGpio::ApiGpio;
    InitGpio();
    RemapTimers();
  }
  else {
    if(ioctl(gpio_fd,EVIOCGVERSION,&ver)==0) {
      gpio_api=RDGpio::ApiInput;
      InitInput();
    }
    else {
      ::close(gpio_fd);
      return false;
    }
  }
  gpio_open=true;
  gpio_input_timer->start(GPIO_CLOCK_INTERVAL);
  return true;
}


void RDGpio::close()
{
  if(!gpio_open) {
    return;
  }
  gpio_input_timer->stop();
  ::close(gpio_fd);
  gpio_open=false;
  if(gpio_revert_mapper!=NULL) {
    delete gpio_revert_mapper;
    gpio_revert_mapper=NULL;
  }
  for(int i=0;i<outputs();i++) {
    if(gpio_revert_timer[i]!=NULL) {
      delete gpio_revert_timer[i];
      gpio_revert_timer[i]=NULL;
    }
  }
}


int RDGpio::inputs() const
{
  if(!gpio_open) {
    return 0;
  }
  return gpio_info.inputs;
}


int RDGpio::outputs() const
{
  if(!gpio_open) {
    return 0;
  }
  return gpio_info.outputs;
}


unsigned RDGpio::inputMask()
{
  struct gpio_mask mask;
  struct input_event input;
  static unsigned input_mask=0;

  if(!gpio_open) {
    return 0;
  }
  switch(gpio_api) {
      case RDGpio::ApiGpio:
	memset(&mask,0,sizeof(struct gpio_mask));
	ioctl(gpio_fd,GPIO_GET_INPUTS,&mask);
	return mask.mask[0];

      case RDGpio::Input:
	while(read(gpio_fd,&input,sizeof(input))>0) {
	  if((input.type==EV_KEY)&&(gpio_key_map[input.code]>=0)) {
	    if(input.value==0) {
	      input_mask&=~(1<<gpio_key_map[input.code]);
	    }
	    else {
	      input_mask|=(1<<gpio_key_map[input.code]);
	    }
	  }
	}
	return input_mask;
  }
  return 0;
}


bool RDGpio::inputState(int line)
{
  if(!gpio_open) {
    return false;
  }
  if((inputMask()&(1<<line))==0) {
    return false;
  }
  return true;
}


unsigned RDGpio::outputMask() const
{
  struct gpio_mask mask;

  if(!gpio_open) {
    return 0;
  }
  switch(gpio_api) {
      case RDGpio::ApiGpio:
	memset(&mask,0,sizeof(struct gpio_mask));
	ioctl(gpio_fd,GPIO_GET_OUTPUTS,&mask);
	return mask.mask[0];

      case RDGpio::ApiInput:
	return 0;
  }
  return 0;
}


void RDGpio::gpoSet(int line,unsigned interval)
{
  struct gpio_line out;

  if(!gpio_open) {
    return;
  }
  switch(gpio_api) {
      case RDGpio::ApiGpio:
	out.line=line;
	out.state=1;
	ioctl(gpio_fd,GPIO_SET_OUTPUT,&out);
	SetReversion(line,interval);
	break;

      case RDGpio::ApiInput:
	break;
  }
}


void RDGpio::gpoReset(int line,unsigned interval)
{
  struct gpio_line out;

  if(!gpio_open) {
    return;
  }
  switch(gpio_api) {
      case RDGpio::ApiGpio:
	out.line=line;
	out.state=0;
	ioctl(gpio_fd,GPIO_SET_OUTPUT,&out);
	SetReversion(line,interval);
	break;

      case RDGpio::ApiInput:
	break;
  }
}


void RDGpio::inputTimerData()
{
  unsigned input_mask;
  unsigned output_mask;
  unsigned mask;

  if((input_mask=inputMask())!=gpio_input_mask) {
    for(int i=0;i<inputs();i++) {
      mask=1<<i;
      if((gpio_input_mask&mask)!=(input_mask&mask)) {
	if((input_mask&mask)==0) {
	  emit inputChanged(i,false);
	}
	else {
	  emit inputChanged(i,true);
	}
      }
    }
    gpio_input_mask=input_mask;
  }
  if((output_mask=outputMask())!=gpio_output_mask) {
    for(int i=0;i<outputs();i++) {
      mask=1<<i;
      if((gpio_output_mask&mask)!=(output_mask&mask)) {
	if((output_mask&mask)==0) {
	  emit outputChanged(i,false);
	}
	else {
	  emit outputChanged(i,true);
	}
      }
    }
    gpio_output_mask=output_mask;
  }
}
  
  void RDGpio::revertData(int id)
{
  if((outputMask()&(1<<id))==0) {
    gpoSet(id);
  }
  else {
    gpoReset(id);
  }
}


void RDGpio::RemapTimers()
{
  struct gpio_info info;

  //
  // Free Old Timers
  //
  if(gpio_revert_mapper!=NULL) {
    delete gpio_revert_mapper;
    gpio_revert_mapper=NULL;
  }
  for(int i=0;i<gpio_info.outputs;i++) {
    if(gpio_revert_timer[i]!=NULL) {
      delete gpio_revert_timer[i];
      gpio_revert_timer[i]=NULL;
    }
  }

  //
  // Create New Timers
  //
  ioctl(gpio_fd,GPIO_GETINFO,&info);
  gpio_revert_mapper=new QSignalMapper(this,"gpio_revert_mapper");
  connect(gpio_revert_mapper,SIGNAL(mapped(int)),this,SLOT(revertData(int)));
  for(int i=0;i<info.outputs;i++) {
    gpio_revert_timer[i]=new QTimer(this);
    gpio_revert_mapper->setMapping(gpio_revert_timer[i],i);
    connect(gpio_revert_timer[i],SIGNAL(timeout()),
	    gpio_revert_mapper,SLOT(map()));
  }
}


void RDGpio::SetReversion(int line,unsigned interval)
{
  if(interval==0) {
    if(gpio_revert_timer[line]->isActive()) {
      gpio_revert_timer[line]->stop();
    }
    return;
  }
  if(gpio_revert_timer[line]->isActive()) {
    gpio_revert_timer[line]->changeInterval(interval);
  }
  else {
    gpio_revert_timer[line]->start(interval,true);
  }
}


void RDGpio::Clear()
{
  gpio_open=false;
  gpio_description="Unknown Device";
  memset(&gpio_info,0,sizeof(struct gpio_info));
  gpio_input_mask=0;
  gpio_api=RDGpio::ApiGpio;
  for(int i=0;i<KEY_MAX;i++) {
    gpio_key_map[i]=-1;
  }
}


void RDGpio::InitGpio()
{
  gpio_description=gpio_info.name;

  //
  // Enable Input Filters
  //
  if((gpio_info.caps&GPIO_CAP_FILTER)!=0) {
    gpio_mask mask;
    memset(&mask,0xFF,sizeof(struct gpio_mask));
    ioctl(gpio_fd,GPIO_SET_FILTERS,&mask);
  }
}


void RDGpio::InitInput()
{
  unsigned char bitmask[EV_MAX/8+1];
  unsigned char keymask[KEY_MAX/8+1];
  int i=0;
  char desc[256];

  memset(&gpio_info,0,sizeof(struct gpio_info));

  if(ioctl(gpio_fd,EVIOCGNAME(256),desc)>=0) {
    gpio_description=desc;
  }
  if(ioctl(gpio_fd,EVIOCGBIT(0,EV_MAX),bitmask)<0) {
    return;
  }
  if((bitmask[0]&(1<<EV_KEY))==0) {
    return;
  }
  ioctl(gpio_fd,EVIOCGBIT(EV_KEY,KEY_MAX),keymask);
  while((i<KEY_MAX)&&(gpio_info.inputs<GPIO_MAX_LINES)) {
    if((keymask[i/8]&(1<<(i%8)))!=0) {
      gpio_key_map[i]=gpio_info.inputs++;
    }
    i++;
  }
  gpio_info.mode=RDGpio::Auto;
  gpio_info.outputs=0;
}

