use std::collections::HashMap;
use std::process::Command;
use std::time::Duration;
use crossbeam_channel::Sender;
use serde_derive::Deserialize;
use crate::blocks::{Block, ConfigBlock, Update};
use crate::config::SharedConfig;
use crate::de::deserialize_duration;
use crate::errors::*;
use crate::formatting::value::Value;
use crate::formatting::FormatTemplate;
use crate::input::{I3BarEvent, MouseButton};
use crate::scheduler::Task;
use crate::util::has_command;
use crate::widgets::{text::TextWidget, I3BarWidget, Spacing, State};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TemperatureScale {
    Celsius,
    Fahrenheit,
}
impl Default for TemperatureScale {
    fn default() -> Self {
        Self::Celsius
    }
}
pub struct Temperature {
    id: usize,
    text: TextWidget,
    output: String,
    collapsed: bool,
    update_interval: Duration,
    scale: TemperatureScale,
    maximum_good: i64,
    maximum_idle: i64,
    maximum_info: i64,
    maximum_warning: i64,
    format: FormatTemplate,
    chip: Option<String>,
    inputs: Option<Vec<String>>,
    fallback_required: bool,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(deny_unknown_fields, default)]
pub struct TemperatureConfig {
    
    #[serde(deserialize_with = "deserialize_duration")]
    pub interval: Duration,
    
    pub collapsed: bool,
    
    #[serde(default)]
    pub scale: TemperatureScale,
    
    #[serde(default)]
    pub good: Option<i64>,
    
    #[serde(default)]
    pub idle: Option<i64>,
    
    #[serde(default)]
    pub info: Option<i64>,
    
    #[serde(default)]
    pub warning: Option<i64>,
    
    pub format: String,
    
    pub chip: Option<String>,
    
