Code to specify how to present time-series and Phillip's diagram graphics from FRED data.
//! Deserialize data to be served as JSON to build Unemployment/Inflation scatter-plots.

use std::collections::BTreeMap;
use std::convert::TryInto;
use std::fs;

use serde::Serialize;

use countries::Country;
use keytree::{
    KeyTree,
    KeyTreeRef,
};
use time_series::{
    RegularTimeSeries,
};

use crate::{ GraphicRange, SeriesId, SeriesSpec, Transform };
use crate::error::*;

#[derive(Debug)]
pub struct UIData(pub BTreeMap<(Country, usize), String>); 

#[derive(Debug, Serialize)]
pub enum Scale {
    Group {
        x_min:  f32,
        x_max:  f32,
        y_min:  f32,
        y_max:  f32,
    },
    Single {
        x_min:  f32,
        x_max:  f32,
        y_min:  f32,
        y_max:  f32,
    },
}

pub struct Json(BTreeMap<(Country, usize), GraphicJson>);

impl Json {
    pub fn into_data(&self) -> Result<UIData, Error> {
        let mut map: BTreeMap<(Country, usize), String> = BTreeMap::new();
        for (key, value) in self.0.iter() {
            let json = serde_json::to_string(&value)
                .map_err(|_| {
                    err(file!(), line!(), &format!("Serialize to JSON failed."))
                
                })?;
            map.insert(*key, json);
        }
        Ok(UIData(map))
    } 
}

#[derive(Debug, Serialize)]
pub struct GraphicJson {
    country:    Country,
    index:      usize,
    lines:      Vec<LineJson>,
    scale:      Scale,
    // Describes the plotted range
    x_range:    Option<GraphicRange>,
    y_range:    Option<GraphicRange>,
}

#[derive(Debug, Serialize)]
pub struct LineJson {
    x_series_id:    SeriesId,
    y_series_id:    SeriesId,
    rts:            RegularTimeSeries<2>,
    x_transforms:   Vec<Transform>,
    y_transforms:   Vec<Transform>,
}

pub struct GraphicBuilder {
    lines:  Vec<LineJson>,
    x_min:  Option<f32>,
    x_max:  Option<f32>,
    y_min:  Option<f32>,
    y_max:  Option<f32>,
}

impl GraphicBuilder {
    // Update if required
    fn x_min(&mut self, x: f32) {
        match self.x_min {
            None        => self.x_min = Some(x),
            Some(old)   => if x < old { self.x_min = Some(x) },
        }
    }

    // Update if required
    fn x_max(&mut self, x: f32) {
        match self.x_max {
            None        => self.x_max = Some(x),
            Some(old)   => if x > old { self.x_max = Some(x) },
        }
    }

    // Update if required
    fn y_min(&mut self, y: f32) {
        match self.y_min {
            None        => self.y_min = Some(y),
            Some(old)   => if y < old { self.y_min = Some(y) },
        }
    }

    // Update if required
    fn y_max(&mut self, y: f32) {
        match self.y_max {
            None        => self.y_max = Some(y),
            Some(old)   => if y > old { self.y_max = Some(y) },
        }
    }

    fn scale(&self) -> Scale {
        Scale::Single {
            x_min: self.x_min.unwrap(),
            x_max: self.x_max.unwrap(),
            y_min: self.y_min.unwrap(),
            y_max: self.y_max.unwrap(),
        }
    }
}

#[derive(Debug)]
pub struct Spec(Vec<GraphicSpec>);



impl Spec {

    /// Read in ts specification from file.
    /// ```
    /// let ts_spec = ts::Spec::from_file("ui_spec.keytree");
    /// ```
    pub fn from_file(path: &str) -> Result<Self, Error> {
        let source_spec = match fs::read_to_string(path) {
            Ok(ss) => ss,
            Err(_) => { return Err(
                err(file!(), line!(), &format!("Failed to read file {}", path))
            )},
        };
        let kt = KeyTree::parse(&source_spec).unwrap();
        let out = kt.to_ref().try_into().map_err(|e: keytree::error::Error| {
            err(file!(), line!(), &e.to_string())
        });
        out
    }

