use rppal::gpio::{Gpio, OutputPin};
use serde::{Deserialize};
use serde_json;
use sqlite;
use std::env;
use std::fs::File;
use std::path::Path;
use std::thread;
use std::time::Duration;

// Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16.
const GPIO_SWITCH: u8 = 24;

// set duration of pin switching test

#[derive(Deserialize)]
struct Config{
    database: String,
    device_name_voltage: String,
    test_duration_seconds: u64,
    voltage_off: f32,
    voltage_recover: f32,
}

#[derive(Deserialize)]
#[allow(non_snake_case)]
struct DbData{
    V: String,
}

//fn main() {
fn main () {
    let home_dir = env::home_dir().expect("Could not determine home directory");
    let mut config_file_path: &str = &(home_dir.to_string_lossy() + "/.isolated-switches/config.json");

    // read and parse command line parameters
    let args: Vec<String> = env::args().collect();
    for n in 1..args.len() {
        match args[n].as_str() {
            "on" => {
                pin_switch_on();
                return;
            },
            "off" => { 
                pin_switch_off();
                return;
            },
            "status" => { println!("Inverter status: {}" , if pin_status() == 0 { "off" } else { "on" }); std::process::exit(0); },
            "test" => pin_test(config_file_path),
            "config" => {
                if args.len() > n + 1 { config_file_path = args[n + 1].as_str(); }
            },
            _ => ()
        }
    }

    let config: Config = read_config(&config_file_path);
    let pin_status_cur = pin_status();
    let voltage = get_current_voltage(&config);

    if voltage < config.voltage_off && pin_status_cur > 0 {
        println!("Low voltage!");
        pin_switch_off();
    }
    else if voltage >= config.voltage_recover && pin_status_cur == 0 {
        println!("Voltage recovered!");
        pin_switch_on();
    }
    else {
        // no change in status needed
        let pin_status_text = if pin_status_cur == 0 { "off" } else { "on" };
        println!("No change in inverter status. Current status: {}", pin_status_text);
    }

    //end program with OK status
}

// read and parse configuration file
fn read_config(config_file_path: &str) -> Config {
    let config_file = File::open(config_file_path)
        .expect(&("Could not read config file ".to_owned() + config_file_path ));
    let config: Config = serde_json::from_reader(config_file)
        .expect("file should be proper JSON");
    config
}

fn pin_status () -> u8 {
    let pin_level: u8 = Gpio::new().unwrap().get(GPIO_SWITCH).unwrap().read() as u8;
    return pin_level;
}

fn pin_switch_on () {
    let mut pin: OutputPin = Gpio::new().unwrap().get(GPIO_SWITCH).unwrap().into_output();
    pin.set_reset_on_drop(false);
    pin.set_high();
    println!("Inverter switched on.");
}

fn pin_switch_off () {
    let mut pin: OutputPin = Gpio::new().unwrap().get(GPIO_SWITCH).unwrap().into_output();
    pin.set_reset_on_drop(false);
    pin.set_low();
    println!("Inverter switched off.");
}

fn pin_test (config_path: &str) {
    let config: Config = read_config(&config_path);
    pin_switch_on();
    thread::sleep(Duration::from_millis(config.test_duration_seconds * 1000));
    pin_switch_off();
}

fn get_current_voltage (config: &Config) -> f32 {
    let mut voltage: f32 = 0.0;
    if !Path::new(&config.database).exists() {
        return voltage;
    }

    let db_connection = sqlite::open(&config.database).unwrap();
    let query = format!("SELECT data FROM '{device}' ORDER BY date DESC, time DESC LIMIT 1;", device = config.device_name_voltage);

    let _ = db_connection.iterate(query, |pairs | {
        let data = pairs.get(0).unwrap().1.unwrap();
        let test_parse: DbData = serde_json::from_str(data).expect("no proper JSON data found on database");
        let raw_value = match test_parse.V.parse::<i32>() {
            Ok(v) => v,
            Err(_) => return true
        };
        voltage = raw_value as f32 / 1000.0;
        true
    });
    return voltage;
}
