/*
 * Copyright (C) 2011 Florian Streibelt <florian@freitagsrunde.org>
 * Licensed under GPL v3 or later
 *
 */

#include <stdio.h>		/* Standard input/output definitions */
#include <string.h>		/* String function definitions */
#include <unistd.h>		/* UNIX standard function definitions */
#include <fcntl.h>		/* File control definitions */
#include <errno.h>		/* Error number definitions */
#include <termios.h>		/* POSIX terminal control definitions */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <pthread.h>		/* Posix 1003.1c threads */
#include <signal.h>
#include <unistd.h>


int fd;
int display_dirty=0;
int screensaver;
int brightness = 4;
int offset = 0;
char *fifo;
char *pausemsg = NULL;

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_t thread1;

int
initport (int fd)
{
  struct termios options;
  // Get the current options for the port...
  tcgetattr (fd, &options);
  // Set the baud rates to 9600...
  cfsetispeed (&options, B9600);
  cfsetospeed (&options, B9600);
  // Enable the receiver and set local mode...
  options.c_cflag |= (CLOCAL | CREAD);

  options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;

  // Set the new options for the port...
  tcsetattr (fd, TCSANOW, &options);
  return 1;
}

//reset the display to defaults
char display_init[] = { 0x1b, 0x40 };

//show or hide the cursor
char display_cursor_off[] = { 0x1f, 0x43, 0x0 };
char display_cursor_on[] = { 0x1f, 0x43, 0x1 };

// blink-command: 
// where the last value defines the blink rate, 
// with 0=always on, 255=always off
char display_on[] = { 0x1f, 0x45, 0x0 };
char display_off[] = { 0x1f, 0x45, 0xff };

//Modes for new chars:
char display_overwrite[] = { 0x1f, 0x01 };
char display_vscroll[] = { 0x1f, 0x02 };
char display_hscroll[] = { 0x1f, 0x03 };

//clear and cursor home
char display_clear_screen[] = { 0x0c };

// activate timer display in lower right corner.
// actual time has to be set via display_set_timer.
char display_time[] = { 0x1f, 0x55 };

// configures hour and minutes of the internal clock and
// displays the time in the lower right corner.
// turns off when something gets written into the line.
// two bytes have to follow with hour and minute
char display_set_timer[] = { 0x1F, 0x54 };

// various commands for the display:

// send a command sequence from the heap(!) {sizeof used}
// only to be used within the macros presented below!
#define DWC(c)               display_write(c,sizeof(c))

//send a null terminated string:
#define SEND_TEXT(t)         display_write(t,strlen(t))

//various commands
#define SEND_DISPLAY_INIT    DWC(display_init)
#define SEND_CURSOR_OFF      DWC(display_cursor_off)
#define SEND_CURSOR_ON       DWC(display_cursor_on)
#define SEND_DEFINE_TIMER    DWC(display_set_timer)
#define SEND_SHOW_TIMER      DWC(display_time)
#define SEND_MODE_OVERWRITE  DWC(display_overwrite)
#define SEND_MODE_VSCROLL    DWC(display_vscroll)
#define SEND_MODE_HSCROLL    DWC(display_hscroll)
#define SEND_DISPLAY_OFF     DWC(display_off)
#define SEND_DISPLAY_ON      DWC(display_on)
#define SEND_CLEAR           DWC(display_clear_screen);

// internal defines.
#define TIME_BASE  	     200000
#define TB_TO_SECS(x)        (x*1000000/TIME_BASE)

#define TIME_CLOCK	     TB_TO_SECS(65)
#define TIME_DIM 	     TB_TO_SECS(90)
#define TIME_OFF 	     TB_TO_SECS(520)

#define DEVICE               "/dev/ttyUSB0"


// chars per line in our display:   
#define LINELEN 20


void
display_write (const void *buf, size_t count)
{
  pthread_mutex_lock (&mutex1);
  write (fd, buf, count);
  pthread_mutex_unlock (&mutex1);

}


void
display_putstring (char *s)
{
  screensaver = 0;
  SEND_TEXT (s);
}


void
showtime (void)
{

  SEND_CURSOR_OFF;

  char c[5] = { 0x1F, 0x54, 0x00, 0x00 };
  time_t timer;			// Define the timer
  struct tm *tblock;		// Define a structure for time block

  timer = time (NULL);
  tblock = localtime (&timer);
  c[2] = tblock->tm_hour;
  c[3] = tblock->tm_min;

  display_write (&c, 4);

  SEND_MODE_HSCROLL;
  char *msg = "Freitagsrunde 4!";
  display_write (msg, strlen (msg));
}

void 
send_display_init(){
	  SEND_DISPLAY_INIT;
	  SEND_CURSOR_OFF;
	  SEND_DISPLAY_ON;
	  SEND_MODE_OVERWRITE;
}

