#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <Arduino.h>
#include <HCSR04.h>

#ifndef ArduinoSort_h
#define ArduinoSort_h

/**** These are the functions you can use ****/

// Sort an array
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray);

// Sort in reverse
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray);

// Sort an array with custom comparison function
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType));

// Sort in reverse with custom comparison function
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType));





/**** Implementation below. Do not use below functions ****/

namespace ArduinoSort {
	template<typename AnyType> bool builtinLargerThan(AnyType first, AnyType second) {
		return first > second;
	}

	template<> bool builtinLargerThan(char* first, char* second) {
		return strcmp(first, second) > 0;
	}

	template<typename AnyType> void insertionSort(AnyType array[], size_t sizeOfArray, bool reverse, bool (*largerThan)(AnyType, AnyType)) {
		for (size_t i = 1; i < sizeOfArray; i++) {
			for (size_t j = i; j > 0 && (largerThan(array[j-1], array[j]) != reverse); j--) {
				AnyType tmp = array[j-1];
				array[j-1] = array[j];
				array[j] = tmp;
			}
		}
	}
}

template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray) {
	ArduinoSort::insertionSort(array, sizeOfArray, false, ArduinoSort::builtinLargerThan);
}

template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray) {
	ArduinoSort::insertionSort(array, sizeOfArray, true, ArduinoSort::builtinLargerThan);
}

template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType)) {
	ArduinoSort::insertionSort(array, sizeOfArray, false, largerThan);
}

template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType)) {
	ArduinoSort::insertionSort(array, sizeOfArray, true, largerThan);
}


#endif


#define switchUp A3
#define switchDown A0
#define switchConfirm A2
#define switchMenu 12
#define switchManual A1

#define sonarT 6
#define sonarE 5
#define buzzPin 4
#define motorPin 3

LiquidCrystal_I2C lcd(0x27,16,2);
UltraSonicDistanceSensor hcsr04(sonarT, sonarE);



int waterLev_y, waterLev_x, motor_state, curr_data, waterLev, tnkHeight;
unsigned int  time_y, time_x, time_xy;
float consumed_water, time_diff, amount, waterPerSec;
//int siphon          =0;

int valuesSonar[7] = {};

int measureDistance(){
    for(int & i : valuesSonar){
        i = hcsr04.measureDistanceCm();
        delay(40);
        Serial.println(i);
    }
    sortArray(valuesSonar, 7);
    return valuesSonar[3];
}

int waterLevel() {
    curr_data = (int)((((float)tnkHeight / 1.25) - (((float)measureDistance()) - ((float)tnkHeight / 6))) / ((float)tnkHeight / 125));
    if (curr_data < 0)
        curr_data = 0;
    if (curr_data > 100)
        curr_data = 100;
    return curr_data;
}

int setCLW(int posX, int posY, int value) {
    while (true) {
        if (digitalRead(switchUp) == HIGH) {
            value = value + 1;
            delay(250);
        }
        if(digitalRead(switchDown) == HIGH) {
            delay(200);
            value = value - 1;
        }
        if (value > 999)
            value = 1;
        if (value < 1)
            value = 999;

        lcd.setCursor(posX, posY);

        if (value<100){
            if (value<10)
                lcd.print(" 0"+String(value) + " ");
            else
                lcd.print(" "+String(value) + " ");
        } else {
            lcd.print(String(value) + " ");
        }

        if (digitalRead(switchConfirm) == HIGH) {
            while (true) {
                delay(100);
                if (digitalRead(switchConfirm) == LOW)
                    break;
            }
            break;
        }
    }
    return value;
}


void setCirCu(int tnk_x, int tnk_y) {
    lcd.setCursor(0,0);
    lcd.print("Tank Size=");
    amount = (float)0.000513402015425836 * (float)0.8 * tnk_x * tnk_y * tnkHeight;
    lcd.print(amount);
    lcd.print(" L");
    delay(1500);
}


void pumpStatus(char *str, int usedWater, int motorState) {
    if (motorState == 1) {
        lcd.clear();
        digitalWrite(motorPin, HIGH);
        motor_state = 1;
    } else if (motorState == 0) {
        lcd.clear();
        digitalWrite(motorPin, LOW);
        motor_state = 0;
    }
    lcd.setCursor(13,0);
    lcd.print(str);
    lcd.print("   ");
    lcd.setCursor(0,1);
    lcd.print("Used(L): " + String(usedWater) + "     ");

}


