use std::fmt;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

use crate::antlr::hamelinparser::{
    IdentifierContextAll, IdentifierContextAttrs, SimpleIdentifierContextAll,
};
use crate::err::{TranslationError, TranslationErrors};
use crate::{parse_identifier, parse_simple_identifier};
use antlr_rust::tree::ParseTree;
use derive_more::{From, TryUnwrap};
use std::rc::Rc;

#[derive(Clone, Debug, PartialEq, Hash, Eq, From, TryUnwrap)]
pub enum Identifier {
    Simple(SimpleIdentifier),
    Compound(CompoundIdentifier),
}

impl Identifier {
    pub fn to_hamelin(&self) -> String {
        match self {
            Identifier::Simple(simple) => simple.to_hamelin(),
            Identifier::Compound(compound) => compound.to_hamelin(),
        }
    }

    pub fn simples(&self) -> Vec<SimpleIdentifier> {
        match self {
            Identifier::Simple(simple) => vec![simple.clone()],
            Identifier::Compound(compound) => compound.simples.clone(),
        }
    }

    pub fn prefix_from_simples(&self, prefix: &[SimpleIdentifier]) -> CompoundIdentifier {
        CompoundIdentifier::new(
            prefix
                .iter()
                .chain(self.simples().iter())
                .cloned()
                .collect(),
        )
    }

    pub fn from_simples(simples: Vec<SimpleIdentifier>) -> Option<Identifier> {
        if simples.is_empty() {
            None
        } else if simples.len() == 1 {
            Some(simples[0].clone().into())
        } else {
            Some(CompoundIdentifier::new(simples).into())
        }
    }

    pub fn prefix_from_strings(&self, prefix: &[&str]) -> CompoundIdentifier {
        CompoundIdentifier::new(
            prefix
                .iter()
                .map(|s| SimpleIdentifier::new(s))
                .chain(self.simples().into_iter())
                .collect(),
        )
    }

    pub fn prefix(&self) -> &[SimpleIdentifier] {
        match self {
            Identifier::Simple(_) => &[],
            Identifier::Compound(compound) => {
                compound.simples[0..compound.simples.len() - 1].as_ref()
            }
        }
    }

    pub fn last(&self) -> &SimpleIdentifier {
        match self {
            Identifier::Simple(simple) => simple,
            Identifier::Compound(compound) => compound.simples.last().unwrap(),
        }
    }

    pub fn first(&self) -> &SimpleIdentifier {
        match self {
            Identifier::Simple(simple) => simple,
            Identifier::Compound(compound) => compound.first(),
        }
    }

    pub fn rest(&self) -> &[SimpleIdentifier] {
        match self {
            Identifier::Simple(_) => &[],
            Identifier::Compound(compound) => &compound.simples[1..],
        }
    }

    pub fn fmt_indented(
        &self,
        f: &mut std::fmt::Formatter<'_>,
        indentation: usize,
    ) -> std::fmt::Result {
        match self {
            Identifier::Simple(simple) => simple.fmt_indented(f, indentation),
            Identifier::Compound(compound) => compound.fmt_indented(f, indentation),
        }
    }

    pub fn prepend(self, id: Identifier) -> CompoundIdentifier {
        CompoundIdentifier::from_idents(&[id, self.clone().into()])
    }
}

impl Display for Identifier {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.fmt_indented(f, 0)
    }
}

impl FromStr for Identifier {
    type Err = TranslationErrors;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        HamelinIdentifier::new(parse_identifier(s.to_string())?).to_sql()
    }
}

impl From<crate::tree::ast::identifier::Identifier> for Identifier {
    fn from(ast_ident: crate::tree::ast::identifier::Identifier) -> Self {
        match ast_ident {
            crate::tree::ast::identifier::Identifier::Simple(simple) => {
                Identifier::Simple(simple.into())
            }
            crate::tree::ast::identifier::Identifier::Compound(compound) => {
                Identifier::Compound(compound.into())
            }
        }
    }
}

impl From<crate::tree::ast::identifier::SimpleIdentifier> for Identifier {
    fn from(ast_ident: crate::tree::ast::identifier::SimpleIdentifier) -> Self {
        Identifier::Simple(ast_ident.into())
    }
}

impl From<crate::tree::ast::identifier::CompoundIdentifier> for Identifier {
    fn from(ast_ident: crate::tree::ast::identifier::CompoundIdentifier) -> Self {
        Identifier::Compound(ast_ident.into())
    }
}