    pub fn into_json(&self, root_path: &str) -> Result<Json, Error> {

        let mut json = BTreeMap::new();

        for GraphicSpec {
            country,
            index,
            seriess,
            lines,
            x_range,
            y_range,
        } in &self.0 {

            let mut graphic_json_builder = GraphicBuilder {
                lines:  Vec::new(),
                x_min:  None,
                x_max:  None,
                y_min:  None,
                y_max:  None,
            };

            for LineSpec {
                x,
                y,
            } in lines { 
                let x_rts = match seriess.get(x) {
                    Some(s) => s.read_data_with_transforms(root_path)?,
                    None => {
                        return Err(err(file!(), line!(), &format! ("Series {} lookup failed.", y)))
                    },
                };
                let x_min = x_rts.min(0);
                let x_max = x_rts.max(0);

                let y_rts = match seriess.get(y) {
                    Some(series_json) => series_json.read_data_with_transforms(root_path)?,
                    None => {
                        return Err(err(file!(), line!(), &format! ("Series {} lookup failed.", y)))
                    },
                };
                let y_min = y_rts.min(0);
                let y_max = y_rts.max(0);

                let rts = x_rts.zip_one_one(y_rts)
                    .map_err(|e| err(file!(), line!(), &e.to_string()))?; 

                let line_json = LineJson {
                    x_series_id:    x.clone(),
                    y_series_id:    y.clone(),
                    rts:            rts,
                    x_transforms:   seriess.get(x).unwrap().transforms.clone(),
                    y_transforms:   seriess.get(y).unwrap().transforms.clone(),
                };
            
                graphic_json_builder.lines.push(line_json);
                graphic_json_builder.x_min(x_min);
                graphic_json_builder.x_max(x_max);
                graphic_json_builder.y_min(y_min);
                graphic_json_builder.y_max(y_max);
            }

            let graphic_json = GraphicJson {
                country:    *country,
                index:      *index,
                scale:      graphic_json_builder.scale(),
                lines:      graphic_json_builder.lines,
                x_range:    *x_range,
                y_range:    *y_range,
            
            };

            json.insert((*country, *index), graphic_json);
        }
        Ok(Json(json))
    }
}

impl<'a> TryInto<Spec> for KeyTreeRef<'a> {
    type Error = keytree::Error;

    fn try_into(self) -> Result<Spec, Self::Error> {
        Ok(
            Spec(self.vec_at("ui_spec::graphic")?)
        )
    }
}

#[derive(Debug)]
pub struct GraphicSpec {
    pub country:    Country,
    pub index:      usize,
    pub seriess:    BTreeMap<SeriesId, SeriesSpec>,
    pub lines:      Vec<LineSpec>,
    pub x_range:    Option<GraphicRange>,
    pub y_range:    Option<GraphicRange>,
}

impl<'a> TryInto<GraphicSpec> for KeyTreeRef<'a> {
    type Error = keytree::Error;

    fn try_into(self) -> Result<GraphicSpec, Self::Error> {

        let v: Vec<SeriesSpec> = self.vec_at("graphic::series")?;
        let mut seriess = BTreeMap::new();
        for series_spec in v {
            seriess.insert(series_spec.series_id.clone(), series_spec);
        };

        Ok(
            GraphicSpec {
                country:    self.value("graphic::country")?,
                index:      self.value("graphic::index")?,
                seriess,
                lines:      self.vec_at("graphic::line")?,
                x_range:    self.opt_value("graphic::x_range")?,
                y_range:    self.opt_value("graphic::y_range")?,
            }
        )
    }
}

#[derive(Debug)]
pub struct LineSpec {
    x:          SeriesId,
    y:          SeriesId,
}

impl<'a> TryInto<LineSpec> for KeyTreeRef<'a> {
    type Error = keytree::Error;

    fn try_into(self) -> Result<LineSpec, Self::Error> {
        Ok(
            LineSpec {
                x: self.value("line::x")?,
                y: self.value("line::y")?,
            }
        )
    }
}