#define VfdClkPin 3
#define VfdDataPin 2

//Must be analog
#define keyboardPin A0
//Must be analog
#define ConfigPin A1

//Serial port is arbitrary Pins 0 and 1.
//Please program Arduino with RS232 away from MAX232 or things will go bad.

//SD card ports are arbitrary Pins 10-13

//This code is on GNU GPLv2 License
//Copyright MCbx 2015.

//Dear future user, After configuring defines, you probably will need to configure display
//unit, which in my system is quite uncommon. The display routine is displayString.
//It uses "transmitBits" subroutine. If you want to use LCD, rewrite these routines.
//DisplayString takes string to display. If 1 (default) is in second parameter, it displays on
//a clear screen. If 0, it displays it from the last position of "0" to the length of cursor.
//EXAMPLE: displayString("aa",0); displayString("bb",0); displayString(cc,0); gives "AABBCC"
// but with 1, it will display AA, then clear and put BB, then clear and put CC.

//You should also configure waitForKey routine, because it supports resistive keyboard and your
//resistive keyboard may give different values than my one. To do it, use similar function
//in a new project, but uncomment "Serial.println..." to get values of pressed keys.

//Keyboard is typical telephone keyboard with * and # between 0. "*" is for "Yes" or "Again",
//"#" is for "No", "Cancel", or "Backspace".

#include <SPI.h>
#include <SD.h>

unsigned char pinCount=0;
bool displayPins=1;

//needed for displayString using VFD tube
void transmitBits(unsigned char bword, unsigned char length)
{ //PLEASE DO NOT REMOVE DELAYS.
  for (unsigned char i=0;i<length;i++)
  {
     digitalWrite(VfdClkPin,HIGH);
     delayMicroseconds(80);
     digitalWrite(VfdDataPin,!(bitRead(bword,length-1-i)));
     delayMicroseconds(80);
     digitalWrite(VfdClkPin,LOW);
  }
}

//Display my text on VFD tube.
void displayString(String text, bool clear=1)
{
  unsigned char initSeq=B10010000;
  if (!clear)
  {
     initSeq=B10000010;
  }
  for (unsigned char i=0;i<text.length();i++)
  {
     transmitBits(initSeq,8);
     transmitBits(B00000000,2); //first iteration - go home
     transmitBits(text.charAt(i),6);
     initSeq=B10000010;
  }
  if (clear)
  {
    for (unsigned char i=text.length();i<16;i++)
    {
       transmitBits(B10000010,8);
      transmitBits(B00100000,8); //fill rest of display with spaces
    }
  }
}

//locking thing waiting for key
char waitForKey()
{
  int v;
  while(1)
  {
  v=analogRead(keyboardPin); //sample
  delay(100);
  if (abs(v-analogRead(keyboardPin))>10)
    continue;
 // Serial.println(v);
  if ((v>120)&&(v<130)) 
    return 1;
  if ((v>80)&&(v<90)) 
    return 2;
  if ((v>40)&&(v<50)) 
    return 3;
  if ((v>345)&&(v<405)) 
    return 4;
  if ((v>280)&&(v<290)) 
    return 5;
  if ((v>175)&&(v<185)) 
    return 6;
  if ((v>695)&&(v<705)) 
    return 7;
  if ((v>595)&&(v<615)) 
    return 8;
  if ((v>425)&&(v<445)) 
    return 9;
  if ((v>780)&&(v<790)) 
    return 0;
  if ((v>805)&&(v<845)) 
    return -1; //* - star
  if ((v>650)&&(v<660)) 
    return -2; //# - hash
  }
}

//clear serial port buffers
void serialFlush()
{
  while(Serial.available() > 0) 
  {
    char t = Serial.read();
  }
}  

//Display error, wait for key, reset
void ErrorHandler(String Err)
{
  displayString("ERR "+Err);
  waitForKey();
  asm volatile ("  jmp 0"); //bad reset, but works
}

//reset tester
void testReset()
{
  serialFlush();
  Serial.write(48);
  delay(20);
  Serial.write(48);
  delay(20);
  Serial.write(48);
  delay(20);
  char i=0;
  while (i<100)
  {
    i++;
    delay(5);
    if (Serial.available())
    {
      serialFlush();
      return;
    }
  }
  ErrorHandler("COM TIMEOUT");
  return;
}

//Power chip on
void testPowerOn()
{
  serialFlush();
  Serial.write(49);
  delay(10);
  char i=0;
  while (i<100)
  {
    i++;
    delay(5);
    if (Serial.available())
    {
      char d=Serial.read();
      if (d=='O')   //O as OK
        return;
      else
        ErrorHandler("PWR BAD "+String(d));   
    }
  }
  ErrorHandler("COM TIMEOUT");
}