#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub struct SimpleIdentifier {
    pub name: String,
}

impl SimpleIdentifier {
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
        }
    }

    pub fn from_parse_tree(tree: &SimpleIdentifierContextAll) -> Result<Self, TranslationError> {
        match tree {
            SimpleIdentifierContextAll::BackQuotedIdentifierContext(ctx) => {
                let text = ctx
                    .get_text()
                    .get(1..ctx.get_text().len() - 1)
                    .unwrap()
                    .replace("``", "`");
                Ok(Self::new(&text))
            }
            SimpleIdentifierContextAll::UnquotedIdentifierContext(ctx) => {
                Ok(Self::new(&ctx.get_text()))
            }
            SimpleIdentifierContextAll::Error(ctx) => {
                Err(TranslationError::msg(ctx, "parse_error"))
            }
        }
    }

    pub fn is_clean(&self) -> bool {
        match parse_simple_identifier(self.name.clone()) {
            Ok(tree) => match tree.as_ref() {
                SimpleIdentifierContextAll::BackQuotedIdentifierContext(_) => true,
                SimpleIdentifierContextAll::UnquotedIdentifierContext(_) => true,
                SimpleIdentifierContextAll::Error(_) => false,
            },
            Err(_) => false,
        }
    }

    pub fn to_hamelin(&self) -> String {
        if self.is_clean() {
            self.name.clone()
        } else {
            format!("`{}`", self.name.replace("`", "``"))
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: usize) -> std::fmt::Result {
        write!(f, "\"{}\"", self.name.replace("\"", "\"\""))
    }
}

// Conversions from tree AST identifiers to SQL identifiers
impl From<crate::tree::ast::identifier::SimpleIdentifier> for SimpleIdentifier {
    fn from(ast_ident: crate::tree::ast::identifier::SimpleIdentifier) -> Self {
        SimpleIdentifier::new(ast_ident.as_str())
    }
}

impl Display for SimpleIdentifier {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.fmt_indented(f, 0)
    }
}

impl From<SimpleIdentifier> for String {
    fn from(ident: SimpleIdentifier) -> Self {
        ident.name
    }
}

impl FromStr for SimpleIdentifier {
    type Err = TranslationErrors;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        HamelinSimpleIdentifier::new(parse_simple_identifier(s.to_string())?).to_sql()
    }
}

#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub struct CompoundIdentifier {
    pub simples: Vec<SimpleIdentifier>,
}

impl CompoundIdentifier {
    pub fn new(simples: Vec<SimpleIdentifier>) -> Self {
        if simples.len() < 2 {
            panic!("CompoundIdentifier must have at least two elements.");
        }
        Self { simples }
    }

    pub fn to_hamelin(&self) -> String {
        self.simples
            .iter()
            .map(|s| s.to_hamelin())
            .collect::<Vec<String>>()
            .join(".")
    }

    pub fn from_idents(idents: &[Identifier]) -> Self {
        let simples = idents
            .iter()
            .flat_map(|id| match id {
                Identifier::Simple(simple) => vec![simple.clone()],
                Identifier::Compound(compound) => compound.simples.clone(),
            })
            .collect();
        Self::new(simples)
    }

    pub fn from_two(one: &str, two: &str) -> Self {
        Self {
            simples: vec![SimpleIdentifier::new(one), SimpleIdentifier::new(two)],
        }
    }

    pub fn first(&self) -> &SimpleIdentifier {
        self.simples.first().unwrap()
    }

    pub fn rest(&self) -> Identifier {
        if self.simples.len() == 2 {
            Identifier::Simple(self.simples[1].clone())
        } else {
            Identifier::Compound(CompoundIdentifier {
                simples: self.simples[1..].to_vec(),
            })
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> std::fmt::Result {
        for (i, simple) in self.simples.iter().enumerate() {
            simple.fmt_indented(f, indentation)?;
            if i < self.simples.len() - 1 {
                write!(f, ".")?;
            }
        }
        Ok(())
    }
}

impl Display for CompoundIdentifier {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.fmt_indented(f, 0)
    }
}

impl From<crate::tree::ast::identifier::CompoundIdentifier> for CompoundIdentifier {
    fn from(ast_ident: crate::tree::ast::identifier::CompoundIdentifier) -> Self {
        CompoundIdentifier::new(ast_ident.parts.into_iter().map(|p| p.into()).collect())
    }
}

// Need to keep this part of the "old AST" around until we figure out how to kill the SQL identifiers from the Type tree.
pub struct HamelinIdentifier {
    pub ctx: Rc<IdentifierContextAll<'static>>,
}

impl HamelinIdentifier {
    pub fn new(ctx: Rc<IdentifierContextAll<'static>>) -> Self {
        Self { ctx }
    }

