use super::{
    configuration::{self, *},
    constants::*,
    models::{
        electric_heating::{ElectricHeating, Intensity},
        message::HPMessage,
    },
    types::{HeatPumpResponse, HeatPumpResponseSender, SerialBuffer},
};
use crate::{
    modules::heat_pump::types::HPReceivingFrame,
    utilities::helpers::{parse_byte_to_bits, read_file_to_string, write_string_to_file},
    APP_STATE,
};
use log::{debug, error, info, warn};
use serialport::SerialPort;
use std::collections::HashSet;
use std::{
    mem::{size_of, transmute},
    time::Duration,
};

/// Open desired serial port
pub fn open_serial_port(
    port_name: &str,
    baud_rate: u32,
    data_bits: serialport::DataBits,
    stop_bits: serialport::StopBits,
    parity: serialport::Parity,
) -> Result<Box<dyn SerialPort>, serialport::Error> {
    let port = serialport::new(port_name.to_string(), baud_rate)
        .data_bits(data_bits)
        .stop_bits(stop_bits)
        .parity(parity)
        .timeout(Duration::from_millis(TIMEOUT))
        .open();
    if port.is_ok() {
        info!(
            "Serial port has been successfully open on {}, {}bps, {:?}, {:?}, {:?}",
            port_name, baud_rate, data_bits, parity, stop_bits,
        );
    }
    port
}

/// Read the config file and update the app state
pub fn initialize_hp_config() {
    match read_file_to_string(HP_CONFIG_FILE_NAME) {
        Ok(config) => match serde_json::from_str(&config) {
            Ok(config) => {
                let mut lock = APP_STATE.lock().unwrap();
                lock.hp_config = config;
            }
            Err(_) => warn!("Error while parsing heat pump config. Using default values"),
        },
        Err(_) => warn!(
            "{} file not found. Using default values",
            HP_CONFIG_FILE_NAME
        ),
    };
}

/// Parse the buffer to a HPMessage struct
pub fn parse_buffer_to_struct(buffer: SerialBuffer) -> HPMessage {
    let test: HPMessage;
    let arr: [u8; 18] = buffer.as_slice().try_into().unwrap();
    unsafe {
        test = transmute::<[u8; size_of::<HPMessage>()], HPMessage>(arr);
    }
    test
}

/// According to the protocol (documentation's first page), the checksum is calculated as follows:
pub fn calculate_check_sum_up(buffer: &SerialBuffer) -> u8 {
    let temp = buffer[1..16].to_vec();
    let mut sum_odd: u16 = 0;
    let mut sum_even: u16 = 0;
    for (index, value) in temp.iter().enumerate() {
        // add to odd to even and vice versa as first element was removed
        if index % 2 == 0 {
            sum_odd += *value as u16;
        } else {
            sum_even += *value as u16;
        }
    }
    ((sum_odd & 0x0f) as u8) | (((sum_even << 4) & 0x0f) as u8)
}

/// Check if the message has correct format
fn has_hp_message_valid_format(buffer: &SerialBuffer) -> bool {
    buffer.len() == SERIAL_BUFFER_SIZE
        && *(buffer.first().unwrap_or(&0)) == START_BYTE
        && *(buffer.last().unwrap_or(&0)) == END_BYTE
        && calculate_check_sum_up(buffer) == buffer[16]
}

/// Assign received values to the heat pump struct in the app state
fn proccess_first_frame(msg: &HPMessage) {
    let mut lock = APP_STATE.lock().unwrap();
    // TODO mode
    lock.hp_state.mode = Some(msg.state_1);
    lock.hp_state.compressor_frequency = Some(msg.state_2);
    lock.hp_state.faults.error_1 = parse_byte_to_bits(&msg.state_3, HashSet::from(FAULT_CODES_1));
    lock.hp_state.faults.error_2 = parse_byte_to_bits(&msg.state_4, HashSet::from(FAULT_CODES_2));
    lock.hp_state.machine_current_value = Some(msg.state_5);
    lock.hp_state.water_temperature = Some(msg.state_6);
    lock.hp_state.flags.status_1 =
        parse_byte_to_bits(&msg.state_7, HashSet::from(STATUS_FLAG_CODES_1));
    lock.hp_state.electric_heating = evaluate_electric_heating_flags(&msg.state_8);
    lock.hp_state.water_tank_temperature = Some(msg.state_9);
    lock.hp_state.water_supply_pipe_temperature = Some(msg.state_11);
    lock.hp_state.solar_collector_temperature = Some(msg.state_12);
    lock.hp_state.faults.error_3 = parse_byte_to_bits(&msg.state_13, HashSet::from(FAULT_CODES_3));
    lock.hp_state.outdoor_ambient_temperature = Some(((msg.state_14 as f32) - 60.0) / 2.0);
}
/// Assign received values to the heat pump struct in the app state
fn proccess_second_frame(msg: &HPMessage) {
    let mut lock = APP_STATE.lock().unwrap();
    lock.hp_state.electronic_expansion_valve_opening_degree = Some(msg.state_1);
    lock.hp_state.outdoor_coil_temperature = Some(((msg.state_2 as f32) - 60.0) / 2.0);
    lock.hp_state.exhaust_gas_temperature = Some(msg.state_3);
    lock.hp_state.return_air_temperature = Some(((msg.state_6 as f32) - 30.0) / 2.0);
    lock.hp_state.throttling_temperature = Some(((msg.state_7 as f32) - 30.0) / 2.0);
    lock.hp_state.faults.error_4 = parse_byte_to_bits(&msg.state_8, HashSet::from(FAULT_CODES_4));
    lock.hp_state.flags.status_2 =
        parse_byte_to_bits(&msg.state_9, HashSet::from(STATUS_FLAG_CODES_2));
}