void
display_dim (short i)
{
  //     return;
  if (i == brightness)
    {

      //printf ("display_dim: would not change brightness. abort.\n");

      return;
    }
  if (i > 0 && i < 5)
    {
      int l = 0;
      char *d;
      if (brightness == 0)
	{
	 send_display_init();
	  char c[4] = { 0x1f, 0x58, 0x04, 0x00 };
	  l = 6;
	  //c[5]=i;
	  d = c;
	}
      else
	{
	  //set brightness directly:
	  char c[4] = { 0x1f, 0x58, 0x01, 0x00 };
	  l = 3;
	  c[2] = i;
	  d = c;
	  //printf ("command mode: %x \n", c[2]);
	}
      display_write (d, l);
      brightness = i;
    }
  else if (i == 0)
    {
      //turn off
      SEND_DISPLAY_OFF;
      brightness = i;
    }
}

void *
rthread (void *args)
{

  int pipe;
  char pc = ' ';
  char c = ' ';
  while (1)
    {
      pipe = open ("/tmp/display", O_RDONLY);
      int r;
      while ((r = read (pipe, &c, 1)) > 0)
	{

	  screensaver = 0;
          if (display_dirty) {
		printf("DEBUG: resetting display\n");
	 	send_display_init();
		display_dirty=0;
	  }
	  display_dim (4);

	  if (pc == '\n')
	    {
	      display_write (&pc, 1);
	    }
	  if (c != '\n')
	    {
	      display_write (&c, 1);
	    }
	  pc = c;
	}
      close (pipe);
    }
}

void
sighandler (int sig)
{
  pthread_kill (thread1, 0);
  send_display_init();
  SEND_DISPLAY_OFF;

  close (fd);
  if (fifo)
    {
      unlink (fifo);
    }
  exit (0);
  //signal(sig, sighandler);
}

void scrolltext(){

	if (pausemsg){
	  char *message = calloc (LINELEN + 1, sizeof (char));

	int spaces=0;

	  if (offset<LINELEN){
            spaces = LINELEN-offset; //anzahl spaces im ersten durchgang
            memset(message , ' ', spaces);
          }

	  strncpy (message+spaces,  pausemsg + offset - LINELEN, LINELEN-spaces);

	  offset=(offset + 1) % (strlen(pausemsg)+LINELEN);
	  if (offset==0){
	   offset=LINELEN;
	  }

	  int l = strlen (message);
	  if (l < LINELEN)
	    {
	      message = strncat (message, pausemsg, LINELEN - l);
	    }

	  SEND_TEXT ("\x0b");
	  SEND_TEXT (message);
	  free (message);
	}
}

int
main (int argc, char **argv)
{
  char *devnode;

  //TODO: arg parser!
  fifo = strdup ("/tmp/display");
  pausemsg = NULL ; //strdup("Bitte scanne Deine ID-Karte um Dich anzumelden *** "); 

 if (argc != 2)
    {
      devnode = strdup (DEVICE);
      printf ("no device specified, using default %s\n", devnode);
    }
  else
    {
      devnode = strdup (argv[1]);
    }
  fd = open (devnode, O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd == -1)
    {
      perror (devnode);
      return 1;
    }
  else
    {
      fcntl (fd, F_SETFL, 0);
    }

  initport (fd);

  //init display, set, vertical scroll, cursor off
  send_display_init();

  pthread_create (&thread1, NULL, rthread, NULL);
  signal (SIGINT, sighandler);

  if (mkfifo (fifo, S_IRWXU | S_IRWXG | S_IRWXO))
    {
      perror (fifo);
      if (errno != EEXIST && errno != ESPIPE)
	{
	  return (1);
	}
    }


  //showtime();
  display_putstring ("Display initialized  ");
  display_putstring (fifo);
  sleep (3);

  screensaver = 0;
  offset = 0; // offset for scrolltext in screensaver

  while (1)
    {
      usleep (TIME_BASE);

      if (0 == (screensaver) % TB_TO_SECS (1))
	{
	  //one-second event comes HERE:
	  printf
	    ("debug: screensaver: %d time until clock: %d time until dim: %d time until off: %d\n",
	     screensaver, TIME_CLOCK - screensaver, TIME_DIM - screensaver,
	     TIME_OFF - screensaver);
	}

      //printf ("screensaver = %05d brightness: %d\n", screensaver, brightness);
      if (screensaver <  (TIME_CLOCK + TIME_OFF + TIME_DIM + 10)){
	screensaver++;
      }

      if (screensaver == TIME_CLOCK)
	{
	  display_dirty=1;
	  showtime ();
	}
      if (screensaver == TIME_OFF)
	{
	  display_dirty=1;
	  display_dim (0);
	}


      //show a scroll text while the screensaver is active...
      if ((screensaver > (TIME_CLOCK + 10)) && (screensaver < TIME_OFF))
	{
	        display_dirty=1;
		scrolltext();
	}

      //after some more seconds of inactivity dim display and finally turn off
      if ((screensaver > TIME_DIM) && (brightness > 1))
	{
	  //printf ("dimming\n");
	  display_dim (brightness - 1);
	}
    }

  //you'll never get here 
  close (fd);
  return 0;
}