void setup() {

//    pinMode(resetPin, OUTPUT);
    pinMode(motorPin,OUTPUT);
    pinMode(buzzPin,OUTPUT);
    pinMode(switchMenu, INPUT);
    pinMode(switchConfirm, INPUT);
    pinMode(switchUp, INPUT);
    pinMode(switchDown, INPUT);
    pinMode(switchManual, INPUT);
    delay(200);
    lcd.init();
    lcd.backlight();
    lcd.clear();
    Serial.begin(9600);
    Serial.println("GG");
    tnkHeight = 0;
    if (tnkHeight == 0){
        lcd.print("WATER PUMP");
        lcd.setCursor(0, 1);
        lcd.print("Booting");
        delay(1000);
        lcd.clear();
        lcd.print("Keep Tank Empty");
        delay(1000);
        lcd.clear();
        tnkHeight = (int)(floor((double)measureDistance()));
        lcd.print("Height:");
        lcd.print(tnkHeight);
        delay(1000);
        lcd.clear();
        int asd = 0;
        while(true) {
          if (asd == 0){
            lcd.setCursor(2,0);
            lcd.print("Circle Tank");
          } else {
            lcd.setCursor(1,0);
            lcd.print("Rectangle Tank");
          }
          if (digitalRead(switchConfirm) == HIGH) {
            while (true) {
              delay(50);
              if (digitalRead(switchConfirm) == LOW)
                break;
            }
            break;
          }
          if (digitalRead(switchUp) == HIGH) {
            while (true) {
              delay(50);
              if (digitalRead(switchUp) == LOW)
                  break;
            }
            if (asd == 0){
              asd = 1;
              
            } else {
              asd = 0;
            }
            lcd.clear();
          }
        }
        lcd.clear();
        if (asd == 0){
          lcd.setCursor(4,0);
          lcd.print("Hight:");
          int size = setCLW(6, 1, 0);
          setCirCu(size, size);
        } else {
          lcd.setCursor(4,0);
          lcd.print("Hight:");
          int sizex = setCLW(6, 1, 0);
          lcd.clear();
          lcd.setCursor(4,0);
          lcd.print("Weight:");
          int sizey = setCLW(6, 1, 0);
          setCirCu(sizex, sizey);
        }
        waterPerSec = 0;
        consumed_water = 0;
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
    } else {
        lcd.print("Current Failure!");
        delay(2000);
        waterPerSec = 0;
        consumed_water = 0;
        lcd.clear();
    }
}

void loop() {
    waterLev = waterLevel();
    lcd.setCursor(0,0);
    lcd.print("Water: ");
    if (waterLev<100){
        if (waterLev<10)
            lcd.print(String(waterLev) + "%  ");
        else
            lcd.print(String(waterLev) + "% ");
    } else {
        lcd.print(String(waterLev) + "%");
    }

    if (motor_state == 0){
        pumpStatus("OFF", (int)consumed_water, 2);
    } else if (motor_state == 1){
        pumpStatus("ON", (int)consumed_water, 2);
    }

    if (motor_state == 0) {
        if (waterLev < 15 || digitalRead(switchManual)) {
            time_y = millis();
            if (waterPerSec == 0) {
                Serial.println("First Time");

                pumpStatus("ON", 0, 1);
                waterLev_y = waterLev;
                Serial.println(time_y);
                Serial.println(waterLev_y);
            } else {
                pumpStatus("ON", (int)consumed_water, 1);
            }
        }
    } else if (motor_state == 1) {
        if (waterPerSec == 0) {
            if (waterLev >= 100 || digitalRead(switchManual)) {
                waterLev_x = waterLev;
                time_x = millis();
                waterPerSec = ((waterLev_x - waterLev_y) / (((float)(time_x - time_y)/1000) * (amount / 100)));
                Serial.print("amount = ");
                Serial.println(amount);
                Serial.print("waterPerSec = ");
                Serial.println(waterPerSec);
                consumed_water = amount;
                Serial.println(time_x);
                Serial.println(waterLev_x);
                pumpStatus("OFF", (int)consumed_water, 0);
            }
        } else {
            time_x = millis();
            if (((time_x - time_y)/1000) >= 1){
                time_y = millis();
                consumed_water = consumed_water + waterPerSec;
                pumpStatus("ON", (int)consumed_water, 2);
                Serial.print("waterPerSec = ");
                Serial.println(waterPerSec);
            }
            if (waterLev >= 100 || digitalRead(switchManual)) {
                pumpStatus("OFF", (int)consumed_water, 0);
                Serial.print("consumed_water = ");
                Serial.println(consumed_water);
            }
        }
    }
}

//------------------------------------------------------------------------------------------------------------------------\\
  //===========================================================The End========================================================\\
 //____________________________________________________________________________________________________________________________\\