#include <LCDWIKI_GUI.h> //Core graphics library
#include <LCDWIKI_SPI.h> //Hardware-specific library

#define MODEL ILI9225
#define CS   A5 //These can change but these are the recommended pins for the fastest screen update times
#define RST  A4  
#define CD   A3
#define LED  A0

LCDWIKI_SPI mylcd(MODEL,CS,CD,RST,LED); //model,cs,dc,reset,led

#define  BLACK   0x0000 //These are the different colour combinations you can use
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

#define HALL A2 //can change as deemed necessary
#define SWITCH 5 //can change as deemed necessary

const float pi = 3.14159265;
volatile float SPEED, LASTSPEED, MAXSPEED, DISTANCE, ADDON, MINUTES = 0;
volatile float TIME[3];
const float WheelRadius = 0.35; //This value should be as accurate as possible and be measured from the center of the wheel to the ground
const int DELAY = (int(((2*pi*WheelRadius)/27.78)*1000) + 2); //This sets the maximum speed of the bike to approximately 100 km/h
boolean INITIALIZE = true;
boolean MAGNETSTUCK = false;

void setup() {
  mylcd.Init_LCD();
  mylcd.Fill_Screen(BLACK); //fills the screen to a black colour this can be changed to any color you want, make sure to also change the text back colour too
  mylcd.Set_Text_Mode(0);
  mylcd.Set_Text_Back_colour(BLACK); //text back colour should be the same as the background colour but it doesnt have to be
  pinMode(HALL, INPUT); //sets pins to inputs
  pinMode(SWITCH, INPUT_PULLUP);
}

void loop(){
  while (digitalRead(SWITCH) == HIGH) {

    Initialize();   //resets the screen if needed

    Speed();        //executes the speed code found in Void speed(){CODE}

    TIME[2] = Seconds();  //Arduino notices and records current time before displaying data
    if (MAGNETSTUCK == true){         //if magnet is trapped near the sesnor:
      mylcd.Set_Text_colour(RED);     //turns digits red
      Write();                        //prints the speed value
    } else {
      mylcd.Set_Text_colour(CYAN);   //digits remain white
      Write();                        //prints the speed value
    }

    MAGNETSTUCK = false;  //magnet stuck is reset

    ADDON = (Seconds() - TIME[2]) - 2.0*pi*WheelRadius/((SPEED + 7.5)/3.6); //subtracts the period of a wheel rotation from the amount of time required to update the screen
    //if speedometer goes weird around a certain sped range (normally in the teens) you can increase the + 7.5 value to counteract this
  }
  Stats();
  ADDON = 0;
}


void Initialize(){
  if (INITIALIZE == true) {
    delay(500);
    mylcd.Fill_Screen(BLACK); //fills the screen to a black colour this can be changed to any color you want, make sure to also change the text back colour too
    mylcd.Set_Text_colour(WHITE);
    mylcd.Set_Text_Size(2); //sets text size to 2 times the normal
    mylcd.Print_String("Speed:", 5, 5); //prints a string "speed" at 5 units accross and 5 units down
    mylcd.Print_String("Distance:", 5, 160); //prints a string "distance" at 5 units accross and 160 units down
    INITIALIZE = false;
  }
}