//Prepares string and pushes it in command
//Used as "Set IO" and "Set data"
//TODO: Optimize, it's just QtICTester's code ripoff.
void pushCombo(String combo, char command)
{
    //prepare 24-bit bit pattern.
  String pattern="";
  for (char i=0;i<12-(pinCount/2);i++)
  {
    pattern=pattern+'1';
  }
  pattern=pattern+combo+pattern; //crude hack - the same number of free pins on both sides

  //parse bit pattern to bytes
  unsigned char verb0=0;
  unsigned char verb1=0;
  unsigned char verb2=0;
  unsigned char verb3=0;

  unsigned char nMul=128;
  for (char F=0;F<8;F++)
  {
    if (pattern.charAt(F)=='1')
    {
      verb0+=nMul;
    }
    if (pattern.charAt(F+8)=='1')
    {
      verb1+=nMul;
    }
    nMul=nMul/2;
  }
  nMul=32;
  for (char F=0;F<5;F++)
  {
    if (pattern.charAt(F+16)=='1')
    {
      verb2+=nMul;
    }
    if (F==0)
    {
      nMul=nMul/2;
    }
    nMul=nMul/2;
  }
  nMul=4;
  for (char F=0;F<3;F++)
  {
    if (pattern.charAt(F+21)=='1')
    {
      verb3+=nMul;
    }
    nMul=nMul/2;
  }

  //send bytes to device  
  serialFlush();
  Serial.write(command);
  Serial.write(verb0);
  Serial.write(verb1);
  Serial.write(verb2);
  Serial.write(verb3);
  delay(1);
  char i=0;
  while (i<250)
  {
    i++;
    delay(1);
    if (Serial.available())
    {
      char d=Serial.read();
      if (d=='O')   //O as OK
        return;
      else
        ErrorHandler("COM BAD "+String(d));   
    }
  }
  ErrorHandler("COM TIMEOUT");

}

//compare, return first bad pin
char testGet(String comp)
{
  serialFlush();
  Serial.write(52);
  char ret[5] = {0,0,0,0,0};
  Serial.readBytes(ret,5);
  if (ret[4]!='O')
  {
    ErrorHandler("GET DATA");
  }
  //Get result. We will always get 24-bit result.
  String res="000000000000000000000000";
  for (char i=0;i<8;i++)
  {
    if (bitRead(ret[0],i)==1)
    {
      res.setCharAt(7-i,'1');
    }
    if (bitRead(ret[1],i)==1)
    {
      res.setCharAt(15-i,'1');
    }
    if ((bitRead(ret[3],i)==1)&&(i<3))
    {
      res.setCharAt(23-i,'1');
    }
    if ((bitRead(ret[2],i)==1)&&(i<4))
    {
      res.setCharAt(20-i,'1');
    }
  }
  if (bitRead(ret[2],5))
  {
    res.setCharAt(16,'1');
  }

  //cut res
  res=res.substring(12-(pinCount/2),12-(pinCount/2)+pinCount);
  comp=comp.substring(0,pinCount);
  for (char i=0;i<res.length();i++)
  {
    if ((res.charAt(i)!=comp.charAt(i))&&(comp.charAt(i)!='X'))
    {
      delay(20);
      serialFlush();
      Serial.write(48);
      testReset();
      return i+1;
    }
  }
  
  return 0;
}

////////////
// SETUP ///
////////////
void setup() {
  //initialize VFD module
  pinMode(VfdClkPin, OUTPUT);
  digitalWrite(VfdClkPin,LOW); //LOW means HIGH (transistor) means inactive
  pinMode(VfdDataPin, OUTPUT);
  digitalWrite(VfdDataPin,LOW); //this is meaningless
  pinMode(keyboardPin,INPUT);
  pinMode(ConfigPin,INPUT);
  displayString(""); //clear screen
  displayString("BOOT.",0);

  //if it's more or less 5V, boot slower. If it's disconnected, faster mode.
  if(analogRead(ConfigPin)>1000) 
  {
    Serial.begin(57600, SERIAL_8N1); //in Vcc - 57600 - jumper in
    displayString("-",0);
  }
  else
  {
    Serial.begin(19200, SERIAL_8N1); //in gnd - 19200 - jumper out
    displayString("1",0);
  }
  displayString(" .",0);
  testReset();
  

  displayString(" .",0);
  //init card
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);
  if (!SD.begin(10))
  {
    displayString("SD ERR",1);
    waitForKey();
  }

  displayString(" .",0);
  //test DIP config
  if (!SD.exists("icpower.rc"))
  {
    displayString("NO ICPOWER.RC"); //this is a warning. If no file, use pin numbers.
    waitForKey();
  }
  else
  {
    displayPins=0;
  }


  displayString(" .OK",0);
}