    pub inputs: Option<Vec<String>>,
}
impl Default for TemperatureConfig {
    fn default() -> Self {
        Self {
            format: "{average} avg, {max} max".to_string(),
            interval: Duration::from_secs(5),
            collapsed: true,
            scale: TemperatureScale::default(),
            good: None,
            idle: None,
            info: None,
            warning: None,
            chip: None,
            inputs: None,
        }
    }
}
impl ConfigBlock for Temperature {
    type Config = TemperatureConfig;
    fn new(
        id: usize,
        block_config: Self::Config,
        shared_config: SharedConfig,
        _tx_update_request: Sender<Task>,
    ) -> Result<Self> {
        Ok(Temperature {
            id,
            update_interval: block_config.interval,
            text: TextWidget::new(id, 0, shared_config)
                .with_icon("thermometer")?
                .with_spacing(if block_config.collapsed {
                    Spacing::Hidden
                } else {
                    Spacing::Normal
                }),
            output: String::new(),
            collapsed: block_config.collapsed,
            scale: block_config.scale,
            maximum_good: block_config
                .good
                .unwrap_or_else(|| match block_config.scale {
                    TemperatureScale::Celsius => 20,
                    TemperatureScale::Fahrenheit => 68,
                }),
            maximum_idle: block_config
                .idle
                .unwrap_or_else(|| match block_config.scale {
                    TemperatureScale::Celsius => 45,
                    TemperatureScale::Fahrenheit => 113,
                }),
            maximum_info: block_config
                .info
                .unwrap_or_else(|| match block_config.scale {
                    TemperatureScale::Celsius => 60,
                    TemperatureScale::Fahrenheit => 140,
                }),
            maximum_warning: block_config
                .warning
                .unwrap_or_else(|| match block_config.scale {
                    TemperatureScale::Celsius => 80,
                    TemperatureScale::Fahrenheit => 176,
                }),
            format: FormatTemplate::from_string(&block_config.format)
                .block_error("temperature", "Invalid format specified for temperature")?,
            chip: block_config.chip,
            inputs: block_config.inputs,
            fallback_required: !has_command("temperature", "sensors -j").unwrap_or(false),
        })
    }
}
type SensorsOutput = HashMap<String, HashMap<String, serde_json::Value>>;
type InputReadings = HashMap<String, f64>;
impl Block for Temperature {
    fn update(&mut self) -> Result<Option<Update>> {
        let mut args = if self.fallback_required {
            vec!["-u"]
        } else {
            vec!["-j"]
        };
        if let TemperatureScale::Fahrenheit = self.scale {
            args.push("-f");
        }
        if let Some(ref chip) = &self.chip {
            args.push(chip);
        }
        let output = Command::new("sensors")
            .args(&args)
            .output()
            .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
            .unwrap_or_else(|e| e.to_string());
        let mut temperatures: Vec<i64> = Vec::new();
        if self.fallback_required {
            for line in output.lines() {
                if let Some(rest) = line.strip_prefix("  temp") {
                    let rest = rest
                        .split('_')
                        .flat_map(|x| x.split(' '))
                        .flat_map(|x| x.split('.'))
                        .collect::<Vec<_>>();
                    if rest[1].starts_with("input") {
                        match rest[2].parse::<i64>() {
                            Ok(t) if t == 0 => Ok(()),
                            Ok(t) if t > -101 && t < 151 => {
                                temperatures.push(t);
                                Ok(())
                            }
                            Ok(t) => {
                                
                                eprintln!("Temperature ({}) outside of range ([-100, 150])", t);
                                Ok(())
                            }
                            Err(_) => Err(BlockError(
                                "temperature".to_owned(),
                                "failed to parse temperature as an integer".to_owned(),
                            )),
                        }?
                    }
                }
            }
        } else {
            let parsed: SensorsOutput = serde_json::from_str(&output)
                .block_error("temperature", "sensors output is invalid")?;
            for (_chip, inputs) in parsed {
                for (input_name, input_values) in inputs {
                    if let Some(ref whitelist) = self.inputs {
                        if !whitelist.contains(&input_name) {
                            continue;
                        }
                    }
                    let values_parsed: InputReadings = match serde_json::from_value(input_values) {
                        Ok(values) => values,
                        Err(_) => continue, 
                    };
                    for (value_name, value) in values_parsed {
                        if !value_name.starts_with("temp") || !value_name.ends_with("input") {
                            continue;
                        }
                        if value > -101f64 && value < 151f64 {
                            temperatures.push(value as i64);
                        } else {
                            
                            eprintln!("Temperature ({}) outside of range ([-100, 150])", value);
                        }
                    }
                }
            }
        }
        if !temperatures.is_empty() {
            let max: i64 = *temperatures
                .iter()
                .max()
                .block_error("temperature", "failed to get max temperature")?;
            let min: i64 = *temperatures
                .iter()
                .min()
                .block_error("temperature", "failed to get min temperature")?;
            let avg: i64 = (temperatures.iter().sum::<i64>() as f64 / temperatures.len() as f64)
                .round() as i64;
            let values = map!(
                "average" => Value::from_integer(avg).degrees(),
                "min" => Value::from_integer(min).degrees(),
                "max" => Value::from_integer(max).degrees()
            );
            self.output = self.format.render(&values)?;
            if !self.collapsed {
                self.text.set_text(self.output.clone());
            }
            let state = match max {
                m if m <= self.maximum_good => State::Good,
                m if m <= self.maximum_idle => State::Idle,
                m if m <= self.maximum_info => State::Info,
                m if m <= self.maximum_warning => State::Warning,
                _ => State::Critical,
            };
            self.text.set_state(state);
        }
        Ok(Some(self.update_interval.into()))
    }
    fn view(&self) -> Vec<&dyn I3BarWidget> {
        vec![&self.text]
    }
    fn click(&mut self, e: &I3BarEvent) -> Result<()> {
        if e.button == MouseButton::Left {
            self.collapsed = !self.collapsed;
            if self.collapsed {
                self.text.set_text(String::new());
                self.text.set_spacing(Spacing::Hidden);
            } else {
                self.text.set_text(self.output.clone());
                self.text.set_spacing(Spacing::Normal);
            }
        }
        Ok(())
    }
    fn id(&self) -> usize {
        self.id
    }
}