void Speed(){
  LASTSPEED = SPEED;      //sets the last speed as the previous value for speed
  TIME[0] = TIME[1];      //Records the time as the previous time which the magnet passed the sensor

  while ((analogRead(HALL) >= 300) && (digitalRead(SWITCH) == HIGH)){}  //waites for magnet to pass the sensor
  TIME[1] = Seconds();                                                  //Arduino notices and records current time that the magnet passes the sesnor

  if (ADDON > 0){                                                   //If the screen is updated and update time is significant:
    DISTANCE = DISTANCE + SPEED*((Seconds()-TIME[2])/3600);         //estimates and adds the amount of distance lost due to screen update
    TIME[0] = TIME[1];                                              //resets Time 1 as the first time to pass the magnet
    delay(DELAY);                                                   //this is to help stop the magnet being detected twice in quick succession

    while ((analogRead(HALL) >= 300) && (digitalRead(SWITCH) == HIGH)){}   //waits until sensor re-detects the magnet
    TIME[1] = Seconds();                                                   //now time 2 is the time the sensor re-detects the magnet
  }

  delay(DELAY);                   //this is to help stop the magnet being detected twice in quick succession

  if ((TIME[1]-TIME[0]) > 0){                             //THe Timedifference will initally be 0 but this would result in an infinite speed thus the code ignores it
    SPEED = ((2*pi*WheelRadius)/(TIME[1]-TIME[0]))*3.6;   //speed of the bike = (distance (circumfrance of the circle) / (the difference in time) (then converted to kilometers and hour)
    if (SPEED > (LASTSPEED + 20)){                        //If the bike accelerates too quickly:
      SPEED = LASTSPEED;                                  //The current speed value must be wrong therefore the old one is used instead
      MAGNETSTUCK = true;                                 //Likely that the magnet has not escaped the sensor soon enough which turns the numbers red
    } 
  }

  if (SPEED > MAXSPEED){ MAXSPEED = SPEED; }  //This caclulates the maximum speed achieved

  DISTANCE = DISTANCE + SPEED*((TIME[1]-TIME[0])/3600);   //The distance = the total distance traveled + Speed * Time. Dividing the Time difference by 3600 converts seconds into hours
}

void Write(){
  mylcd.Set_Text_Size(15);                              //sets text size to 15 times normal

  if (SPEED >= 10) {
    mylcd.Print_Number_Int(int(SPEED), CENTER, 30, 2, ' ', 10);
  } else if (int(SPEED) == int(LASTSPEED)){
    mylcd.Print_Number_Int(int(SPEED), CENTER, 30, 2, ' ', 10);
  } else {
    mylcd.Print_String("  ", CENTER, 30);               //Or it clears the speed value
    mylcd.Print_Number_Int(int(SPEED), CENTER, 30, 2, ' ', 10);
  }

  mylcd.Set_Text_Size(3);                               // sets text size to 3 times normal
  mylcd.Print_Number_Float(DISTANCE, 2, 5, 185,'.', 5, ' '); //prints distance value
}

void Stats(){ 
  if (INITIALIZE == false){ //Similar to initalize
    delay(500);
    mylcd.Fill_Screen(BLACK);
    MINUTES = Seconds()/60;
    INITIALIZE = true;
  } 

  mylcd.Set_Text_colour(WHITE);
  mylcd.Set_Text_Size(2);

  //The following just print each of the different statistcs recorded by the arduino
  mylcd.Print_String("Distance: ", 5, 10);
  mylcd.Print_Number_Float(DISTANCE, 2, 10, 30, '.', 7, ' ');

  mylcd.Print_String("Duration: ", 5, 65);
  if (MINUTES >= 60) {
    mylcd.Set_Text_Size(2);
    mylcd.Print_Number_Float((MINUTES/60), 2, 10, 85, '.', 7, ' ');
    mylcd.Set_Text_Size(1);
    mylcd.Print_String("hr", 120, 90);
  } else {
    mylcd.Set_Text_Size(2);
    mylcd.Print_Number_Float(MINUTES, 2, 10, 85, '.', 7, ' ');
    mylcd.Set_Text_Size(1);
    mylcd.Print_String("min", 120, 90);
  }

  mylcd.Set_Text_Size(2);
  mylcd.Print_String("Average Speed: ", 5, 120);
  mylcd.Print_Number_Float((DISTANCE/(MINUTES/60)), 2, 10, 140, '.', 7, ' ');

  mylcd.Print_String("Max Speed: ", 5, 175);
  mylcd.Print_Number_Float(MAXSPEED, 2, 10, 195, '.', 7, ' ');

  mylcd.Set_Text_Size(1);
  mylcd.Print_String("km", 120, 35);
  mylcd.Print_String("Km/h", 120, 145);
  mylcd.Print_String("Km/h", 120, 200);
}

float Seconds(){
  return float(millis())/1000; //returns seconds
}
