// this script was modified from some code from http://adafruit.com somewhere.
// Paint with a spashscreen
// 2012 James Kasper http://jk.cbwp.net!
#include "TFTLCD.h"
#include "TouchScreen.h"
#include <SD.h>

#define YP A3  // must be an analog pin, use "An" notation!
#define XM A2  // must be an analog pin, use "An" notation!
#define YM 9   // can be a digital pin
#define XP 8   // can be a digital pin

#define TS_MINX 150
#define TS_MINY 120
#define TS_MAXX 920
#define TS_MAXY 940

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

// The control pins can connect to any pins but we'll use the 
// analog lines since that means we can double up the pins
// with the touch screen (see the TFT paint example)
#define LCD_CS A3    // Chip Select goes to Analog 3
#define LCD_CD A2    // Command/Data goes to Analog 2
#define LCD_WR A1    // LCD Write goes to Analog 1
#define LCD_RD A0    // LCD Read goes to Analog 0
/* For the 8 data pins:
Duemilanove/Diecimila/UNO/etc ('168 and '328 chips) microcontoller:
D0 connects to digital 8
D1 connects to digital 9
D2 connects to digital 2
D3 connects to digital 3
D4 connects to digital 4
D5 connects to digital 5
D6 connects to digital 6
D7 connects to digital 7
*/

// For Arduino Uno/Duemilanove, etc
//  connect the SD card with DI going to pin 11, DO going to pin 12 and SCK going to pin 13 (standard)
//  Then pin 10 goes to CS (or whatever you have set up)
#define SD_CS 10     // Set the chip select line to whatever you use (10 doesnt conflict with the library)

// In the SD card, place 24 bit color BMP files (be sure they are 24-bit!)
// There are examples in the sketch folder

// optional
#define LCD_RESET A4

// Color definitions
/*
#define	BLACK               0 //0x0000
#define	BLUE               31 //0x001F
#define	GREEN            2016 //0x07E0
#define CYAN             2047 //0x07FF
#define	RED             63488 //0xF800
#define MAGENTA         63519 //0xF81F
#define YELLOW          65504 //0xFFE0 
#define WHITE           65535 //0xFFFF
//#define	BLACK 0x0000
#define	BROWN 0xA145
//#define	RED 0xF800
#define	ORANGE 0xFD20
//#define	YELLOW 0xFFE0
//#define	GREEN 0x07E0
//#define	BLUE 0x001F
#define	VIOLET 0x8010
#define	GREY 0x8410
//#define	WHITE 0xFFFF
*/
#define	BLACK 0x0000
#define	WHITE 0xFFFF
#define	RED 0xF800
#define	C0 0x8000
#define	C1 0xFA20
#define	C2 0xFD00
#define	C3 0x0400
#define	C4 0x0010
#define	C5 0xF81F
#define	C6 0x8010
#define	C7 0x8222
#define	C8 0x8410
#define	C9 0x0000
#define	C10 0xF800
#define	C11 0xFD20
#define	C12 0xFFE0
#define	C13 0x07E0
#define	C14 0x001F
#define	C15 0xFE19
#define	C16 0xDD1B
#define	C17 0xDDD0
#define	C18 0xA514
#define	C19 0xFFFF
unsigned int colors[] = {C0,C1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,C13,C14,C15,C16,C17,C18,C19};
int menu = 0;

// our TFT wiring
TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

// the file itself
File bmpFile;

// information we extract about the bitmap file
int penRadius, bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;

// these are described as if the color is at the top
// along the 20x1 pin side
int pageWidth = 320;
int pageHeight = 240;
int numBoxesRow = 10;
int numBoxesCol = 10;
int boxHeight = 32;
int boxWidth = 24;
int x, y;
boolean sColorCrLf;
unsigned int sColor, tColor, currentcolor;
char bfn[8];
char msg[20];
int menuMAX = 2;

