use crate::{
    compiler::{
        AddressingModel, Capability, EntryPoint, ExecutionMode, ExecutionModel, Memory,
        MemoryModel, expect_list, expect_one_of, expect_symbol,
    },
    parser::{Ast, Localised, Location},
};

use super::{
    BuiltinDecoration, CompilerError, Decoration, Function, GlobalVariable, Import, Module,
    StorageClass, expect_empty, expect_empty_ast,
};

pub fn compile_module<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let _cap = expect_one_of(&["Shader"], expect_symbol(list.next(), &loc)?)?;
    let _exec = expect_one_of(&["Logical"], expect_symbol(list.next(), &loc)?)?;
    let _memory = expect_one_of(&["GLSL450"], expect_symbol(list.next(), &loc)?)?;
    module.memory_model = Memory {
        addressing_model: AddressingModel::Logical,
        memory_model: MemoryModel::GLSL450,
    };
    module.capabilities.push(Capability::Shader);
    expect_empty_ast(list)
}

pub fn compile_import<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let name = expect_symbol(list.next(), &loc)?.into_inner();
    let value = expect_symbol(list.next(), &loc)?.into_inner();
    module.imports.push(Import { name, value });
    expect_empty_ast(list)
}

pub fn compile_bind<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let Localised {
        location: name_and_type_loc,
        item: name_and_type,
    } = expect_list(list.next(), &loc)?;
    let name_and_type = name_and_type
        .into_iter()
        .map(|x| expect_symbol(Some(x), &name_and_type_loc).map(Localised::into_inner))
        .collect::<Result<String, CompilerError>>()?;
    let mut name_and_type = name_and_type.split(':').map(|s| s.to_string());
    // name_and_type is of the name:type format, like foo:f32
    let name: String = name_and_type
        .next()
        .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?;
    let typ: String = name_and_type
        .next()
        .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?;
    expect_empty(name_and_type, name_and_type_loc)?;
    let Localised {
        location: bind_loc,
        item: bind,
    } = expect_list(list.next(), &loc)?;
    let bind: Vec<String> = bind
        .into_iter()
        .map(|x| expect_symbol(Some(x), &bind_loc).map(Localised::into_inner))
        .collect::<Result<_, _>>()?;
    let bind_name = bind
        .get(0)
        .ok_or(CompilerError::ExpectedSymbol(bind_loc.clone()))?;

    let bind_name = match bind_name.as_str() {
        "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord),
        "Location" => Decoration::Location(bind.get(1).unwrap().parse::<u32>().unwrap()),
        b => return Err(CompilerError::UnknownBind(b.to_string(), bind_loc)),
    };
    let mut exists = false;
    for var in &module.globals {
        if var.name == name {
            exists = true;
        }
    }
    if !exists {
        module.globals.push(GlobalVariable {
            name,
            typ,
            storage_class: StorageClass::Undefined,
            decorations: vec![bind_name],
        });
    }
    expect_empty_ast(list)
}

pub fn compile_dec<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let Localised {
        location: name_and_type_loc,
        item: name_and_type,
    } = expect_symbol(list.next(), &loc)?;
    let mut name_and_type = name_and_type.split(':').map(|s| s.to_string());
    let name: String = name_and_type
        .next()
        .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?;
    let typ: String = name_and_type
        .next()
        .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?;
    expect_empty(name_and_type, name_and_type_loc)?;

    let storage_class = expect_symbol(list.next(), &loc)?;
    let mut exists = false;
    for var in &module.globals {
        if var.name.as_str() == name.as_str() {
            exists = true;
        }
    }
    let storage_class = match storage_class.as_str() {
        "Input" => StorageClass::Input,
        "Output" => StorageClass::Output,
        _ => StorageClass::Undefined,
    };
    if !exists {
        module.globals.push(GlobalVariable {
            name: name.to_string(),
            typ: typ.to_string(),
            storage_class: storage_class.clone(),
            decorations: vec![],
        });
    }
    if exists {
        module
            .globals
            .iter_mut()
            .find(|x| x.name.as_str() == name.as_str())
            .unwrap()
            .storage_class = storage_class.clone();
    }
    expect_empty_ast(list)
}

pub fn compile_entry<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let name = expect_symbol(list.next(), &loc)?;
    let _exec_model = expect_one_of(&["Fragment"], expect_symbol(list.next(), &loc)?)?;
    let _exec_mode = expect_one_of(&["OriginUpperLeft"], expect_symbol(list.next(), &loc)?)?;
    let Localised {
        location: interface_loc,
        item: interface,
    } = expect_list(list.next(), &loc)?;
    let interface: Vec<String> = interface
        .into_iter()
        .map(|x| expect_symbol(Some(x), &interface_loc).map(|s| s.into_inner().replace(":", "")))
        .collect::<Result<_, _>>()?;
    module.entry_points.push(EntryPoint {
        execution_model: ExecutionModel::Fragment,
        execution_mode: ExecutionMode::OriginUpperLeft,
        name: name.to_string(),
        interface,
    });

    expect_empty_ast(list)
}

pub fn compile_fun<I: Iterator<Item = Ast>>(
    module: &mut Module,
    mut list: I,
    loc: Location,
) -> Result<(), CompilerError> {
    let Localised {
        location: name_loc,
        item: name,
    } = expect_list(list.next(), &loc)?;
    let mut name_it = name.into_iter();
    let name = expect_symbol(name_it.next(), &name_loc)?;
    expect_empty(name_it, name_loc)?;
    let _return_type = expect_one_of(&[":<>"], expect_symbol(list.next(), &loc)?)?;
    let return_type = _return_type.into_inner().replace(":", "");
    let body = list.collect::<Vec<_>>();
    let location = if let (Some(s), Some(e)) = (
        body.first().map(|a| a.location()),
        body.last().map(|a| a.location()),
    ) {
        Location::range(s, e)
    } else {
        Location::Char { line: 0, col: 0 }
    };
    let fun = Function {
        name: name.to_string(),
        return_type: return_type,
        arguments: vec![],
        body: Some(vec![]),
        ast: Some(Ast::List(Localised {
            location,
            item: body,
        })),
    };
    module.functions.push(fun);

    Ok(())
}