use std::path::PathBuf;
use vhdl_lang::ast::{ArchitectureBody, ConcurrentStatement, DesignFile, Designator, EntityDeclaration, InstantiatedUnit, InterfaceDeclaration, InterfaceList, LabeledConcurrentStatement, Mode, ModeIndication, Name};
use vhdl_lang::{kind_str, HasTokenSpan, Token, TokenAccess, TokenSpan, VHDLParser, VHDLStandard};

use super::hdlparam::*;

pub fn vhdl_parse_str(path: &PathBuf, code: &str) -> Option<DesignFile> {
    let mut diagnostics = Vec::new();
    let parser = VHDLParser::new(VHDLStandard::VHDL2008);
    if let Ok((_, design_file)) = parser.parse_vhdl_str(code, path, &mut diagnostics) {
        return Some(design_file);
    }
    None
}

pub fn make_fast_from_units(
    arch_and_entity: Vec<((ArchitectureBody, Vec<Token>), (EntityDeclaration, Vec<Token>))>,
) -> Option<FastHdlparam> {
    let mut hdlparam = FastHdlparam {
        fast_macro: Macro {
            defines: Vec::new(),
            errors: Vec::new(),
            includes: Vec::new(),
            invalid: Vec::new()
        },
        content: Vec::new(),
        file_type: "common".to_string()
    };

    arch_and_entity.iter().for_each(|((arch, arch_tokens), (entity, entity_tokens))| {
        let name = entity.ident.tree.item.name_utf8();
        let arch_name = arch.ident.tree.item.name_utf8();
        let range = get_range_from_token(
            entity_tokens.get_token(entity.span().get_start_token()),
            entity_tokens.get_token(entity.span().get_end_token())
        );

        hdlparam.new_vhdl_module(name, arch_name, range);

        if let Some(param_list) = &entity.generic_clause {
            parse_interface_list(param_list, &entity_tokens).iter().for_each(|(name, _, net_type, _, init, range)| {
                hdlparam.add_parameter(name, net_type, init, range.clone());
            });
        }

        if let Some(port_list) = &entity.port_clause {
            parse_interface_list(port_list, &entity_tokens).iter().for_each(|(name, dir_type, net_type, width, init, range)| {
                hdlparam.add_port(name, dir_type, net_type, width, range.clone());
            });
        }

        let instances = arch.statements.iter().filter(|statement| {
            match statement.statement.item {
                ConcurrentStatement::Instance(_) => true,
                _ => false
            }
        })
        .map(|statement| parse_instance(statement, arch_tokens))
        .collect::<Vec<Instance>>();

        if let Some(last_module) = hdlparam.content.last_mut() {
            last_module.instances = instances
        }
    });

    Some(hdlparam)
}

