// rdkernelgpio.cpp
//
// Control Class for the Linux SysFS GPIO Interface
//
//   (C) Copyright 2017 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 "rdkernelgpio.h"

RDKernelGpio::RDKernelGpio(QObject *parent)
  : QObject(parent)
{
  gpio_poll_timer=new QTimer(this);
  connect(gpio_poll_timer,SIGNAL(timeout()),this,SLOT(pollData()));
}


RDKernelGpio::~RDKernelGpio()
{
  for(unsigned i=0;i<gpio_gpios.size();i++) {
    removeGpio(gpio_gpios[i]);
  }
  delete gpio_poll_timer;
}


bool RDKernelGpio::addGpio(int gpio)
{
  FILE *f=NULL;

  for(unsigned i=0;i<gpio_gpios.size();i++) {
    if(gpio==gpio_gpios[i]) {
      return true;
    }
  }

  if((f=OpenNode("export","w"))==NULL) {
    return false;
  }
  fprintf(f,"%u",gpio);
  fclose(f);
  gpio_gpios.push_back(gpio);
  gpio_states.push_back(value(gpio));

  if(gpio_gpios.size()==1) {
    gpio_poll_timer->start(KERNELGPIO_POLL_INTERVAL);
  }

  return true;
}


bool RDKernelGpio::removeGpio(int gpio)
{
  FILE *f=NULL;

  if((f=OpenNode("unexport","w"))==NULL) {
    return false;
  }
  fprintf(f,"%u",gpio);
  fclose(f);
  
  return true;
}


RDKernelGpio::Direction RDKernelGpio::direction(int gpio,bool *ok) const
{
  RDKernelGpio::Direction ret=RDKernelGpio::In;
  FILE *f=NULL;
  char str[255];

  if((f=OpenNode("direction","r",gpio))!=NULL) {
    fscanf(f,"%s",str);
    if(QString(str)=="out") {
      ret=RDKernelGpio::Out;
    }
    fclose(f);
    if(ok!=NULL) {
      *ok=true;
    }
  }
  else {
    if(ok!=NULL) {
      *ok=false;
    }
  }    

  return ret;
}


bool RDKernelGpio::setDirection(int gpio,RDKernelGpio::Direction dir) const
{
  FILE *f=NULL;

  if((f=OpenNode("direction","w",gpio))!=NULL) {
    switch(dir) {
    case RDKernelGpio::In:
      fprintf(f,"in");
      break;

    case RDKernelGpio::Out:
      fprintf(f,"out");
      break;
    }
    fclose(f);
    return true;
  }
  return false;
}


bool RDKernelGpio::activeLow(int gpio,bool *ok) const
{
  unsigned ret=false;
  FILE *f=NULL;

  if((f=OpenNode("active_low","r",gpio))!=NULL) {
    fscanf(f,"%u",&ret);
    fclose(f);
    if(ok!=NULL) {
      *ok=true;
    }
  }
  else {
    if(ok!=NULL) {
      *ok=false;
    }
  }    

  return (bool)ret;
}


bool RDKernelGpio::setActiveLow(int gpio,bool state) const
{
  FILE *f=NULL;

  if((f=OpenNode("active_low","w",gpio))!=NULL) {
    fprintf(f,"%u",state);
    fclose(f);
    return true;
  }
  return false;
}


bool RDKernelGpio::value(int gpio,bool *ok) const
{
  unsigned ret=false;
  FILE *f=NULL;

  if((f=OpenNode("value","r",gpio))!=NULL) {
    fscanf(f,"%u",&ret);
    fclose(f);
    if(ok!=NULL) {
      *ok=true;
    }
  }
  else {
    if(ok!=NULL) {
      *ok=false;
    }
  }    

  return (bool)ret;
}


bool RDKernelGpio::setValue(int gpio,bool state) const
{
  FILE *f=NULL;

  if((f=OpenNode("value","w",gpio))!=NULL) {
    fprintf(f,"%u",state);
    fclose(f);
    return true;
  }
  return false;
}


void RDKernelGpio::pollData()
{
  bool state=false;

  for(unsigned i=0;i<gpio_gpios.size();i++) {
    if((state=value(gpio_gpios[i]))!=gpio_states[i]) {
      gpio_states[i]=state;
      emit valueChanged(gpio_gpios[i],state);
    }
  }
}


FILE *RDKernelGpio::OpenNode(const QString &name,const char *mode,int gpio) const
{
  if(gpio<0) {
    return fopen((KERNELGPIO_SYS_FILE+"/"+name),mode);
  }
  return fopen((KERNELGPIO_SYS_FILE+QString().sprintf("/gpio%d/",gpio)+name),
	       mode);
}