void setup(){
  Serial.begin(115200);
  Serial.println("Paint with a spashscreen\nBy:\nhttp://jk.cbwp.net!\n");
  Serial.println(freeRam());
  
  tft.reset();
  
  // find the TFT display
  uint16_t identifier = tft.readRegister(0x0);
/*  
  if (identifier == 0x9325) {
//    Serial.println("Found ILI9325");
  } else if (identifier == 0x9328) {
//    Serial.println("Found ILI9328");
  } else {
    Serial.print("Unknown driver chip ");
    Serial.println(identifier, HEX);
    while (1);
  }  
*/ 
  tft.initDisplay();

  // splash screen
 // Serial.print("Initializing SD card...");
  pinMode(10, OUTPUT);

  if (!SD.begin(10)) {
   // Serial.println("failed!");
  } else {
/*    
    Serial.println("SD OK!");

    bmpFile = SD.open("s002.bmp");

    if (!bmpFile) {
      Serial.println("didnt find image");
    } else if (! bmpReadHeader(bmpFile)) { 
      Serial.println("bad bmp");
    } else {
      Serial.print("image size "); 
      Serial.print(bmpWidth, DEC);
      Serial.print(", ");
      Serial.println(bmpHeight, DEC);
 
      tft.fillScreen(BLACK);
      //tft.setRotation(2);
      bmpdraw(bmpFile, 0, 0); //120, 40);
      //tft.setRotation(0);
      bmpFile.close();
      delay(2000);
    }
*/
  }

  // drawing page
  tft.fillScreen(BLACK);
  menuItem(20,30); // C10
  msg = "MESSAGES";
  resetMenu();
  //wyolumLogo();
  
  // for debug
  pinMode(13, OUTPUT);
  // get penRadius from analog input
  pinMode(19, INPUT); 
}

#define MINPRESSURE 10
#define MAXPRESSURE 1000
boolean paused = false;

void loop(){//}void ignore(){
  digitalWrite(13, HIGH);
  Point p = ts.getPoint();
  digitalWrite(13, LOW);

  // if you're sharing pins, you'll need to fix the directions of the touchscreen pins!
  //pinMode(XP, OUTPUT);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  //pinMode(YM, OUTPUT);

  // we have some minimum pressure we consider 'valid'
  // pressure of 0 means no pressing!

  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    // press the bottom of the screen to erase 
    if (p.y < (TS_MINY-5)) { 
      //Serial.println("erase");
      msg = "Erasing";
      messageBox();
      tft.fillRect(boxWidth,  boxHeight*2, tft.width()-boxWidth, tft.height()-boxHeight*2, BLACK);
      paused = false;
      msg = "Ok";
      messageBox();
      //resetMenu();
    }

    // turn from 0->1023 to tft.width
    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = map(p.y, TS_MINY, TS_MAXY, tft.height(), 0);

    // pressing pause/draw button
    boolean changed = false;
    if ((p.x < boxWidth*1) && (p.y < boxHeight*10) && (p.y > boxHeight*9)){
      paused = !paused;
      delay(200);
      if (paused){
        msg = "Pausing";
        pauseBox();
      } else {
        msg = "Unpausing";
        drawBox();
      }
      messageBox();
    }
    
    // pressing save button
    else if ((p.x < boxWidth*1) && (p.y < boxHeight*9) && (p.y > boxHeight*8)){
      //bfn = "v10.txt"; 
      grabScreen(bfn); delay(200);
    }

    // pressing load button
    else if ((p.x < boxWidth*1) && (p.y < boxHeight*8) && (p.y > boxHeight*7)){
      //bfn = "v10.txt"; 
      readBmpFile(bfn); delay(200);
    }

    // pressing menu button
    else if ((p.x < boxWidth*1) && (p.y < boxHeight*7) && (p.y > boxHeight*6)){
      menu++;
      if (menu == menuMAX){ menu = 0; }
      menuBox();
      paletteBox();
      delay(200);
    }

    // pressing message box
    else if ((p.x < boxWidth*1) && (p.y < boxHeight*6) && (p.y > boxHeight*2)){
      resetMenu();
      msg = "You are here";
      messageBox();
      delay(200);
    }

    // pressing menu items
    else if (p.y < boxHeight*2) {
      menuItem(p.x,p.y);
      msg = "Changed color";
      messageBox();
      paletteBox();
    }

    // drawing stuff
    else {
      penRadius =  analogRead(5);
      penRadius = map(penRadius, 0, 1023, 1, 5);

      if (((p.y-penRadius) > boxHeight) && ((p.y+penRadius) < tft.height()) && ((p.x-penRadius) > boxWidth) && ((p.x+penRadius) < tft.width())) {
        tft.fillCircle(p.x, p.y, penRadius, currentcolor);
      }
    }
  }
}

