/*
; ulcd.c
; Version 0.1
; Simple code for driving 4D Systems serial uLCD
;
; Copyright (C) 2006 Denis Bodor <lefinnois@lefinnois.net>
;
; 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 <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>

#include <gd.h>

#define BAUDRATE B115200
#define MODEMDEVICE "/dev/ttyS0"
//#define _POSIX_SOURCE 1 /* code source conforme à POSIX */
#define FALSE 0
#define TRUE 1

#define SENDLCD(fd,str) sendlcd(fd, str, (sizeof(str)/sizeof(*str)))

unsigned char fond[3] = { 0x42, 0x00, 0x00 };
unsigned char fondbleu[3] = { 0x42, 0x00, 0xFF };
unsigned char fondblanc[3] = { 0x42, 0xFF, 0xFF };

unsigned char putpix[5] = { 0x50, 0x00, 0x00, 0x00, 0x00 };
unsigned char lcdbacklight[3] = { 0x59, 0x00, 0x00 };
unsigned char lcdcontrast[3] = {0x59, 0x02, 0x17};
unsigned char lcdautobaud[1] = { 0x55 };
unsigned char lcderase[1] = { 0x45 };
unsigned char lcddisppower[3] = { 0x59, 0x01, 0x00 };
unsigned char lcdpower[3] = { 0x59, 0x03, 0x00 };

int verbose=0;

void goout(int fd) {
  fprintf(stderr, "Close by goout\n");
  close(fd);
  exit(EXIT_FAILURE);
}

unsigned char sendlcd(int fd, unsigned char *data, int taille) 
{
  int res=0;
  unsigned char buf[255];

  if(write( fd, data, taille ) != taille){
    fprintf(stderr,"Write Error : %s (%d)\n",
        strerror(errno),errno);
    return(0xFF);
  }

  buf[0]=0x00;
//  res = read(fd,buf,255);
  if((res = read(fd,buf,255)) < 0) {
//  if(res < 0) {
    fprintf(stderr,"Read Error : %s (%d)\n",
        strerror(errno),errno);
    return(0xFF);
  }

  if(res == 0 ) {
    fprintf(stderr, "sendlcd - Rien lu\n");
    return(0xFF);
  }

  return(buf[0]);
}

int showusage(char *binname)
{
  printf("Send image or commands to 4D system uLCD\n");
  printf("Usage : %s [OPTIONS]\n", binname);
  printf(" -i filename       	PNG image to display\n");
  printf(" -x 			center image on LCD screen (must be used AFTER -i filename to work)\n");
  printf(" -m 			minimize number of pixel writes by using background color\n");
  printf(" -c value       	set contrast from 0 to 40\n");
  printf(" -b value       	set backlight from 0 (off) to 2\n");
  printf(" -e 			erase LCD screen\n");
  printf(" -d value		display power off (<1) or power on (>0) (must be used before disconnect)\n");
  printf(" -p value		uLCD power off (<1) or power on (>0)\n");
  printf(" -a 			Ask uLCD to autodetect serial speed (called before anything else when used)\n");
  printf(" -v 			be verbose\n");
  printf(" -h 			show this help\n");
  return(0);
}