fn parse_instance(statement: &LabeledConcurrentStatement, tokens: &Vec<Token>) -> Instance {
    let name = if let Some(tree) = &statement.label.tree { tree.item.name_utf8() } else { "unknown".to_string() };
    let range = get_range_from_token(
        tokens.get_token(statement.span().get_start_token()),
        tokens.get_token(statement.span().get_end_token())
    );

    let mut parsed_instance = Instance {
        name,
        inst_type: "".to_string(),
        instparams: None,
        intstparam_assignments: Vec::new(),
        instports: None,
        intstport_assignments: Vec::new(),
        range
    };

    match &statement.statement.item {
        ConcurrentStatement::Instance(instance) => {
            parsed_instance.inst_type = match &instance.unit {
                InstantiatedUnit::Component(name) => {
                    match &name.item {
                        Name::Designator(designator) => {
                            match &designator.item {
                                Designator::Identifier(symbol) => symbol.name_utf8(),
                                _ => "unknown".to_string()
                            }
                        }
                        _ => "unknown".to_string()
                    }
                },
                _ => "unknown".to_string()
            };

            if let Some(parameter_list) = &instance.generic_map {
                parsed_instance.instparams = Some(get_range_from_token(
                    tokens.get_token(parameter_list.span().get_start_token()),
                    tokens.get_token(parameter_list.span().get_end_token())
                ));

                parsed_instance.intstparam_assignments = parameter_list.list.items.iter().map(|association_ele| {
                    let formal = association_ele.formal.clone().and_then(|formal| 
                        Some(parse_string_from_tokenspan(formal.span(), tokens))
                    ).unwrap_or(
                        "unknown".to_string()
                    );
                    let actual = parse_string_from_tokenspan(association_ele.actual.span(), tokens);
                    let assign_range = if association_ele.formal.is_some() {
                        get_range_from_token(
                            tokens.get_token(association_ele.formal.clone().unwrap().span().get_start_token()),
                            tokens.get_token(association_ele.actual.span().get_end_token())
                        )
                    } else {
                        get_range_from_token(
                            tokens.get_token(association_ele.actual.span().get_start_token()),
                            tokens.get_token(association_ele.actual.span().get_end_token())
                        )
                    };
                    InstParameter { parameter: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
                }).collect::<Vec<InstParameter>>();
            }

            if let Some(port_list) = &instance.port_map {
                parsed_instance.instports = Some(get_range_from_token(
                    tokens.get_token(port_list.span().get_start_token()),
                    tokens.get_token(port_list.span().get_end_token())
                ));
                parsed_instance.intstport_assignments = port_list.list.items.iter().map(|association_ele| {
                    let formal = association_ele.formal.clone().and_then(|formal| 
                        Some(parse_string_from_tokenspan(formal.span(), tokens))
                    ).unwrap_or(
                        "unknown".to_string()
                    );
                    let actual = parse_string_from_tokenspan(association_ele.actual.span(), tokens);
                    let assign_range = if association_ele.formal.is_some() {
                        get_range_from_token(
                            tokens.get_token(association_ele.formal.clone().unwrap().span().get_start_token()),
                            tokens.get_token(association_ele.actual.span().get_end_token())
                        )
                    } else {
                        get_range_from_token(
                            tokens.get_token(association_ele.actual.span().get_start_token()),
                            tokens.get_token(association_ele.actual.span().get_end_token())
                        )
                    };
                    InstPort { port: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
                }).collect::<Vec<InstPort>>();
            }
        }
        _ => ()
    };

    parsed_instance
}

fn parse_interface_list(list: &InterfaceList, tokens: &Vec<Token>) -> Vec<(String, String, String, String, String, Range)> {
    let mut interface_list = Vec::new();
    list.items.iter().for_each(|interface| {
        let range = get_range_from_token(
            tokens.get_token(interface.span().get_start_token()),
            tokens.get_token(interface.span().get_end_token())
        );
        match interface {
            InterfaceDeclaration::Object(object) => {
                let name = object.idents.first().unwrap().tree.item.name_utf8();
                let (dir_type, net_type, width, init) = match &object.mode {
                    ModeIndication::Simple(simple_mode) => {
                        let dir_type = if let Some(mode_token) = &simple_mode.mode {
                            match mode_token.item {
                                Mode::In => "in",
                                Mode::Out => "out",
                                Mode::Buffer | Mode::InOut => "inout",
                                Mode::Linkage => "unknown"
                            }.to_string()
                        } else {
                            "unknown".to_string()
                        };
                        let net_type = simple_mode.subtype_indication.type_mark.item.to_string();
                        let width = if let Some(constraint) = &simple_mode.subtype_indication.constraint {
                            parse_width_from_tokenspan(constraint.span(), tokens)
                        } else {
                            "1".to_string()
                        };
                        let init = if let Some(expression) = &simple_mode.expression {
                            parse_width_from_tokenspan(expression.span(), tokens)
                        } else {
                            "unknown".to_string()
                        };
                        (dir_type, net_type, width, init)
                    }
                    _ => ("unknown".to_string(), "unknown".to_string(), "unknown".to_string(), "unknown".to_string())
                };
                interface_list.push((name, dir_type, net_type, width, init, range));
            }
            _ => ()
        }
    });
    interface_list
} 

fn parse_string_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String {
    span.iter().map(|id| {
        if let Some(token) = tokens.get_token(id) {
            if get_value(token) == "None" {
                kind_str(token.kind).to_string()
            } else {
                get_value(token)
            }
        } else {
            "".to_string()
        }
    }).collect()
}

fn parse_width_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String {
    // skip '(' and ')'
    span.iter().skip(1).take(span.len() - 2).map(|id| {
        if let Some(token) = tokens.get_token(id) {
            if get_value(token) == "None" {
                if kind_str(token.kind) == "downto" || kind_str(token.kind) == "to" {
                    ":".to_string()
                } else {
                    kind_str(token.kind).to_string()
                }
            } else {
                get_value(token)
            }
        } else {
            "".to_string()
        }
    }).collect()
}

fn get_range_from_token(start_token: Option<&Token>, end_token: Option<&Token>) -> Range {
    let start = if let Some(token) = start_token {
        Position { line: token.pos.start().line, character: token.pos.start().character }
    } else {
        Position { line: 0, character: 0 }
    };

    let end = if let Some(token) = end_token {
        Position { line: token.pos.end().line, character: token.pos.end().character }
    } else {
        Position { line: 0, character: 0 }
    };

    Range { start, end }
}

#[allow(unused)]
fn get_value(token: &Token) -> String {
    match &token.value {
        vhdl_lang::Value::Identifier(symbol) => {
            return symbol.name_utf8();
        }
        vhdl_lang::Value::String(latin1_string) => {
            return latin1_string.to_string();
        }
        vhdl_lang::Value::BitString(latin1_string, _) => {
            return latin1_string.to_string().replace("\\", "");
        }
        vhdl_lang::Value::AbstractLiteral(latin1_string, _) => {
            return latin1_string.to_string();
        }
        vhdl_lang::Value::Character(character) => {
            return character.to_string();
        }
        vhdl_lang::Value::Text(latin1_string) => {
            return latin1_string.to_string();
        }
        vhdl_lang::Value::None => {
            return "None".to_string();
        }
    }
}