void resetMenu(){
  drawBox();
  saveBox();
  loadBox();
  menuBox();
  messageBox();
  paletteBox();
}
void boxes(int b, char *t){
  int bC;
  if (b==3){ bC=menu+4; }else{ bC=10+b; }
  if (b<0){ b=0; }
  int bP=b*32+4;
  int bH1=9-b;
  int bH2=1;
  if (bH1==5){ bH1=2; bH2=4; }
  tft.drawRect(boxWidth*0+0, boxHeight*bH1+0, boxWidth-0, boxHeight*bH2-0, colors[bC]);
  tft.fillRect(boxWidth*0+1, boxHeight*bH1+1, boxWidth-2, boxHeight*bH2-2, BLACK);
  tft.drawRect(boxWidth*0+2, boxHeight*bH1+2, boxWidth-4, boxHeight*bH2-4, colors[bC]);
  tft.setRotation(3);
  tft.setCursor(bP, 8);
  tft.setTextColor(colors[bC]);
  tft.setTextSize(1);
  tft.println(t);
  tft.setRotation(0);
}
void drawBox(){
  boxes(-7,"DRAW");
}
void pauseBox(){
  boxes(0,"STOP");
}
void saveBox(){
  boxes(1,"SAVE");
}
void loadBox(){
  boxes(2,"LOAD");
}
void menuBox(){
  boxes(3,"MENU");
}
void messageBox(){
  boxes(4,msg);
}
void paletteBox(){
  switch (menu){
    case 0:
      for(int i=0;i<10;i++){
        tft.fillRect(boxWidth*i, boxHeight, boxWidth, boxHeight, colors[i]);
      }
      for(int i=0;i<10;i++){
        tft.fillRect(boxWidth*i, boxHeight*0, boxWidth, boxHeight, colors[i+10]);
      }
    break;
    case 1:
      tft.fillRect(boxWidth*0, boxHeight*0, boxWidth*12, boxHeight*2, BLACK);
      tft.fillRect(boxWidth*0+2, boxHeight+8, boxWidth-4, boxHeight-16, currentcolor);
      tft.drawTriangle(boxWidth*1+2, boxHeight+2, boxWidth*1+22, boxHeight+2, boxWidth*1+12, boxHeight+22, currentcolor);
      tft.fillRoundRect(boxWidth*2+2, boxHeight+8, boxWidth-4, boxHeight-16, 4, currentcolor);
      tft.fillCircle(boxWidth*3+12, boxHeight+12, 9, currentcolor);
      tft.drawTriangle(boxWidth*4+2, boxHeight+22, boxWidth*4+2, boxHeight+2, boxWidth*4+22, boxHeight+12, currentcolor);
      tft.drawLine(boxWidth*5+12, boxHeight+22, boxWidth*5+12, boxHeight+2, currentcolor);
      tft.drawLine(boxWidth*6+22, boxHeight+22, boxWidth*6+2, boxHeight+2, currentcolor);
      tft.drawTriangle(boxWidth*7+2, boxHeight+6, boxWidth*7+22, boxHeight+6, boxWidth*7+12, boxHeight+22, currentcolor);
        tft.drawTriangle(boxWidth*7+2, boxHeight+18, boxWidth*7+22, boxHeight+18, boxWidth*7+12, boxHeight+2, currentcolor);
      tft.fillCircle(boxWidth*8+12, boxHeight+12, 9, currentcolor);
        tft.fillCircle(boxWidth*8+12, boxHeight+12, 6, BLACK);
      tft.drawTriangle(boxWidth*9+22, boxHeight+2, boxWidth*9+22, boxHeight+22, boxWidth*9+2, boxHeight+12, currentcolor);
        tft.drawTriangle(boxWidth*9+21, boxHeight+3, boxWidth*9+21, boxHeight+21, boxWidth*9+3, boxHeight+12, currentcolor);
          tft.drawTriangle(boxWidth*9+20, boxHeight+4, boxWidth*9+20, boxHeight+20, boxWidth*9+4, boxHeight+12, currentcolor);
            tft.drawTriangle(boxWidth*9+19, boxHeight+5, boxWidth*9+19, boxHeight+19, boxWidth*9+5, boxHeight+12, currentcolor);

      tft.fillRect(boxWidth*0+8, boxHeight*0+2, boxWidth-16, boxHeight-4, currentcolor);
      tft.drawTriangle(boxWidth*1+2, boxHeight*0+22, boxWidth*1+22, boxHeight*0+22, boxWidth*1+12, boxHeight*0+2, currentcolor);
      tft.fillRoundRect(boxWidth*2+8, boxHeight*0+2, boxWidth-16, boxHeight-4, 4, currentcolor);
      tft.fillCircle(boxWidth*3+12, boxHeight*0+12, 3, currentcolor);
      tft.drawTriangle(boxWidth*4+22, boxHeight*0+2, boxWidth*4+22, boxHeight*0+22, boxWidth*4+2, boxHeight*0+12, currentcolor);
      tft.drawLine(boxWidth*5+2, boxHeight*0+12, boxWidth*5+22, boxHeight*0+12, currentcolor);
      tft.drawLine(boxWidth*6+2, boxHeight*0+22, boxWidth*6+22, boxHeight*0+2, currentcolor);
      tft.drawTriangle(boxWidth*7+6, boxHeight*0+22, boxWidth*7+6, boxHeight*0+2, boxWidth*7+22, boxHeight*0+12, currentcolor);
        tft.drawTriangle(boxWidth*7+18, boxHeight*0+22, boxWidth*7+18, boxHeight*0+2, boxWidth*7+2, boxHeight*0+12, currentcolor);
      tft.fillRect(boxWidth*8+2, boxHeight*0+2, boxWidth-4, boxHeight-4, currentcolor);
        tft.fillRect(boxWidth*8+6, boxHeight*0+6, boxWidth-12, boxHeight-12, BLACK);
      tft.fillRoundRect(boxWidth*9+2, boxHeight*0+2, boxWidth-4, boxHeight-4, 4, currentcolor);
        tft.fillRoundRect(boxWidth*9+6, boxHeight*0+6, boxWidth-12, boxHeight-12, 2, BLACK);
    break;
  }
  currentMenuItem();
}
void menuItem(int x, int y){
  if (y > boxHeight){
    for(int i=0;i<10;i++){
      if (x < boxWidth*(i+1)) { 
        currentcolor = colors[i];
        changeBFN(i);
        return; 
      }
    }
  } else {
    for(int i=0;i<10;i++){
      if (x < boxWidth*(i+1)) { 
        currentcolor = colors[i+10];
        changeBFN(i+10);
        return;
      }
    }
  }
}
void changeBFN(int i){
  //char tmp[7];
  sprintf(bfn, "%i.txt", i);
  //bfn = tmp;
  Serial.print("Bfn:"); Serial.println(bfn);
  Serial.println(freeRam());
}
void currentMenuItem(){
  if(currentcolor == colors[19]){
    tft.drawRect(boxWidth*9, boxHeight*0, boxWidth, boxHeight, RED);
  }else{
    for(int i=0;i<10;i++){
      if(currentcolor == colors[i]){
        tft.drawRect(boxWidth*i, boxHeight, boxWidth, boxHeight, WHITE);
      }else if(currentcolor == colors[i+10]){
        tft.drawRect(boxWidth*i, boxHeight*0, boxWidth, boxHeight, WHITE);
      }
    }
  }
}

