use anyhow::Result;
use std::{borrow::Cow, collections::HashMap, fmt::Display};

use crate::{
    dic::{FieldDictionary, Version},
    tokenizer::{self},
};

#[derive(Debug, PartialEq, Eq)]
pub struct MessageField {
    pub translated_tag: String,
    pub raw_tag: String,
    pub translated_value: String,
    pub raw_value: String,
}

impl MessageField {
    fn tag(&self) -> Cow<str> {
        if self.translated_tag != self.raw_tag {
            Cow::from(format!("{} ({})", &self.translated_tag, &self.raw_tag))
        } else {
            Cow::from(&self.translated_tag)
        }
    }

    fn value(&self) -> Cow<str> {
        if self.translated_value != self.raw_value {
            Cow::from(format!("{} ({})", &self.translated_value, &self.raw_value))
        } else {
            Cow::from(&self.translated_value)
        }
    }
}

impl Display for MessageField {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:<25}: {}", self.tag(), self.value())
    }
}

#[derive(Debug)]
pub struct FixMessage {
    pub fields: Vec<MessageField>,
}

impl Display for FixMessage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut field_lines = vec![];
        let mut msg_type = "unknown";
        let mut sender = "unknown";
        let mut target = "unknown";
        let mut sending_time = "unknown";
        for f in &self.fields {
            match f.translated_tag.as_str() {
                "MsgType" => {
                    msg_type = &f.translated_value;
                }
                "SenderCompID" => {
                    sender = &f.translated_value;
                }
                "TargetCompID" => {
                    target = &f.translated_value;
                }
                "SendingTime" => {
                    sending_time = &f.translated_value;
                }
                _ => {}
            }
            field_lines.push(format!("  {}", f.to_string()));
        }
        let summary = format!(
            "[{}] {} -> {} at {}",
            msg_type, sender, target, sending_time
        );
        write!(f, "{}\n{}", summary, &field_lines.join("\n"))
    }
}

#[derive(Debug, Default)]
pub struct Parser {
    dic_cache: HashMap<String, FieldDictionary>,
}

impl Parser {
    pub fn new() -> Parser {
        Parser {
            dic_cache: HashMap::new(),
        }
    }

    pub fn parse(&mut self, s: &str) -> Result<Vec<FixMessage>> {
        let mut v = vec![];
        for line in s.lines() {
            if line.trim().is_empty() {
                continue;
            }
            let tokenized = tokenizer::tokenize(line)?;
            let key = tokenized.version;
            if !self.dic_cache.contains_key(tokenized.version) {
                let dic = FieldDictionary::load_xml(&Version::from_str(key)?)?;
                self.dic_cache.insert(key.to_owned(), dic);
            }
            // self.dic_cache is ensured to have the key by the insertion above.
            let dic = self.dic_cache.get(key).unwrap();
            let fields = tokenized
                .tag_values
                .iter()
                .map(|(tag, value)| {
                    let (translated_tag, translated_value) = dic.translate(tag, value);
                    MessageField {
                        translated_tag,
                        translated_value,
                        raw_tag: tag.to_string(),
                        raw_value: value.to_string(),
                    }
                })
                .collect();
            v.push(FixMessage { fields });
        }
        Ok(v)
    }
}

#[cfg(test)]
mod tests {
    use std::time::Instant;

    use super::*;

    #[test]
    fn parse_test() {
        let mut parser = Parser::new();
        let message = "pre 8=FIX.4.1|9=154|35=6|49=BRKR|56=INVMGR|10=159 suffix\npre 8=FIX.4.1|9=154|35=6|49=BRKR2|56=INVMGR2|10=159 suffix";
        let v = parser.parse(message).unwrap();
        let expected1 = vec![
            MessageField {
                raw_tag: "8".to_string(),
                raw_value: "FIX.4.1".to_string(),
                translated_tag: "BeginString".to_string(),
                translated_value: "FIX.4.1".to_string(),
            },
            MessageField {
                raw_tag: "9".to_string(),
                raw_value: "154".to_string(),
                translated_tag: "BodyLength".to_string(),
                translated_value: "154".to_string(),
            },
            MessageField {
                raw_tag: "35".to_string(),
                raw_value: "6".to_string(),
                translated_tag: "MsgType".to_string(),
                translated_value: "INDICATION_OF_INTEREST".to_string(),
            },
            MessageField {
                raw_tag: "49".to_string(),
                raw_value: "BRKR".to_string(),
                translated_tag: "SenderCompID".to_string(),
                translated_value: "BRKR".to_string(),
            },
            MessageField {
                raw_tag: "56".to_string(),
                raw_value: "INVMGR".to_string(),
                translated_tag: "TargetCompID".to_string(),
                translated_value: "INVMGR".to_string(),
            },
        ];
        for (i, field) in v.get(0).unwrap().fields.iter().enumerate() {
            assert_eq!(field, expected1.get(i).unwrap());
        }
    }

    #[test]
    fn perf_test() {
        let mut parser = Parser::new();
        let messages = (0..10000)
            .map(|i| {
                format!(
                    "pre 8=FIX.4.1|9=154|35=6|49=BRKR{}|56=INVMGR{}|10=159 suffix",
                    i, i
                )
            })
            .collect::<Vec<String>>();
        let now = Instant::now();
        let v = parser.parse(messages.join("\n").as_str()).unwrap();
        println!("took {} ms", now.elapsed().as_millis());
        let expected = |i| {
            vec![
                MessageField {
                    raw_tag: "8".to_string(),
                    raw_value: "FIX.4.1".to_string(),
                    translated_tag: "BeginString".to_string(),
                    translated_value: "FIX.4.1".to_string(),
                },
                MessageField {
                    raw_tag: "9".to_string(),
                    raw_value: "154".to_string(),
                    translated_tag: "BodyLength".to_string(),
                    translated_value: "154".to_string(),
                },
                MessageField {
                    raw_tag: "35".to_string(),
                    raw_value: "6".to_string(),
                    translated_tag: "MsgType".to_string(),
                    translated_value: "INDICATION_OF_INTEREST".to_string(),
                },
                MessageField {
                    raw_tag: "49".to_string(),
                    raw_value: format!("BRKR{}", i),
                    translated_tag: "SenderCompID".to_string(),
                    translated_value: format!("BRKR{}", i),
                },
                MessageField {
                    raw_tag: "56".to_string(),
                    raw_value: format!("INVMGR{}", i),
                    translated_tag: "TargetCompID".to_string(),
                    translated_value: format!("INVMGR{}", i),
                },
            ]
        };
        for (i, msg) in v.iter().enumerate() {
            for (j, field) in msg.fields.iter().enumerate() {
                assert_eq!(field, expected(i).get(j).unwrap());
            }
        }
    }

    #[test]
    fn string_repr() {
        let mut parser = Parser::new();
        let message = "pre 8=FIX.4.1|9=154|35=6|49=BRKR|56=INVMGR|10=159 suffix\npre 8=FIX.4.1|9=154|35=6|49=BRKR2|56=INVMGR2|10=159 suffix";
        let v = parser.parse(message).unwrap();
        for x in v {
            println!("{}\n", x);
        }
    }
}