/// Check and process the message
pub fn process_hp_message(buffer: SerialBuffer) -> bool {
    if !has_hp_message_valid_format(&buffer) {
        return false;
    }
    let message = parse_buffer_to_struct(buffer);
    match HPReceivingFrame::from_u8(message.function_code) {
        Ok(result) => match result {
            HPReceivingFrame::First => proccess_first_frame(&message),
            HPReceivingFrame::Second => proccess_second_frame(&message),
        },
        Err(_) => return false,
    }
    debug!(
        "Received heat pump data frame (function code: {:#x}) successfully proccessed",
        message.function_code
    );
    true
}

pub fn is_value_valid(value: u8, allowed_values: Vec<u8>) -> bool {
    allowed_values.iter().any(|&x| x == value)
}

/// Save the heat pump configuration to file
pub fn save_hp_config() {
    let config = &APP_STATE.lock().unwrap().hp_config.clone();
    if let Ok(serialized_config) = serde_json::to_string(config) {
        match write_string_to_file(HP_CONFIG_FILE_NAME, serialized_config) {
            Ok(_) => info!("Heat pump config saved to file {}", HP_CONFIG_FILE_NAME),
            Err(err) => error!(
                "Error while saving heat pump config to file {}: {}",
                HP_CONFIG_FILE_NAME, err
            ),
        }
    }
}

/// Send the response from heat pump to the API. After API request is received
/// process the request, process heat pump and send back the response
pub fn send_response_to_api(responder: Option<HeatPumpResponseSender>, response: HeatPumpResponse) {
    if let Some(responder) = responder {
        match responder.send(response) {
            Ok(_) => debug!("Heat pump '{:?}' response sent to API", response.clone()),
            Err(err) => error!("Error while sending heat pump response to API: {:?}", err),
        }
    }
}

/// Check if heat pump connection is still established. If not, update the state
pub fn hp_is_connected(elapsed_time: u64) -> bool {
    if elapsed_time >= configuration::MAXIMUM_RESPONSE_TIME {
        // Didn't receive any message from heat pump in 120 seconds, it means that heat pump is down (update state)
        let mut lock = APP_STATE.lock().unwrap();
        if lock.hp_state.established_connection {
            lock.hp_state.established_connection = false;
            error!("Heat pump connection is down");
        }
        return false;
    }
    true
}

/// Evaluate the electric heating flags according to the documentation and create the status
fn evaluate_electric_heating_flags(flag: &u8) -> ElectricHeating {
    match parse_byte_to_bits(flag, HashSet::from(ELECTRIC_HEATING_FLAG_CODES)) {
        Some(flags) => {
            let temp: HashSet<u8> = flags.into_iter().collect();
            let mut intensity: Option<Intensity> = None;
            let mut fan_speed: Option<Intensity> = None;
            if temp.contains(&1) {
                intensity = Some(Intensity::Low);
            }
            if temp.contains(&2) {
                intensity = Some(Intensity::Medium);
            }
            if temp.contains(&4) {
                intensity = Some(Intensity::High);
            }
            if temp.contains(&128) {
                fan_speed = Some(Intensity::Low);
            }
            if temp.contains(&64) {
                fan_speed = Some(Intensity::Medium);
            }
            if temp.contains(&32) {
                fan_speed = Some(Intensity::High);
            }
            let water_pump = match temp.contains(&16) {
                true => Some(true),
                false => None,
            };
            ElectricHeating::new(intensity.is_some(), fan_speed, intensity, water_pump)
        }
        None => ElectricHeating::default(),
    }
}

/// Check if the temperature is in the range of the heat pump transmission. Temperature after transmission conversion must be in range [0, 255]. Transmission conversion: `temperature * 2 + 60`.
pub fn is_temperature_in_range(temperature: i8) -> bool {
    // temperature * 2 + 60 >= 0 && temperature * 2 + 60 <= 255 is not used because of overflow, but it is the same as:
    temperature * 2 + 60 >= 0 && temperature <= 97
}
