#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
 
#include "nbus-spi.h"

#define DEBUG 0

int gotHUP;

#ifndef MAX
#define MAX(x,y)  ((x)>(y)?x:y)
#endif


#define LCD_WIDTH    128
#define LCD_HEIGHT   64


#define DATA_LUN  1
#define CMD_LUN   0

static int checkHelperIsRunning(void);

static unsigned char data[8][LCD_WIDTH];

static unsigned int xRes, yRes, bitsPerPixel, stride;
static volatile unsigned char*frameBuffer;
static unsigned char*previousFrameBuffer;
static unsigned long bufferSize;

static unsigned char initString[] = {   
   0xe2,    /* reset */
   0xa2,    /* LCD Bias */
   0xa0,    /* ADC Select = normal */
   0xc8,    /* Common mode = reverse (ie, top of display is row 0) */
   0xa6,    /* display normal (ie, pixel on = black) */
   0x40,    /* Start line = 0 */
   0x24, 
   0x81,    /* Electronic volume set */
   0x19, 
   0x2c, 
   0x2e, 
   0x2f,
   0xa4,    /* display all points = OFF (ie, normal) */
   0xaf,     /* display on */
};

static inline void setPixel(unsigned int x, unsigned int y) {
   
   if (x < LCD_WIDTH && y < LCD_HEIGHT)
      data[y/8][x] |= (1 << (y % 8));               
}
 
static inline void clearPixel(unsigned int x, unsigned int y) {
   
   if (x < LCD_WIDTH && y < LCD_HEIGHT)
      data[y/8][x] &= ~(1 << (y % 8));
}


static void displayUpdateFunction(void) {

#if (DEBUG)
   int n, j;
#endif
   int i, x, y;
   unsigned char c;
   
   memset((void*)frameBuffer, 0, bufferSize);
      
   *previousFrameBuffer = 1;  // to force initial lcd update 
   
   nbus_spi_init();   
   
   nbus_spi_lun(DATA_LUN);
   nbus_spi_speed(37500000, 1);  
   
   nbus_spi_lun(CMD_LUN); 
   nbus_spi_speed(37500000, 1); /* do we even need this second call? */
      
   nbus_spi_write(sizeof(initString), initString, 1);
   
   while(! gotHUP) {
      usleep(50000);    // approx 20Hz refresh
            
      if (! memcmp((void*)frameBuffer, previousFrameBuffer, bufferSize))
         continue;
         
      memcpy(previousFrameBuffer, (void*)frameBuffer, bufferSize);      
                  
      switch(bitsPerPixel) {
      case 1:                  
         {  
            int bit;
            volatile unsigned char *ucp;
            for(y=0; y < yRes; y++) {
               for(x=0; x < xRes; ) {
                  unsigned long offset = (y * stride) + x/8;
                  ucp = &frameBuffer[offset];
                  for(bit=1; bit < 0x100; bit<<=1, x++) {
                     if (x >= xRes) break;
                     
                     if (*ucp & bit)
                        setPixel(x, y);
                     else
                        clearPixel(x,y);                        
                  }
               }
            }
         }         
         break;
         
      default:
         printf("%d bits-per-pixel not handled yet!\n", bitsPerPixel);
         break;
      }
                                                         
      for(i=0; i < 8; i++) {         
         nbus_spi_lun(CMD_LUN);            
         c = i | 0xb0;   // set page                  
         nbus_spi_write(1, &c, 0);         
         c = 0;         // set column to zero                  
         nbus_spi_write(1, &c, 0);         
         c = 0x10;
         nbus_spi_write(1, &c, 1);         
         nbus_spi_lun(DATA_LUN);
         nbus_spi_write(LCD_WIDTH, data[i], 1);
      }
   }
}


int main(void) {
   char name[16];
   int fb;
   DIR *dir;
   struct dirent *ent;
   struct fb_var_screeninfo screen_info;
   struct fb_fix_screeninfo fixed_info;


   if (checkHelperIsRunning()) {
      fprintf(stderr, "lcd-helper is running already\n");
      return 1;
   }
   
   if((dir = opendir("/sys/class/graphics")) == NULL) {
      fprintf(stderr, "ERROR: Cannot access /sys/class/graphics\n");
      return 1;
   }

   while((ent = readdir(dir)) != NULL) {
      snprintf(name, 16, "/dev/%s", ent->d_name);
      fb = open(name, O_RDWR);
      ioctl(fb, FBIOGET_FSCREENINFO, &fixed_info);      
      ioctl(fb, FBIOGET_VSCREENINFO, &screen_info);

      xRes = screen_info.xres;
      yRes = screen_info.yres;

      if(xRes == 128 && yRes == 64) break;
   }
        

   if(fb > 0 && ent != NULL) {
      
      bitsPerPixel = screen_info.bits_per_pixel;
      stride = fixed_info.line_length;
      bufferSize = screen_info.yres_virtual *  stride;
      
#if (DEBUG)      
      printf("Display: %dx%dx%d stride: %d bufferSize: %d bytes\n", 
         xRes, yRes, bitsPerPixel, stride, bufferSize);            
#endif

      if ((previousFrameBuffer = malloc(bufferSize)) == NULL) {   
         fprintf(stderr, "Memory allocation error\n");
         close(fb);
         return 1;
      }
      
      frameBuffer = mmap(NULL, MAX(getpagesize(), bufferSize), 
         PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fb, 0);
      if (frameBuffer == MAP_FAILED) {
         printf("MAP_FAILED\n");
         close(fb);
         exit(1);
      }
      
      
      if (daemon(0,0)) {
         fprintf(stderr, "can't daemonize!\n");
         close(fb);
         return 1;
      } 
      
      displayUpdateFunction();
      close(fb);      
      
   } else {
      fprintf(stderr, "ERROR: Can't open fb device\n");
      return 1;
   }

   return 0;
}

static int checkHelperIsRunning(void) {
	int ret;
	struct dirent entry, *ep;
	DIR *dp;
	int retVal = 0;
	
	if ((dp = opendir("/proc"))) {
		for (ret = readdir_r(dp, &entry, &ep); retVal == 0 && ret == 0 && ep
				!= NULL; ret = readdir_r(dp, &entry, &ep)) {
			if (ep->d_type == DT_DIR) {
				int pidDir = 1;
				char *cp = ep->d_name;

				while (*cp) {
					if (!isdigit(*cp)) {
						pidDir = 0;
						break;
					}
					cp++;
				}
				if (pidDir) {
					char buf[80];
					FILE *f;
					sprintf(buf, "/proc/%s/cmdline", ep->d_name);
					if ((f = fopen(buf, "r"))) {
						if (fgets(buf, sizeof(buf), f)) {
							if (strstr(buf, "lcd-helper")) {
							   if (atoi(ep->d_name) != getpid())
								retVal = 1;
							}
						}
						fclose(f);
					}
				}
			}
		}
		closedir(dp);
	}

	return retVal;
}