unsigned int getPixel(int x, int y){
  tft.writeRegister(0x0020, x); // GRAM Address Set (Horizontal Address) (R20h)
  tft.writeRegister(0x0021, y); // GRAM Address Set (Vertical Address) (R21h)
  unsigned int junk = tft.readRegister(0x0022);
  unsigned int c = tft.readRegister(0x0022);
  return c;
}
void grabScreen(char *f){
  SD.remove(f);
  bmpFile = SD.open(f, FILE_WRITE);

  // if the file opened okay, write to it:
  if (bmpFile) {
    msg = "Saving";
    messageBox();
    Serial.print("Writing to ");
    Serial.print(f);
    Serial.println("...");
    for (y=boxHeight*2;y<320;y++){
      for(x=boxWidth;x<240;x++){
        sColor = getPixel(x, y);
        //if (sColor == 2047 || sColor == 65504){ Serial.println(sColor); }
        bmpFile.println(sColor);
      }
    }

    // close the file:
    bmpFile.close();
    msg = "ok";
    messageBox();
    //Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    msg = "Bad save";
    messageBox();
    Serial.print("error opening: ");
    Serial.print(f);
    Serial.println(";");
  }
}
void readBmpFile(char *f){
  //tft.fillScreen(BLACK);
  msg = "Opening";
  messageBox();
  // re-open the file for reading:
  bmpFile = SD.open(f);
  if (bmpFile) {
    Serial.print("Reading from ");
    Serial.print(f);
    Serial.println("...");
    //Serial.print(" and redrawing screen...");
    // read from the file until there's nothing else in it:
    while (bmpFile.available()) {
      
      tColor = 0;
      sColor = 0;
      for (y=boxHeight*2;y<320;y++){
        for(x=boxWidth;x<240;x++){
          sColorCrLf = false;
          while(!sColorCrLf){
            tColor = bmpFile.read();
            if(tColor == 13){
              tColor = bmpFile.read();
              if(tColor == 10){
                //done with line
                //Serial.println(sColor); // debug
                tft.drawPixel(x, y, sColor);
                sColor = 0;
                tColor = 0;
                sColorCrLf = true;
              }
            }else{
              tColor = tColor - 48;
              sColor = sColor*10 +tColor;
            }
          }
        }
      }
      // close the file:
      bmpFile.close();
      msg = "ok";
      messageBox();
      //Serial.println("done.");
    }
  } else {
    // if the file didn't open, print an error:
    msg = "bad open";
    messageBox();
    Serial.print("error opening: ");
    Serial.print(f);
    Serial.println(";");
  }
}
/*********************************************/
// This procedure reads a bitmap and draws it to the screen
// its sped up by reading many pixels worth of data at a time
// instead of just one pixel at a time. increading the buffer takes
// more RAM but makes the drawing a little faster. 20 pixels' worth
// is probably a good place

