Code to specify how to present time-series and Phillip's diagram graphics from FRED data.
//! Functions specific to selecting Fred data.

use std::convert::TryInto;
use std::fs;

use countries::Country;
use fred_api::Fred;

use keytree::{
    KeyTree,
    KeyTreeRef,
};

use crate::{
    DataSpec,
    DataType,
    SeriesId,
    SeriesSpec,
};
use crate::parser::{ FredSpec, ParserSpec };

use crate::error::*;

/// Return all the countries with good data as a `Vec`.
pub fn countries_with_data() -> Vec<Country> {
    vec!(
        Country::Australia,
        Country::Austria,
        Country::Belgium,
        Country::Canada,
        Country::Chile,
        Country::CzechRepublic,
        Country::Denmark,
        Country::Estonia,
        Country::Finland,
        Country::France,
        Country::Germany,
        Country::Greece,
        Country::Ireland,
        Country::Israel,
        Country::Italy,
        Country::Japan,
        Country::Latvia,
        Country::Netherlands,
        Country::NewZealand,
        Country::Norway,
        Country::Poland,
        Country::Serbia,
        Country::SouthKorea,
        Country::Spain,
        Country::Sweden,
        Country::Switzerland,
        Country::UnitedKingdom,
        Country::UnitedStates,
    )
}

/// To use countries in Fred tags, some adjustments need to be made over standard country names.
pub fn fred_country(country: Country) -> String {
    match country {
        Country::SouthKorea => "korea".into(),
        Country::UnitedStates => "usa".into(),
        _ => country.to_string().to_lowercase(),
    }
}

/// A specification of what tags and filters to use to select Fred series, which are then
/// converted into a generic data specification.
pub struct DataSelector(Vec<TagSelector>);

impl DataSelector {

    /// Build `DataSelector` from file.
    pub fn from_file(path: &str) -> Self {
        let data_selector = fs::read_to_string(path).unwrap();
        let kt = KeyTree::parse(&data_selector).unwrap();
        kt.to_ref().try_into().unwrap()
    }

    /// Takes a `DataSelector` and returns a `DataSpec`.
    pub fn into_data_spec(&self) -> Result<DataSpec, Error> {

        let mut data_spec = DataSpec::new();

        for tag_selector in &self.0 {

            println!("");
            println!("{} {}", tag_selector.country, tag_selector.data_type);


            let series_items = match Fred::tags_series(&tag_selector.tag()) {
                Ok(tags_series) => {
                    tags_series.seriess
                },
                Err(e) => { return Err(err( file!(), line!(), &e.to_string())) },
            };

            for series_item in series_items.iter() {

                let series_id = SeriesId::new(&series_item.id.clone());

                if tag_selector.is_selected(series_item) {
                    println!("      {} {}", series_item.id, series_item.title);
                    let series_spec = SeriesSpec {
                        data_type:  tag_selector.data_type,
                        country:    Some(tag_selector.country),
                        series_id:  series_id,
                        transforms: Vec::new(),
                        parser:     ParserSpec::FredSpec(FredSpec::empty()),
                    };
                    println!("      {} {}", series_item.id, series_item.title);
                    data_spec.insert(&series_spec);
                    
                } else {
                    println!("drop: {} {}", series_item.id, series_item.title);
                }
            }
        }
        Ok(data_spec)
    }


    /// Takes a `DataSelector` and returns a `DataSpec`.
    pub fn resume_into_data_spec(&self, country: Country, data_type: DataType) -> Result<DataSpec, Error> {

        let mut data_spec = DataSpec::new();

        for tag_selector in self.0.iter().skip_while(|tag_selector| {
            tag_selector.country != country|| 
            tag_selector.data_type != data_type
        }) {
        
            println!("{} {}", tag_selector.country, tag_selector.data_type);


            let series_items = match Fred::tags_series(&tag_selector.tag()) {
                Ok(tags_series) => {
                    tags_series.seriess
                },
                Err(e) => { return Err(err(file!(), line!(), &e.to_string())) },
            };

            for series_item in series_items.iter() {

                let series_id = SeriesId::new(&series_item.id.clone());

                if tag_selector.is_selected(series_item) {
                    println!("    {} {}", series_item.id, series_item.title);
                    let series_spec = SeriesSpec {
                        country:    Some(tag_selector.country),
                        data_type:  tag_selector.data_type,
                        series_id:  series_id,
                        transforms: Vec::new(),
                        parser:     ParserSpec::FredSpec(FredSpec::empty()),
                    };
                    println!("    {} {}", series_item.id, series_item.title);
                    data_spec.insert(&series_spec);
                    
                }
            }
        }
        Ok(data_spec)
    }
}

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

    fn try_into(self) -> Result<DataSelector, Self::Error> {
        let v: Vec<TagSelector> = self.vec_at("selectors::series")?;
        Ok(DataSelector(v))
    }
}

/// Specification for how to select data series from Fred. A component of `DataSelector`.
#[derive(Debug)]
pub struct TagSelector {
    country:    Country,
    data_type:  DataType,
    tags:       Vec<String>,
    enumerate:  Vec<String>,
    exclude:    Vec<String>,
    require:    Vec<String>,
}

impl TagSelector {

    /// Return a compiled tag from parts.
    pub fn tag(&self) -> String {
        let mut s = String::new();
        for tag in &self.tags {
            s.push_str(&tag.trim());
            s.push(';');
        }
        s.push_str(&fred_country(self.country));
        s
    }

    // /// Return
    // pub fn into_series_items(&self) -> Result<Vec<fred_api::SeriesItem>, Error> {

    //     let mut v: Vec<fred_api::SeriesItem> = Vec::new();

    //     let series_items = match Fred::tags_series(&self.tag()) {
    //         Ok(tags_series) => {
    //             tags_series.seriess
    //         },
    //         Err(err) => { return Err(failed_fred_request(&err.to_string())) },
    //     };

    //     for series_item in series_items.iter() {

    //         if self.is_selected(series_item) {
    //             println!("    {}", series_item.id);
    //             v.push(series_item.clone());
    //         }
    //     }
    //     Ok(v)
    // }

    /// Test if `series_item` is selected.
    pub fn is_selected(&self, series_item: &fred_api::SeriesItem) -> bool {

        let title = &series_item.title.clone();

        // Return false if self.enumerate is not empty and none match.

        if !self.enumerate.is_empty() &&
        !self.enumerate.iter().any(|enum_title| enum_title == title)
        {
            return false
        }

        // Return false if self.exclude is not empty and there is an exclusion

        if !self.exclude.is_empty() &&
        self.exclude.iter().any(|exclusion| title.contains(exclusion))
        {
            return false
        }

        // Return false if self.require is not empty and a requirement is not met

        if !self.require.is_empty() &&
        self.require.iter().any(|requirement| !title.contains(requirement))
        {
            return false
        }

        true
    }
}

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

    fn try_into(self) -> Result<TagSelector, Self::Error> {
        Ok(
            TagSelector {
                country:    self.value("series::country")?,
                data_type:  self.value("series::data_type")?,
                tags:       self.opt_vec_value("series::tag")?,
                enumerate:  self.opt_vec_value("series::enumerate")?,
                exclude:    self.opt_vec_value("series::exclude")?,
                require:    self.opt_vec_value("series::require")?,
            }
        )
    }
}