use std::collections::{HashMap, BTreeMap};
use vm::types::{Value, TypeSignature, TupleTypeSignature, PrincipalData, TraitIdentifier, QualifiedContractIdentifier, parse_name_type_pairs};
use vm::types::signatures::FunctionSignature;
use vm::callables::{DefinedFunction, DefineType};
use vm::representations::{SymbolicExpression, ClarityName};
use vm::representations::SymbolicExpressionType::{Atom, AtomValue, List, LiteralValue, Field};
use vm::errors::{RuntimeErrorType, CheckErrors, InterpreterResult as Result, check_argument_count, check_arguments_at_least};
use vm::contexts::{ContractContext, LocalContext, Environment};
use vm::eval;

define_named_enum!(DefineFunctions {
    Constant("define-constant"),
    PrivateFunction("define-private"),
    PublicFunction("define-public"),
    ReadOnlyFunction("define-read-only"),
    Map("define-map"),
    PersistedVariable("define-data-var"),
    FungibleToken("define-fungible-token"),
    NonFungibleToken("define-non-fungible-token"),
    Trait("define-trait"),
    UseTrait("use-trait"),
    ImplTrait("impl-trait"),
});

pub enum DefineFunctionsParsed <'a> {
    Constant { name: &'a ClarityName, value: &'a SymbolicExpression },
    PrivateFunction { signature: &'a [SymbolicExpression], body: &'a SymbolicExpression },
    ReadOnlyFunction { signature: &'a [SymbolicExpression], body: &'a SymbolicExpression },
    PublicFunction { signature: &'a [SymbolicExpression], body: &'a SymbolicExpression },
    NonFungibleToken { name: &'a ClarityName, nft_type: &'a SymbolicExpression },
    BoundedFungibleToken { name: &'a ClarityName, max_supply: &'a SymbolicExpression },
    UnboundedFungibleToken { name: &'a ClarityName },
    Map { name: &'a ClarityName, key_type: &'a SymbolicExpression, value_type: &'a SymbolicExpression },
    PersistedVariable  { name: &'a ClarityName, data_type: &'a SymbolicExpression, initial: &'a SymbolicExpression },
    Trait { name: &'a ClarityName, functions: &'a [SymbolicExpression] },
    UseTrait { name: &'a ClarityName, trait_identifier: &'a TraitIdentifier },
    ImplTrait { trait_identifier: &'a TraitIdentifier },
}

pub enum DefineResult {
    Variable(ClarityName, Value),
    Function(ClarityName, DefinedFunction),
    Map(ClarityName, TupleTypeSignature, TupleTypeSignature),
    PersistedVariable(ClarityName, TypeSignature, Value),
    FungibleToken(ClarityName, Option<u128>),
    NonFungibleAsset(ClarityName, TypeSignature),
    Trait(ClarityName, BTreeMap<ClarityName, FunctionSignature>),
    UseTrait(ClarityName, TraitIdentifier),
    ImplTrait(TraitIdentifier),
    NoDefine
}

fn check_legal_define(name: &str, contract_context: &ContractContext) -> Result<()> {
    if contract_context.is_name_used(name) {
        Err(CheckErrors::NameAlreadyUsed(name.to_string()).into())
    } else {
        Ok(())
    }
}

fn handle_define_variable(variable: &ClarityName, expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
    // is the variable name legal?
    check_legal_define(variable, &env.contract_context)?;
    let context = LocalContext::new();
    let value = eval(expression, env, &context)?;
    Ok(DefineResult::Variable(variable.clone(), value))
}

fn handle_define_function(signature: &[SymbolicExpression],
                          expression: &SymbolicExpression,
                          env: &mut Environment,
                          define_type: DefineType) -> Result<DefineResult> {
    let (function_symbol, arg_symbols) = signature.split_first()
        .ok_or(CheckErrors::DefineFunctionBadSignature)?;

    let function_name = function_symbol.match_atom()
        .ok_or(CheckErrors::ExpectedName)?;

    check_legal_define(&function_name, &env.contract_context)?;

    let arguments = parse_name_type_pairs(arg_symbols, env)?;

    for (argument, _) in arguments.iter() {
        check_legal_define(argument, &env.contract_context)?;
    }

    let function = DefinedFunction::new(
        arguments,
        expression.clone(),
        define_type,
        function_name,
        &env.contract_context.contract_identifier.to_string());

    Ok(DefineResult::Function(function_name.clone(), function))
}

fn handle_define_persisted_variable(variable_str: &ClarityName, value_type: &SymbolicExpression, value: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
    check_legal_define(&variable_str, &env.contract_context)?;

    let value_type_signature = TypeSignature::parse_type_repr(value_type, env)?;

    let context = LocalContext::new();
    let value = eval(value, env, &context)?;

    Ok(DefineResult::PersistedVariable(variable_str.clone(), value_type_signature, value))
}

fn handle_define_nonfungible_asset(asset_name: &ClarityName, key_type: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
    check_legal_define(&asset_name, &env.contract_context)?;

    let key_type_signature = TypeSignature::parse_type_repr(key_type, env)?;

    Ok(DefineResult::NonFungibleAsset(asset_name.clone(), key_type_signature))
}

fn handle_define_fungible_token(asset_name: &ClarityName, total_supply: Option<&SymbolicExpression>, env: &mut Environment) -> Result<DefineResult> {
    check_legal_define(&asset_name, &env.contract_context)?;

    if let Some(total_supply_expr) = total_supply {
        let context = LocalContext::new();
        let total_supply_value = eval(total_supply_expr, env, &context)?;
        if let Value::UInt(total_supply_int) = total_supply_value {
            Ok(DefineResult::FungibleToken(asset_name.clone(), Some(total_supply_int)))
        } else {
            Err(CheckErrors::TypeValueError(TypeSignature::UIntType, total_supply_value).into())
        }
    } else {
        Ok(DefineResult::FungibleToken(asset_name.clone(), None))
    }
}

fn handle_define_map(map_str: &ClarityName,
                     key_type: &SymbolicExpression,
                     value_type: &SymbolicExpression,
                     env: &mut Environment) -> Result<DefineResult> {
    check_legal_define(&map_str, &env.contract_context)?;

    let key_type_signature = TupleTypeSignature::parse_name_type_pair_list(key_type, env)?;
    let value_type_signature = TupleTypeSignature::parse_name_type_pair_list(value_type, env)?;

    Ok(DefineResult::Map(map_str.clone(), key_type_signature, value_type_signature))
}

fn handle_define_trait(name: &ClarityName,
                       functions: &[SymbolicExpression],
                       env: &mut Environment) -> Result<DefineResult> {
    check_legal_define(&name, &env.contract_context)?;
    
    let trait_signature = TypeSignature::parse_trait_type_repr(&functions, env)?;
    
    Ok(DefineResult::Trait(name.clone(), trait_signature))
}

fn handle_use_trait(name: &ClarityName, trait_identifier: &TraitIdentifier) -> Result<DefineResult> {
    Ok(DefineResult::UseTrait(name.clone(), trait_identifier.clone()))
}

fn handle_impl_trait(trait_identifier: &TraitIdentifier) -> Result<DefineResult> {
    Ok(DefineResult::ImplTrait(trait_identifier.clone()))
}

impl DefineFunctions {
    pub fn try_parse(expression: &SymbolicExpression) -> Option<(DefineFunctions, &[SymbolicExpression])> {
        let expression = expression.match_list()?;
        let (function_name, args) = expression.split_first()?;
        let function_name = function_name.match_atom()?;
        let define_type = DefineFunctions::lookup_by_name(function_name)?;
        Some((define_type, args))
    }
}

impl <'a> DefineFunctionsParsed <'a> {
    /// Try to parse a Top-Level Expression (e.g., (define-private (foo) 1)) as
    /// a define-statement, returns None if the supplied expression is not a define.
    pub fn try_parse(expression: &'a SymbolicExpression) -> std::result::Result<Option<DefineFunctionsParsed<'a>>, CheckErrors> {
        let (define_type, args) = match DefineFunctions::try_parse(expression) {
            Some(x) => x,
            None => return Ok(None)
        };
        let result = match define_type {
            DefineFunctions::Constant => {
                check_argument_count(2, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                DefineFunctionsParsed::Constant { name, value: &args[1] }
            },
            DefineFunctions::PrivateFunction => {
                check_argument_count(2, args)?;
                let signature = args[0].match_list().ok_or(CheckErrors::DefineFunctionBadSignature)?;
                DefineFunctionsParsed::PrivateFunction { signature, body: &args[1] }
            },
            DefineFunctions::ReadOnlyFunction => {
                check_argument_count(2, args)?;
                let signature = args[0].match_list().ok_or(CheckErrors::DefineFunctionBadSignature)?;
                DefineFunctionsParsed::ReadOnlyFunction { signature, body: &args[1] }
            },
            DefineFunctions::PublicFunction => {
                check_argument_count(2, args)?;
                let signature = args[0].match_list().ok_or(CheckErrors::DefineFunctionBadSignature)?;
                DefineFunctionsParsed::PublicFunction { signature, body: &args[1] }
            },
            DefineFunctions::NonFungibleToken => {
                check_argument_count(2, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                DefineFunctionsParsed::NonFungibleToken { name, nft_type: &args[1] }
            },
            DefineFunctions::FungibleToken => {
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                if args.len() == 1 {
                    DefineFunctionsParsed::UnboundedFungibleToken { name }
                } else if args.len() == 2 {
                    DefineFunctionsParsed::BoundedFungibleToken { name, max_supply: &args[1] }
                } else {
                    return Err(CheckErrors::IncorrectArgumentCount(1, args.len()).into())
                }
            },
            DefineFunctions::Map => {
                check_argument_count(3, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                DefineFunctionsParsed::Map { name, key_type: &args[1], value_type: &args[2] }
            },
            DefineFunctions::PersistedVariable => {
                check_argument_count(3, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                DefineFunctionsParsed::PersistedVariable { name, data_type: &args[1], initial: &args[2] }
            },
            DefineFunctions::Trait => {
                check_argument_count(2, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                DefineFunctionsParsed::Trait { name, functions: &args[1..] }
            },
            DefineFunctions::UseTrait => {
                check_argument_count(2, args)?;
                let name = args[0].match_atom().ok_or(CheckErrors::ExpectedName)?;
                match &args[1].expr {
                    Field(ref field) => DefineFunctionsParsed::UseTrait { 
                        name: &name, 
                        trait_identifier: &field 
                    },
                    _ => return Err(CheckErrors::ExpectedTraitIdentifier.into())
                }
            },
            DefineFunctions::ImplTrait => {
                check_argument_count(1, args)?;
                match &args[0].expr {
                    Field(ref field) => DefineFunctionsParsed::ImplTrait { 
                        trait_identifier: &field 
                    },
                    _ => return Err(CheckErrors::ExpectedTraitIdentifier.into())
                }
            },

        };
        Ok(Some(result))
    }
}

pub fn evaluate_define(expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
    if let Some(define_type) = DefineFunctionsParsed::try_parse(expression)? {
        match define_type {
            DefineFunctionsParsed::Constant { name, value } => {
                handle_define_variable(name, value, env)
            },
            DefineFunctionsParsed::PrivateFunction { signature, body } => {
                handle_define_function(signature, body, env, DefineType::Private)
            },
            DefineFunctionsParsed::ReadOnlyFunction { signature, body } => {
                handle_define_function(signature, body, env, DefineType::ReadOnly)
            },
            DefineFunctionsParsed::PublicFunction { signature, body } => {
                handle_define_function(signature, body, env, DefineType::Public)
            },
            DefineFunctionsParsed::NonFungibleToken { name, nft_type } => {
                handle_define_nonfungible_asset(name, nft_type, env)
            },
            DefineFunctionsParsed::BoundedFungibleToken { name, max_supply } => {
                handle_define_fungible_token(name, Some(max_supply), env)
            },
            DefineFunctionsParsed::UnboundedFungibleToken { name } => {
                handle_define_fungible_token(name, None, env)
            },
            DefineFunctionsParsed::Map { name, key_type, value_type } => {
                handle_define_map(name, key_type, value_type, env)
            },
            DefineFunctionsParsed::PersistedVariable { name, data_type, initial } => {
                handle_define_persisted_variable(name, data_type, initial, env)
            }
            DefineFunctionsParsed::Trait { name, functions } => {
                handle_define_trait(name, functions, env)
            },
            DefineFunctionsParsed::UseTrait { name, trait_identifier } => {
                handle_use_trait(name, trait_identifier)
            },
            DefineFunctionsParsed::ImplTrait { trait_identifier } => {
                handle_impl_trait(trait_identifier)
            }
        }
    } else {
        Ok(DefineResult::NoDefine)
    }
}
