use crate::{
    context::Context,
    function::{Function, FunctionBody, FunctionParameter, FunctionQuery, FunctionSignature},
    meta::Meta,
    registry::Registry,
    types::{
        enum_type::{EnumVariant, RuntimeEnumBuilder},
        struct_type::{RuntimeStructBuilder, StructField},
        TypeQuery,
    },
    Visibility,
};
use std::{
    collections::HashMap,
    error::Error,
    path::{Path, PathBuf},
    sync::Arc,
};
pub type ScriptHandle<'a, SE> = Arc<Script<'a, SE>>;
pub type Script<'a, SE> = Vec<ScriptOperation<'a, SE>>;
pub trait ScriptExpression: Send + Sync {
    fn evaluate(&self, context: &mut Context, registry: &Registry);
}
impl ScriptExpression for () {
    fn evaluate(&self, _: &mut Context, _: &Registry) {}
}
#[allow(clippy::type_complexity)]
pub struct InlineExpression(Arc<dyn Fn(&mut Context, &Registry) + Send + Sync>);
impl InlineExpression {
    pub fn copied<T: Copy + Send + Sync + 'static>(value: T) -> Self {
        Self(Arc::new(move |context, _| {
            context.stack().push(value);
        }))
    }
    pub fn cloned<T: Clone + Send + Sync + 'static>(value: T) -> Self {
        Self(Arc::new(move |context, _| {
            context.stack().push(value.clone());
        }))
    }
    pub fn closure<F: Fn(&mut Context, &Registry) + Send + Sync + 'static>(f: F) -> Self {
        Self(Arc::new(f))
    }
}
impl ScriptExpression for InlineExpression {
    fn evaluate(&self, context: &mut Context, registry: &Registry) {
        (self.0)(context, registry);
    }
}
#[derive(Debug)]
pub enum ScriptOperation<'a, SE: ScriptExpression> {
    None,
    Expression {
        expression: SE,
    },
    DefineRegister {
        query: TypeQuery<'a>,
    },
    DropRegister {
        index: usize,
    },
    PushFromRegister {
        index: usize,
    },
    PopToRegister {
        index: usize,
    },
    MoveRegister {
        from: usize,
        to: usize,
    },
    CallFunction {
        query: FunctionQuery<'a>,
    },
    BranchScope {
        scope_success: ScriptHandle<'a, SE>,
        scope_failure: Option<ScriptHandle<'a, SE>>,
    },
    LoopScope {
        scope: ScriptHandle<'a, SE>,
    },
    PushScope {
        scope: ScriptHandle<'a, SE>,
    },
    PopScope,
    ContinueScopeConditionally,
}
impl<'a, SE: ScriptExpression> ScriptOperation<'a, SE> {
    pub fn label(&self) -> &str {
        match self {
            Self::None => "None",
            Self::Expression { .. } => "Expression",
            Self::DefineRegister { .. } => "DefineRegister",
            Self::DropRegister { .. } => "DropRegister",
            Self::PushFromRegister { .. } => "PushFromRegister",
            Self::PopToRegister { .. } => "PopToRegister",
            Self::MoveRegister { .. } => "MoveRegister",
            Self::CallFunction { .. } => "CallFunction",
            Self::BranchScope { .. } => "BranchScope",
            Self::LoopScope { .. } => "LoopScope",
            Self::PushScope { .. } => "PushScope",
            Self::PopScope => "PopScope",
            Self::ContinueScopeConditionally => "ContinueScopeConditionally",
        }
    }
}
pub struct ScriptBuilder<'a, SE: ScriptExpression>(Script<'a, SE>);
impl<'a, SE: ScriptExpression> Default for ScriptBuilder<'a, SE> {
    fn default() -> Self {
        Self(vec![])
    }
}
impl<'a, SE: ScriptExpression> ScriptBuilder<'a, SE> {
    pub fn build(self) -> ScriptHandle<'a, SE> {
        ScriptHandle::new(self.0)
    }
    pub fn expression(mut self, expression: SE) -> Self {
        self.0.push(ScriptOperation::Expression { expression });
        self
    }
    pub fn define_register(mut self, query: TypeQuery<'a>) -> Self {
        self.0.push(ScriptOperation::DefineRegister { query });
        self
    }
    pub fn drop_register(mut self, index: usize) -> Self {
        self.0.push(ScriptOperation::DropRegister { index });
        self
    }
    pub fn push_from_register(mut self, index: usize) -> Self {
        self.0.push(ScriptOperation::PushFromRegister { index });
        self
    }
    pub fn pop_to_register(mut self, index: usize) -> Self {
        self.0.push(ScriptOperation::PopToRegister { index });
        self
    }
    pub fn move_register(mut self, from: usize, to: usize) -> Self {
        self.0.push(ScriptOperation::MoveRegister { from, to });
        self
    }
    pub fn call_function(mut self, query: FunctionQuery<'a>) -> Self {
        self.0.push(ScriptOperation::CallFunction { query });
        self
    }
    pub fn branch_scope(
        mut self,
        scope_success: ScriptHandle<'a, SE>,
        scope_failure: Option<ScriptHandle<'a, SE>>,
    ) -> Self {
        self.0.push(ScriptOperation::BranchScope {
            scope_success,
            scope_failure,
        });
        self
    }
    pub fn loop_scope(mut self, scope: ScriptHandle<'a, SE>) -> Self {
        self.0.push(ScriptOperation::LoopScope { scope });
        self
    }
    pub fn push_scope(mut self, scope: ScriptHandle<'a, SE>) -> Self {
        self.0.push(ScriptOperation::PushScope { scope });
        self
    }
    pub fn pop_scope(mut self) -> Self {
        self.0.push(ScriptOperation::PopScope);
        self
    }
    pub fn continue_scope_conditionally(mut self) -> Self {
        self.0.push(ScriptOperation::ContinueScopeConditionally);
        self
    }
}
#[derive(Debug)]
pub struct ScriptFunctionParameter<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub type_query: TypeQuery<'a>,
}
impl<'a> ScriptFunctionParameter<'a> {
    pub fn build(&self, registry: &Registry) -> FunctionParameter {
        FunctionParameter {
            meta: self.meta.to_owned(),
            name: self.name.to_owned(),
            type_handle: registry
                .types()
                .find(|type_| self.type_query.is_valid(type_))
                .unwrap()
                .clone(),
        }
    }
}
#[derive(Debug)]
pub struct ScriptFunctionSignature<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub module_name: Option<String>,
    pub type_query: Option<TypeQuery<'a>>,
    pub visibility: Visibility,
    pub inputs: Vec<ScriptFunctionParameter<'a>>,
    pub outputs: Vec<ScriptFunctionParameter<'a>>,
}
impl<'a> ScriptFunctionSignature<'a> {
    pub fn build(&self, registry: &Registry) -> FunctionSignature {
        FunctionSignature {
            meta: self.meta.to_owned(),
            name: self.name.to_owned(),
            module_name: self.module_name.to_owned(),
            type_handle: self.type_query.as_ref().map(|type_query| {
                registry
                    .types()
                    .find(|type_| type_query.is_valid(type_))
                    .unwrap()
                    .clone()
            }),
            visibility: self.visibility,
            inputs: self
                .inputs
                .iter()
                .map(|parameter| parameter.build(registry))
                .collect(),
            outputs: self
                .outputs
                .iter()
                .map(|parameter| parameter.build(registry))
                .collect(),
        }
    }
}
#[derive(Debug)]
pub struct ScriptFunction<'a, SE: ScriptExpression> {
    pub signature: ScriptFunctionSignature<'a>,
    pub script: ScriptHandle<'a, SE>,
}
impl<SE: ScriptExpression> ScriptFunction<'static, SE> {
    pub fn install<SFG: ScriptFunctionGenerator<SE>>(
        &self,
        registry: &mut Registry,
        input: SFG::Input,
    ) -> Option<SFG::Output> {
        let (function, output) = SFG::generate_function(self, registry, input)?;
        registry.add_function(function);
        Some(output)
    }
}
pub trait ScriptFunctionGenerator<SE: ScriptExpression> {
    type Input;
    type Output;
    fn generate_function_body(
        script: ScriptHandle<'static, SE>,
        input: Self::Input,
    ) -> Option<(FunctionBody, Self::Output)>;
    fn generate_function(
        function: &ScriptFunction<'static, SE>,
        registry: &Registry,
        input: Self::Input,
    ) -> Option<(Function, Self::Output)> {
        let (body, output) = Self::generate_function_body(function.script.clone(), input)?;
        Some((
            Function::new(function.signature.build(registry), body),
            output,
        ))
    }
}
#[derive(Debug)]
pub struct ScriptStructField<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub visibility: Visibility,
    pub type_query: TypeQuery<'a>,
}
impl<'a> ScriptStructField<'a> {
    pub fn build(&self, registry: &Registry) -> StructField {
        let mut result = StructField::new(
            &self.name,
            registry
                .types()
                .find(|type_| self.type_query.is_valid(type_))
                .unwrap()
                .clone(),
        )
        .with_visibility(self.visibility);
        result.meta.clone_from(&self.meta);
        result
    }
}
#[derive(Debug)]
pub struct ScriptStruct<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub module_name: Option<String>,
    pub visibility: Visibility,
    pub fields: Vec<ScriptStructField<'a>>,
}
impl<'a> ScriptStruct<'a> {
    pub fn declare(&self, registry: &mut Registry) {
        let mut builder = RuntimeStructBuilder::new(&self.name);
        builder = builder.visibility(self.visibility);
        if let Some(module_name) = self.module_name.as_ref() {
            builder = builder.module_name(module_name);
        }
        if let Some(meta) = self.meta.as_ref() {
            builder = builder.meta(meta.to_owned());
        }
        registry.add_type(builder.build());
    }
    pub fn define(&self, registry: &mut Registry) {
        let query = TypeQuery {
            name: Some(self.name.as_str().into()),
            module_name: self
                .module_name
                .as_ref()
                .map(|module_name| module_name.into()),
            ..Default::default()
        };
        if let Some(handle) = registry.find_type(query) {
            let mut builder = RuntimeStructBuilder::new(&self.name);
            builder = builder.visibility(self.visibility);
            if let Some(module_name) = self.module_name.as_ref() {
                builder = builder.module_name(module_name);
            }
            if let Some(meta) = self.meta.as_ref() {
                builder = builder.meta(meta.to_owned());
            }
            for field in &self.fields {
                builder = builder.field(field.build(registry));
            }
            unsafe {
                let type_ = Arc::as_ptr(&handle).cast_mut();
                *type_ = builder.build().into();
            }
        }
    }
    pub fn install(&self, registry: &mut Registry) {
        let mut builder = RuntimeStructBuilder::new(&self.name);
        builder = builder.visibility(self.visibility);
        if let Some(module_name) = self.module_name.as_ref() {
            builder = builder.module_name(module_name);
        }
        for field in &self.fields {
            builder = builder.field(field.build(registry));
        }
        registry.add_type(builder.build());
    }
}
#[derive(Debug)]
pub struct ScriptEnumVariant<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub fields: Vec<ScriptStructField<'a>>,
    pub discriminant: Option<u8>,
}
impl<'a> ScriptEnumVariant<'a> {
    pub fn build(&self, registry: &Registry) -> EnumVariant {
        let mut result = EnumVariant::new(&self.name);
        result.fields = self
            .fields
            .iter()
            .map(|field| field.build(registry))
            .collect();
        result.meta.clone_from(&self.meta);
        result
    }
}
#[derive(Debug)]
pub struct ScriptEnum<'a> {
    pub meta: Option<Meta>,
    pub name: String,
    pub module_name: Option<String>,
    pub visibility: Visibility,
    pub variants: Vec<ScriptEnumVariant<'a>>,
    pub default_variant: Option<u8>,
}
impl<'a> ScriptEnum<'a> {
    pub fn declare(&self, registry: &mut Registry) {
        let mut builder = RuntimeEnumBuilder::new(&self.name);
        if let Some(discriminant) = self.default_variant {
            builder = builder.set_default_variant(discriminant);
        }
        builder = builder.visibility(self.visibility);
        if let Some(module_name) = self.module_name.as_ref() {
            builder = builder.module_name(module_name);
        }
        if let Some(meta) = self.meta.as_ref() {
            builder = builder.meta(meta.to_owned());
        }
        registry.add_type(builder.build());
    }
    pub fn define(&self, registry: &mut Registry) {
        let query = TypeQuery {
            name: Some(self.name.as_str().into()),
            module_name: self
                .module_name
                .as_ref()
                .map(|module_name| module_name.into()),
            ..Default::default()
        };
        if let Some(handle) = registry.find_type(query) {
            let mut builder = RuntimeEnumBuilder::new(&self.name);
            if let Some(discriminant) = self.default_variant {
                builder = builder.set_default_variant(discriminant);
            }
            builder = builder.visibility(self.visibility);
            if let Some(module_name) = self.module_name.as_ref() {
                builder = builder.module_name(module_name);
            }
            if let Some(meta) = self.meta.as_ref() {
                builder = builder.meta(meta.to_owned());
            }
            for variant in &self.variants {
                if let Some(discriminant) = variant.discriminant {
                    builder =
                        builder.variant_with_discriminant(variant.build(registry), discriminant);
                } else {
                    builder = builder.variant(variant.build(registry));
                }
            }
            unsafe {
                let type_ = Arc::as_ptr(&handle).cast_mut();
                *type_ = builder.build().into();
            }
        }
    }
    pub fn install(&self, registry: &mut Registry) {
        let mut builder = RuntimeEnumBuilder::new(&self.name);
        if let Some(discriminant) = self.default_variant {
            builder = builder.set_default_variant(discriminant);
        }
        builder = builder.visibility(self.visibility);
        if let Some(module_name) = self.module_name.as_ref() {
            builder = builder.module_name(module_name);
        }
        for variant in &self.variants {
            if let Some(discriminant) = variant.discriminant {
                builder = builder.variant_with_discriminant(variant.build(registry), discriminant);
            } else {
                builder = builder.variant(variant.build(registry));
            }
        }
        registry.add_type(builder.build());
    }
}
#[derive(Debug, Default)]
pub struct ScriptModule<'a, SE: ScriptExpression> {
    pub name: String,
    pub structs: Vec<ScriptStruct<'a>>,
    pub enums: Vec<ScriptEnum<'a>>,
    pub functions: Vec<ScriptFunction<'a, SE>>,
}
impl<'a, SE: ScriptExpression> ScriptModule<'a, SE> {
    pub fn fix_module_names(&mut self) {
        for type_ in &mut self.structs {
            type_.module_name = Some(self.name.to_owned());
        }
        for type_ in &mut self.enums {
            type_.module_name = Some(self.name.to_owned());
        }
        for function in &mut self.functions {
            function.signature.module_name = Some(self.name.to_owned());
        }
    }
    pub fn declare_types(&self, registry: &mut Registry) {
        for type_ in &self.structs {
            type_.declare(registry);
        }
        for type_ in &self.enums {
            type_.declare(registry);
        }
    }
    pub fn define_types(&self, registry: &mut Registry) {
        for type_ in &self.structs {
            type_.define(registry);
        }
        for type_ in &self.enums {
            type_.define(registry);
        }
    }
    pub fn install_types(&self, registry: &mut Registry) {
        self.declare_types(registry);
        self.define_types(registry);
    }
}
impl<SE: ScriptExpression> ScriptModule<'static, SE> {
    pub fn install_functions<SFG: ScriptFunctionGenerator<SE>>(
        &self,
        registry: &mut Registry,
        input: SFG::Input,
    ) where
        SFG::Input: Clone,
    {
        for function in &self.functions {
            function.install::<SFG>(registry, input.clone());
        }
    }
}
#[derive(Debug, Default)]
pub struct ScriptPackage<'a, SE: ScriptExpression> {
    pub modules: Vec<ScriptModule<'a, SE>>,
}
impl<SE: ScriptExpression> ScriptPackage<'static, SE> {
    pub fn install<SFG: ScriptFunctionGenerator<SE>>(
        &self,
        registry: &mut Registry,
        input: SFG::Input,
    ) where
        SFG::Input: Clone,
    {
        for module in &self.modules {
            module.install_types(registry);
        }
        for module in &self.modules {
            module.install_functions::<SFG>(registry, input.clone());
        }
    }
}
pub struct ScriptContent<T> {
    pub path: String,
    pub name: String,
    pub data: Result<Option<T>, Box<dyn Error>>,
}
pub trait ScriptContentProvider<T> {
    fn load(&mut self, path: &str) -> Result<Option<T>, Box<dyn Error>>;
    fn unpack_load(&mut self, path: &str) -> Result<Vec<ScriptContent<T>>, Box<dyn Error>> {
        Ok(vec![ScriptContent {
            path: path.to_owned(),
            name: path.to_owned(),
            data: self.load(path),
        }])
    }
    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
        Ok(path.to_owned())
    }
    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>>;
}
pub struct ExtensionContentProvider<S> {
    default_extension: Option<String>,
    extension_providers: HashMap<String, Box<dyn ScriptContentProvider<S>>>,
}
impl<S> Default for ExtensionContentProvider<S> {
    fn default() -> Self {
        Self {
            default_extension: None,
            extension_providers: Default::default(),
        }
    }
}
impl<S> ExtensionContentProvider<S> {
    pub fn default_extension(mut self, extension: impl ToString) -> Self {
        self.default_extension = Some(extension.to_string());
        self
    }
    pub fn extension(
        mut self,
        extension: &str,
        content_provider: impl ScriptContentProvider<S> + 'static,
    ) -> Self {
        self.extension_providers
            .insert(extension.to_owned(), Box::new(content_provider));
        self
    }
}
impl<S> ScriptContentProvider<S> for ExtensionContentProvider<S> {
    fn load(&mut self, _: &str) -> Result<Option<S>, Box<dyn Error>> {
        Ok(None)
    }
    fn unpack_load(&mut self, path: &str) -> Result<Vec<ScriptContent<S>>, Box<dyn Error>> {
        let extension = match Path::new(path).extension() {
            Some(extension) => extension.to_string_lossy().to_string(),
            None => match &self.default_extension {
                Some(extension) => extension.to_owned(),
                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
            },
        };
        if let Some(content_provider) = self.extension_providers.get_mut(&extension) {
            content_provider.unpack_load(path)
        } else {
            Err(Box::new(
                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
            ))
        }
    }
    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
        let extension = match Path::new(path).extension() {
            Some(extension) => extension.to_string_lossy().to_string(),
            None => match &self.default_extension {
                Some(extension) => extension.to_owned(),
                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
            },
        };
        if let Some(content_provider) = self.extension_providers.get(&extension) {
            content_provider.sanitize_path(path)
        } else {
            Err(Box::new(
                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
            ))
        }
    }
    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
        let extension = match Path::new(relative).extension() {
            Some(extension) => extension.to_string_lossy().to_string(),
            None => match &self.default_extension {
                Some(extension) => extension.to_owned(),
                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
            },
        };
        if let Some(content_provider) = self.extension_providers.get(&extension) {
            content_provider.join_paths(parent, relative)
        } else {
            Err(Box::new(
                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
            ))
        }
    }
}
#[derive(Debug)]
pub enum ExtensionContentProviderError {
    NoDefaultExtension,
    ContentProviderForExtensionNotFound(String),
}
impl std::fmt::Display for ExtensionContentProviderError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ExtensionContentProviderError::NoDefaultExtension => {
                write!(f, "No default extension set")
            }
            ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension) => {
                write!(
                    f,
                    "Could not find content provider for extension: `{}`",
                    extension
                )
            }
        }
    }
}
impl Error for ExtensionContentProviderError {}
pub struct IgnoreContentProvider;
impl<S> ScriptContentProvider<S> for IgnoreContentProvider {
    fn load(&mut self, _: &str) -> Result<Option<S>, Box<dyn Error>> {
        Ok(None)
    }
    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
        Ok(format!("{}/{}", parent, relative))
    }
}
pub trait BytesContentParser<T> {
    fn parse(&self, bytes: Vec<u8>) -> Result<T, Box<dyn Error>>;
}
pub struct FileContentProvider<T> {
    extension: String,
    parser: Box<dyn BytesContentParser<T>>,
}
impl<T> FileContentProvider<T> {
    pub fn new(extension: impl ToString, parser: impl BytesContentParser<T> + 'static) -> Self {
        Self {
            extension: extension.to_string(),
            parser: Box::new(parser),
        }
    }
}
impl<T> ScriptContentProvider<T> for FileContentProvider<T> {
    fn load(&mut self, path: &str) -> Result<Option<T>, Box<dyn Error>> {
        Ok(Some(self.parser.parse(std::fs::read(path)?)?))
    }
    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
        let mut result = PathBuf::from(path);
        if result.extension().is_none() {
            result.set_extension(&self.extension);
        }
        Ok(result.canonicalize()?.to_string_lossy().into_owned())
    }
    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
        let mut path = PathBuf::from(parent);
        path.pop();
        Ok(path.join(relative).to_string_lossy().into_owned())
    }
}