////////////////
// MAIN LOOP ///
////////////////
void loop() {
  String chipName="";   //first, get chip name from user
  displayString("CHIP)"+chipName);
  char key=-3;
  while (key!=-1)
  {
    key=waitForKey();
    if (key>-1) //editor
    {
      if (chipName.length()<8)
      {
        chipName=chipName+(int)key;
        displayString("CHIP)"+chipName);
      }
    }
    if (key==-2) //backspace
    {
      chipName.remove(chipName.length()-1);
      displayString("CHIP)"+chipName);
    }
  }   //key -1 confirms name.
  chipName=chipName+".MOD";
//  displayString("LOAD "+chipName);
  if (!SD.exists(chipName))
  {
    displayString("NO "+chipName);
    delay(1000);
    waitForKey();
    return;
  }

  //MAIN TEST ROUTINE
bool onemoretime=1;
bool donotask=0;
 while(onemoretime)
 {
  //First, let's open file and read basic information about tested IC
    onemoretime=0;
    File dataFile(SD.open(chipName));
    dataFile.readStringUntil('\n'); //skip name
    dataFile.readStringUntil('\n'); //skip description
    pinCount = dataFile.readStringUntil('\n').toInt(); //pins number
    unsigned char vcc=dataFile.readStringUntil('\n').toInt(); //vcc
    unsigned char gnd=dataFile.readStringUntil('\n').toInt(); //gnd  
    String initialIO="";
    //By reading the pins description, we have to do two things. First is power pins discovery.
    //Second is initial I/O string. Additional Power pins is TODO. Now it assumes only primary power pins.
    for (unsigned char i=0;i<pinCount;i++)
    {
      String line=dataFile.readStringUntil('\n');
      unsigned char funct=line.charAt(line.length()-2);
      switch (funct)
      {
        case 48:   //0 - in to pic's output
          initialIO+='0';
          break;
        case 49:   //1 - out to pic's input
          initialIO+='1';
          break;
       case 51:    //3 - GND to pic's input
           initialIO+='1';
          break;
       case 52:    //4 - Vcc to pic's input
           initialIO+='1';
          break;
       case 53:    //5 - nc to pic's input
           initialIO+='1';
          break;
      }
    }
    //Now ask about power configuration. It we're looping test, we don't ask.
    if (!donotask)
    {
      if (displayPins)
      {
      displayString("VCC "+String(vcc+(12-(pinCount/2)))+" . GND "+String(gnd+(12-(pinCount/2)))+" )");
      }
      else
      {
        //If we're using DIP definitions, we must read it into program and compare it against situation
        String dv="";
        String dg="";
        File powerFile(SD.open("icpower.rc"));
        //open file
        while (powerFile.available())
        {
           String a=powerFile.readStringUntil(','); //switch number. This is string
           String b=powerFile.readStringUntil(','); //pin function, this is sring
           unsigned char c=powerFile.readStringUntil('\n').toInt(); //pin number, this must be uchar
           if ((vcc+(12-(pinCount/2))==c)&&(b=="1"))
           {
            dv="DIP "+a;
           }
           if ((gnd+(12-(pinCount/2))==c)&&(b=="0"))
           {
            dg="DIP "+a;
           }
        }
        if (dv=="")
        {
          dv="VCC "+String(vcc+(12-(pinCount/2)));
        }
        if (dg=="")
        {
          dg="GND "+String(gnd+(12-(pinCount/2)));
        }
        powerFile.close();
        displayString(dv+" . "+dg);
      }
      
      key=10;   //We leave user to set proper pins
      while (key!=-1) //To avoid accidential starting, the only continuation
      {               //key is -1. All other except -2 (return) are inactive.
        key=waitForKey();
        if (key==-2)
        {
          return;
        }
      }
    }
    //execute script
    unsigned int steps=0;
    while(dataFile.available())
    {
      steps++;
      dataFile.readStringUntil(',');  //skip number of line
      String line=dataFile.readStringUntil('\n'); //read command and argument
      char command=line.charAt(0);     //command
      String argument=line.substring(1);        //argument is everything later
      displayString(String(steps)+" - "+command);
      switch (command)
      {
        case 48:   //0 - reset
          testReset();
          pushCombo(initialIO,50);
          break;
        case 49:   //1 - power on
          testPowerOn();
          break;
        case 50:   //2 - set io
           initialIO=argument;
          pushCombo(initialIO,50);
          break;
        case 51:   //3 - set data
           pushCombo(argument,51);
          break;
        case 52:   //4 - get data
          char q=testGet(argument);
          if (q>0)
          {
            delay(20);
            serialFlush();
            Serial.write(48);   //We're stopping. Again unconditional reset to power off.
            delay(20);
            displayString(String(steps)+". BAD PIN "+String((int)q));
            char k=waitForKey();
            if (k==-1) //again
            {
              while (dataFile.available()) //This seeks the file to end, so while loop throws up
              {
                char dd=dataFile.read();
              }
              onemoretime=1;
              donotask=1;
            }
            else    //any otherkey returns
            {
              return;
            }
            
          }
          break;
      }
  //    waitForKey();   //Please uncomment if debug needed   
    }
    testReset(); //Reset. Reset Every time smething goes wrong. Original Author did reset 3 times.
    dataFile.close();
    if (!((onemoretime)&&(donotask)))
    {
      displayString("CHIP OK.");
      char c=waitForKey();
      if (c==-1) //we want again
        {
          onemoretime=1;
          donotask=1;
        }
    }
 }
      
}