int main(int argc, char **argv)
{
  /* pour TTYS */
  int fd;
  struct termios oldtio,newtio;

  /* pour getops */
  int retopt;
  int opt=0;

  /* pour gd/PNG */
  FILE *in;
  gdImagePtr im=NULL;
  // index couleut
  long int c;
  // hauteur et largeur image 
  int imgh, imgl;
  int h, l;
  // couleur rgb image
  int r,g,b;
  // couleur rgb LCD
  int mr,mg,mb;
  // décalage centrage
  int xstart=0;
  int ystart=0;

  /* pour interne */
  // retour sendlcd
  unsigned char res;
  // stat pixels saute
  int skiped=0;
  // nbr action LCD
  int act=0;
  char *endptr; // pour strtol

  /* pour couleurs */
  unsigned char b1, b2;
  int couleurs[256][256];
  int max=0;
  unsigned char fond1 = 0x00;
  unsigned char fond2 = 0x00;
  int cx,cy;

  int erase=0;
  int autobaud=0;
  int minimize=0;
  long backlight=0;
  long contrast=0;
  long power=0;
  long disp=0;

  while ((retopt = getopt(argc, argv, "mavd:p:hc:eb:i:x")) != -1) {
    switch (retopt) {
      case 'v' :
        verbose=1;
        opt++;
        break;
      case 'e' :
        erase++;
        opt++;
        act++;
        break;
      case 'a' :
        autobaud++;
        opt++;
        act++;
        break;
      case 'd' :
        disp = strtol(optarg, &endptr, 10);
        if ((optarg!=0) && (*endptr == 0)) {
          disp++;
          if(disp == 1) {
            lcddisppower[2] = 0x00;
          }
          if(disp == 2) {
            lcddisppower[2] = 0x01;
          }
          if(disp < 1 || disp > 2) {
            fprintf(stderr,"Bad display power value. Do nothing\n");
            disp=0;
          }
        }
        opt++;
        act++;
        break;
      case 'p' :
        power = strtol(optarg, &endptr, 10);
        if ((optarg!=0) && (*endptr == 0)) {
          power++;
          if(power == 1) {
            lcdpower[2] = 0x00;
          }
          if(power == 2) {
            lcdpower[2] = 0x01;
          }
          if(power < 1 || power > 2) {
            fprintf(stderr,"Bad power value. Do nothing\n");
            power=0;
          }
        }
        opt++;
        act++;
        break;
      case 'c' :
        contrast = strtol(optarg, &endptr, 10);
        if ((optarg!=0) && (*endptr == 0)) {
          contrast++;
          if(contrast < 1) {
            fprintf(stderr,"contrast value too low, using 0\n");
            contrast=0+1;
          }
          if(contrast > 40) {
            fprintf(stderr,"contrast value too high, using 40\n");
            contrast=40+1;
          }
          lcdcontrast[2]=contrast-1;
        } else {
          fprintf(stderr,"Invalide or missing argument for contrast value\n");
          contrast=0;
        }
        opt++;
        act++;
        break;
      case 'b' :
        backlight = strtol(optarg, &endptr, 10);
        if ((optarg!=0) && (*endptr == 0)) {
          backlight++;
          if(backlight < 1) {
            fprintf(stderr,"backlight value too low, using 0\n");
            backlight=0+1;
          }
          if(backlight > 3) {
            fprintf(stderr,"backlight value too high, using 2\n");
            backlight=2+1;
          }
          lcdbacklight[2]=backlight-1;
        } else {
          fprintf(stderr,"Invalide or missing argument for backlight value\n");
          backlight=0;
        }
        opt++;
        act++;
        break;
      case 'i' :
        if((in = fopen(optarg, "rb")) == 0) {
          fprintf(stderr,"Open img Error : %s (%d)\n",
              strerror(errno),errno);
          exit(EXIT_FAILURE);
        }
        /* in case of many -i option */
        if(im) {
          gdImageDestroy(im);
          im=NULL;
          fprintf(stderr,"Warning : multiple -i detected. Centering may not work as expected\n");
        }
        if((im = gdImageCreateFromPng(in)) != NULL) {
          fclose(in);
          imgh = gdImageSY(im);
          imgl = gdImageSX(im);
          if(imgh > 128 || imgl > 128) {
            fprintf(stderr,"Image must be <= 128x128\n");
            return(EXIT_FAILURE);
          }
        } else {
          fclose(in);
          fprintf(stderr,"Unable to load PNG image\n");
          return(EXIT_FAILURE);
        }
        if(imgh != 128 || imgl != 128) {
          fprintf(stderr,"Warning : Image smaller (%dx%d) than screen\n",
              imgl, imgh);
        }
        opt++;
        act++;
        break;
      case 'x' :
        if(im) {
          if(imgh != 128 || imgl != 128) {
            ystart=(128-imgh)/2;
            xstart=(128-imgl)/2;
            fprintf(stderr,"Centering small (%dx%d) image (orig %d:%d)\n",
                imgl, imgh, xstart, ystart);
          } else {
            fprintf(stderr,"Image is %dx%d, centering useless\n",
                imgl, imgh);
          }
        } else {
          fprintf(stderr,"No image to center\n");
        }
        opt++;
        break;
      case 'm' :
        if(im) {
          minimize=1;
        } else {
          fprintf(stderr,"No image to use for minimizing pixel writes\n");
        }
        opt++;
        break;
      case 'h' :
        showusage(argv[0]);
        exit(EXIT_SUCCESS);
      default :
        showusage(argv[0]);
        exit(EXIT_FAILURE);                      
    }
  }

  if (!opt) {
    showusage(argv[0]);
    exit(EXIT_FAILURE);
  }

  if (!act) {
    fprintf(stderr,"Nothing to do with LCD\n");
    exit(EXIT_FAILURE);
  }

  if ((fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY )) < 0) {
    fprintf(stderr,"Open Error : %s (%d)\n",
        strerror(errno),errno);
    exit(EXIT_FAILURE);
  } else {
    if(verbose) printf("info: Open device\n");
  }

  /* sauvegarde de la configuration courante */
  if(tcgetattr(fd,&oldtio) != 0) {
    fprintf(stderr,"tcgetattr Error : %s (%d)\n",
        strerror(errno),errno);
    goout(fd);
  }

  /* raz newtio */