    pub fn to_sql(&self) -> Result<Identifier, TranslationErrors> {
        let idents = TranslationErrors::from_vec(
            self.ctx
                .simpleIdentifier_all()
                .iter()
                .map(|si| HamelinSimpleIdentifier::new(si.clone()).to_sql())
                .collect(),
        )?;
        if idents.len() == 1 {
            Ok(idents[0].clone().into())
        } else if idents.is_empty() {
            Err(TranslationError::msg(self.ctx.as_ref(), "expected identifier").single())
        } else {
            let idents = idents
                .iter()
                .map(|si| si.clone().into())
                .collect::<Vec<Identifier>>();
            Ok(CompoundIdentifier::from_idents(&idents[..]).into())
        }
    }
}

pub struct HamelinSimpleIdentifier {
    pub ctx: Rc<SimpleIdentifierContextAll<'static>>,
}

impl HamelinSimpleIdentifier {
    pub fn new(ctx: Rc<SimpleIdentifierContextAll<'static>>) -> Self {
        Self { ctx }
    }

    pub fn to_sql(&self) -> Result<SimpleIdentifier, TranslationErrors> {
        SimpleIdentifier::from_parse_tree(self.ctx.as_ref()).map_err(|x| x.into())
    }

    pub fn get_tuple_index(&self) -> Result<usize, TranslationError> {
        let ctx = self.ctx.as_ref();
        let text = ctx.get_text();
        let msg = "Bad tuple reference. Must start with f and be followed by a number.";
        if !text.starts_with("f") {
            return Err(TranslationError::msg(ctx, msg));
        }
        text.get(1..)
            .ok_or_else(|| TranslationError::msg(ctx, msg))
            .and_then(|t| {
                t.parse::<usize>()
                    .map_err(|_| TranslationError::msg(ctx, msg))
            })
    }
}

#[cfg(test)]
mod test {
    use rstest::rstest;

    use super::*;

    #[rstest]
    #[case("identifier", "identifier")]
    #[case("`let`", "let")]
    #[case("`let.foob`", "let.foob")]
    pub fn test_simple_identifiers(#[case] hamelin: String, #[case] expected: String) {
        // First test that they parse as "identifiers"
        match HamelinIdentifier::new(parse_identifier(hamelin.clone()).unwrap())
            .to_sql()
            .unwrap()
        {
            Identifier::Simple(simple_identifier) => {
                assert_eq!(simple_identifier.name, expected)
            }
            Identifier::Compound(_) => panic!("should have been simple"),
        }

        // Then test that they parse as "simple identifiers"
        assert_eq!(
            HamelinSimpleIdentifier::new(parse_simple_identifier(hamelin).unwrap())
                .to_sql()
                .unwrap(),
            SimpleIdentifier::new(expected.as_str())
        );
    }

    #[rstest]
    #[case("one.two", vec!["one", "two"])]
    #[case("`let`.two", vec!["let", "two"])]
    #[case("`let`.`select`.`two.three`.four", vec!["let", "select", "two.three", "four"])]
    pub fn test_compound_identifiers(#[case] hamelin: String, #[case] expected: Vec<&str>) {
        // First test that they parse as "identifiers"
        match HamelinIdentifier::new(parse_identifier(hamelin.clone()).unwrap())
            .to_sql()
            .unwrap()
        {
            Identifier::Simple(_) => panic!("should have been compound"),
            Identifier::Compound(compound_identifier) => {
                assert_eq!(
                    compound_identifier
                        .simples
                        .into_iter()
                        .map(|s| s.name)
                        .collect::<Vec<_>>(),
                    expected
                )
            }
        }
    }

    #[rstest]
    #[case("one.two")]
    #[case("select")]
    pub fn test_simple_identifier_errors(#[case] hamelin: String) {
        assert!(parse_simple_identifier(hamelin).is_err());
    }

    #[rstest]
    #[case("select")]
    pub fn test_identifier_errors(#[case] hamelin: String) {
        assert!(parse_simple_identifier(hamelin).is_err());
    }
}