#define BUFFPIXEL 20

void bmpdraw(File f, int x, int y) {
  bmpFile.seek(bmpImageoffset);
  
  uint32_t time = millis();
  uint16_t p;
  uint8_t g, b;
  int i, j;
  
  uint8_t sdbuffer[3 * BUFFPIXEL];  // 3 * pixels to buffer
  uint8_t buffidx = 3*BUFFPIXEL;
  
  //Serial.print("rotation = "); Serial.println(tft.getRotation(), DEC);
  
  for (i=0; i< bmpHeight; i++) {
    // bitmaps are stored with the BOTTOM line first so we have to move 'up'

    if (tft.getRotation() == 3) {
      tft.goTo(x, y+bmpHeight-i); 
    } else if  (tft.getRotation() == 2) {
      tft.goTo(x+i, y);
    } else if  (tft.getRotation() == 1) {
        tft.goTo(x+bmpWidth-i, y+bmpHeight); 
    } else if  (tft.getRotation() == 0) {
      tft.goTo(x+bmpWidth, y+i); 
    }
    
    for (j=0; j<bmpWidth; j++) {
      // read more pixels
      if (buffidx >= 3*BUFFPIXEL) {
        bmpFile.read(sdbuffer, 3*BUFFPIXEL);
        buffidx = 0;
      }
      
      // convert pixel from 888 to 565
      b = sdbuffer[buffidx++];     // blue
      g = sdbuffer[buffidx++];     // green
      p = sdbuffer[buffidx++];     // red
      
      p >>= 3;
      p <<= 6;
      
      g >>= 2;
      p |= g;
      p <<= 5;
      
      b >>= 3;
      p |= b;
     
       // write out the 16 bits of color
      tft.writeData(p);
    }
  }
  //Serial.print(millis() - time, DEC);
  //Serial.println(" ms");
}

boolean bmpReadHeader(File f) {
   // read header
  uint32_t tmp;
  
  if (read16(f) != 0x4D42) {
    // magic bytes missing
    return false;
  }
 
  // read file size
  tmp = read32(f);  
  //Serial.print("size 0x"); Serial.println(tmp, HEX);
  
  // read and ignore creator bytes
  read32(f);
  
  bmpImageoffset = read32(f);  
  //Serial.print("offset "); Serial.println(bmpImageoffset, DEC);
  
  // read DIB header
  tmp = read32(f);
  //Serial.print("header size "); Serial.println(tmp, DEC);
  bmpWidth = read32(f);
  bmpHeight = read32(f);

  
  if (read16(f) != 1)
    return false;
    
  bmpDepth = read16(f);
  //Serial.print("bitdepth "); Serial.println(bmpDepth, DEC);

  if (read32(f) != 0) {
    // compression not supported!
    return false;
  }
  
  //Serial.print("compression "); Serial.println(tmp, DEC);

  return true;
}

/*********************************************/

// These read data from the SD card file and convert them to big endian 
// (the data is stored in little endian format!)

// LITTLE ENDIAN!
uint16_t read16(File f) {
  uint16_t d;
  uint8_t b;
  b = f.read();
  d = f.read();
  d <<= 8;
  d |= b;
  return d;
}


// LITTLE ENDIAN!
uint32_t read32(File f) {
  uint32_t d;
  uint16_t b;
 
  b = read16(f);
  d = read16(f);
  d <<= 16;
  d |= b;
  return d;
}
// this handy function will return the number of bytes currently free in RAM, great for debugging!   
int freeRam(void)
{
  extern int  __bss_end; 
  extern int  *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
} 