//bzero(&newtio, sizeof(newtio));

  memset(&newtio, 0x00, sizeof(newtio));

  /* configuration */
  /* mode RAW, pas de mode canonique, pas d'écho */
  newtio.c_lflag = 0;
  newtio.c_oflag = 0;
  /* ignorer les signaux BREAK en entrée */
  newtio.c_iflag = IGNBRK;
  /* vitesse */
  newtio.c_cflag |= BAUDRATE;
  /* Pas de controle de flux hardware (RTS/CTS) ou soft */
  newtio.c_cflag &= ~CRTSCTS;
  newtio.c_iflag &= ~(IXON | IXOFF | IXANY);
  /* Valider la réception */
  newtio.c_cflag |=  CREAD;
  /* 8 bits de données, pas de parité */
  newtio.c_cflag &= ~(PARENB | CSIZE);
  newtio.c_cflag |= CS8;
  /* Ignorer les signaux de contrôle du modem */
  newtio.c_cflag |= CLOCAL; 
  /* pas d'echo */
  newtio.c_cflag &= ~ECHO;

  /* Si MIN = 0 et TIME > 0, TIME est utilisé comme une valeur de timeout.
   * Une lecture est satisfaite lorsqu'un caractère est reçu, 
   * ou que la durée TIME est dépassée (t = TIME * 0.1s). 
   * Si TIME est dépassé, aucun caractère n'est retourné. */
  /*
   http://www.docmirror.net/fr/linux/howto/programming/Serial-Programming-HOWTO/Serial-Programming-HOWTO-3.html
   */
  newtio.c_cc[VTIME]    = 50;
  newtio.c_cc[VMIN]     = 0;
 
  /* efface a la fois les données reçues non lues, 
   * et les données ecrites non transmises */
  if(tcflush(fd, TCIOFLUSH) != 0) {
    fprintf(stderr,"tcflush Error : %s (%d)\n",
        strerror(errno),errno);
    goout(fd);
  }
 
  /* def config */
  if (tcsetattr(fd,TCSANOW,&newtio) != 0) {
    fprintf(stderr,"tcsetattr Error : %s (%d)\n",
        strerror(errno),errno);
    goout(fd);
  }

  if(power) {
    if(verbose) {
      printf("info: Power to 0x%.02X", lcddisppower[2]);
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcdpower)) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }

  if(autobaud) {
    if(verbose) {
      printf("info: autobaud");
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcdautobaud )) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }


  if(erase) {
    if(verbose) {
      printf("info: erase");
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcderase )) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }

  if(contrast) {
    if(verbose) {
      printf("info: contrast to 0x%.02X", lcdcontrast[2]);
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcdcontrast )) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }

  if(backlight) {
    if(verbose) {
      printf("info: backlight to 0x%.02X", lcdbacklight[2]);
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcdbacklight )) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }

  if(disp) {
    if(verbose) {
      printf("info: Display power to 0x%.02X", lcddisppower[2]);
      fflush(stdout);
    }
    if((res=SENDLCD(fd, lcddisppower )) != 0x06) {
      fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
    } else {
      if(verbose) printf("...ok\n");
    }
  }


  if(im) {
    if(minimize) {
      if(verbose) {
        printf("info: Analysing image. ");
        fflush(stdout);
      }
      /* init color table */
      for(cy=0;cy<256;cy++) {
        for(cx=0;cx<256;cx++) {
          couleurs[cx][cy]=0;
        }
      }
      /* parse image */
      for(h=0; h<imgh; h++) {
        for(l=0; l<imgl; l++) {
          c = gdImageGetPixel(im, l, h);
          r = gdImageRed(im,c);
          g = gdImageGreen(im,c);
          b = gdImageBlue(im,c);
          mr = r*32/256; /* reduction 8b -> 5b */
          mg = g*64/256; /* reduction 8b -> 6b */
          mb = b*32/256; /* reduction 8b -> 5b */
          b1 = (mr << 3) | ((mg >> 3) & 7); /* byte 1 */
          b2 = ((mg << 5) & 224) | mb;      /* byte 2 */
          couleurs[b1][b2]++; /* increment color vector */
        }
      }
      /* what is the biggest value in color table and where ? */
      for(cy=0;cy<256;cy++) {
        for(cx=0;cx<256;cx++) {
          if(couleurs[cx][cy]>max) {
            fond1 = cx;
            fond2 = cy;
            max=couleurs[cx][cy];
          }
        }
      }
      if(verbose) {
        printf("Winner is 0x%.02X 0x%.02X (%d) occurence(s). Set background", fond1, fond2, max);
        fflush(stdout);
      }
      fond[1]=fond1;
      fond[2]=fond2;
      if((res=SENDLCD(fd, fond )) != 0x06) {
        fprintf(stderr,"...no ACK ! (0x%.02X)\n",res);
      } else {
        if(verbose) printf("...ok\n");
      }
    }
    /* parse image for display */
    for(h=0; h<imgh; h++) {
      for(l=0; l<imgl; l++) {
        c = gdImageGetPixel(im, l, h);
        r = gdImageRed(im,c);
        g = gdImageGreen(im,c);
        b = gdImageBlue(im,c);
        mr = r*32/256; /* reduction 8b -> 5b */
        mg = g*64/256; /* reduction 8b -> 6b */
        mb = b*32/256; /* reduction 8b -> 5b */
        b1 = (mr << 3) | ((mg >> 3) & 7); /* byte 1 */
        b2 = ((mg << 5) & 224) | mb;      /* byte 2 */

        /* this pixel have the same colors than background ? */
        if(b1==fond1 && b2==fond2 && minimize) {
          skiped++;
        } else {
          putpix[1]= l+xstart;
          putpix[2]= h+ystart;
          putpix[3]= b1;
          putpix[4]= b2;
          SENDLCD(fd, putpix);
        }
      }
    }
    gdImageDestroy(im);
  }

  /* def old config */
  if (tcsetattr(fd,TCSANOW,&oldtio) != 0) {
    fprintf(stderr,"tcsetattr Error : %s (%d)\n",
        strerror(errno),errno);
    goout(fd);
  }

  close(fd);
  if(verbose) {
    printf("info: Close device\n");
    if(skiped == 16384) {
      printf("info: All pixels skiped. That's ok ?\n", skiped);
    } else {
      printf("info: %d pixel skiped\n", skiped);
    }
  }

  return(EXIT_SUCCESS);
}

