//! The result of emitter

use crate::frame_slot::FrameSlot;
use crate::function::FunctionFlags;
use crate::gcthings::GCThing;
use crate::scope::ScopeIndex;
use crate::scope_notes::ScopeNote;
use ast::source_atom_set::SourceAtomSetIndex;

// WARNING
// The following section is generated by update_stencil.py.
// Do mot modify manually.
//
// @@@@ BEGIN TYPES @@@@
#[derive(Debug, Clone, Copy)]
pub enum ImmutableScriptFlagsEnum {
    #[allow(dead_code)]
    IsForEval = 1 << 0,
    #[allow(dead_code)]
    IsModule = 1 << 1,
    #[allow(dead_code)]
    IsFunction = 1 << 2,
    #[allow(dead_code)]
    SelfHosted = 1 << 3,
    #[allow(dead_code)]
    ForceStrict = 1 << 4,
    #[allow(dead_code)]
    HasNonSyntacticScope = 1 << 5,
    #[allow(dead_code)]
    NoScriptRval = 1 << 6,
    #[allow(dead_code)]
    TreatAsRunOnce = 1 << 7,
    #[allow(dead_code)]
    Strict = 1 << 8,
    #[allow(dead_code)]
    HasModuleGoal = 1 << 9,
    #[allow(dead_code)]
    HasInnerFunctions = 1 << 10,
    #[allow(dead_code)]
    HasDirectEval = 1 << 11,
    #[allow(dead_code)]
    BindingsAccessedDynamically = 1 << 12,
    #[allow(dead_code)]
    HasCallSiteObj = 1 << 13,
    #[allow(dead_code)]
    IsAsync = 1 << 14,
    #[allow(dead_code)]
    IsGenerator = 1 << 15,
    #[allow(dead_code)]
    FunHasExtensibleScope = 1 << 16,
    #[allow(dead_code)]
    FunctionHasThisBinding = 1 << 17,
    #[allow(dead_code)]
    NeedsHomeObject = 1 << 18,
    #[allow(dead_code)]
    IsDerivedClassConstructor = 1 << 19,
    #[allow(dead_code)]
    IsSyntheticFunction = 1 << 20,
    #[allow(dead_code)]
    UseMemberInitializers = 1 << 21,
    #[allow(dead_code)]
    HasRest = 1 << 22,
    #[allow(dead_code)]
    NeedsFunctionEnvironmentObjects = 1 << 23,
    #[allow(dead_code)]
    FunctionHasExtraBodyVarScope = 1 << 24,
    #[allow(dead_code)]
    ShouldDeclareArguments = 1 << 25,
    #[allow(dead_code)]
    NeedsArgsObj = 1 << 26,
    #[allow(dead_code)]
    HasMappedArgsObj = 1 << 27,
    #[allow(dead_code)]
    IsInlinableLargeFunction = 1 << 28,
    #[allow(dead_code)]
    FunctionHasNewTargetBinding = 1 << 29,
}

#[derive(Debug, Clone, Copy)]
pub enum MutableScriptFlagsEnum {
    #[allow(dead_code)]
    HasRunOnce = 1 << 8,
    #[allow(dead_code)]
    HasBeenCloned = 1 << 9,
    #[allow(dead_code)]
    HasScriptCounts = 1 << 10,
    #[allow(dead_code)]
    HasDebugScript = 1 << 11,
    #[allow(dead_code)]
    AllowRelazify = 1 << 14,
    #[allow(dead_code)]
    SpewEnabled = 1 << 15,
    #[allow(dead_code)]
    NeedsFinalWarmUpCount = 1 << 16,
    #[allow(dead_code)]
    BaselineDisabled = 1 << 17,
    #[allow(dead_code)]
    IonDisabled = 1 << 18,
    #[allow(dead_code)]
    Uninlineable = 1 << 19,
    #[allow(dead_code)]
    FailedBoundsCheck = 1 << 21,
    #[allow(dead_code)]
    HadLICMInvalidation = 1 << 22,
    #[allow(dead_code)]
    HadReorderingBailout = 1 << 23,
    #[allow(dead_code)]
    HadEagerTruncationBailout = 1 << 24,
    #[allow(dead_code)]
    FailedLexicalCheck = 1 << 25,
    #[allow(dead_code)]
    HadSpeculativePhiBailout = 1 << 26,
    #[allow(dead_code)]
    HadUnboxFoldingBailout = 1 << 27,
}

// @@@@ END TYPES @@@@

#[derive(Debug)]
pub struct ImmutableScriptFlags {
    value: u32,
}

impl ImmutableScriptFlags {
    pub fn new() -> Self {
        Self { value: 0 }
    }

    pub fn from_raw(bits: u32) -> Self {
        Self { value: bits }
    }

    pub fn set(&mut self, bit: ImmutableScriptFlagsEnum) {
        self.value |= bit as u32;
    }

    pub fn has(&self, bit: ImmutableScriptFlagsEnum) -> bool {
        (self.value & bit as u32) != 0
    }

    pub fn reset(&mut self, bit: ImmutableScriptFlagsEnum) {
        self.value &= !(bit as u32)
    }
}

impl From<ImmutableScriptFlags> for u32 {
    fn from(flags: ImmutableScriptFlags) -> u32 {
        flags.value
    }
}

/// Maps to js::ImmutableScriptData in m-c/js/src/vm/SharedStencil.h.
#[derive(Debug)]
pub struct ImmutableScriptData {
    pub main_offset: u32,
    pub nfixed: FrameSlot,
    pub nslots: u32,
    pub body_scope_index: u32,
    pub num_ic_entries: u32,
    pub fun_length: u16,
    pub bytecode: Vec<u8>,
    pub scope_notes: Vec<ScopeNote>,
    // TODO: Add resume_offsets and try_notes.
}

/// Index into ImmutableScriptDataList.scripts.
#[derive(Debug, Clone, Copy)]
pub struct ImmutableScriptDataIndex {
    index: usize,
}

impl ImmutableScriptDataIndex {
    fn new(index: usize) -> Self {
        Self { index }
    }
}

impl From<ImmutableScriptDataIndex> for usize {
    fn from(index: ImmutableScriptDataIndex) -> usize {
        index.index
    }
}

/// List of ImmutableScriptData.
#[derive(Debug)]
pub struct ImmutableScriptDataList {
    /// Uses Option to allow `allocate()` and `populate()` to be called
    /// separately.
    items: Vec<Option<ImmutableScriptData>>,
}

impl ImmutableScriptDataList {
    pub fn new() -> Self {
        Self { items: Vec::new() }
    }

    pub fn push(&mut self, script: ImmutableScriptData) -> ImmutableScriptDataIndex {
        let index = self.items.len();
        self.items.push(Some(script));
        ImmutableScriptDataIndex::new(index)
    }

    pub fn allocate(&mut self) -> ImmutableScriptDataIndex {
        let index = self.items.len();
        self.items.push(None);
        ImmutableScriptDataIndex::new(index)
    }

    pub fn populate(&mut self, index: ImmutableScriptDataIndex, script: ImmutableScriptData) {
        self.items[usize::from(index)].replace(script);
    }
}

impl From<ImmutableScriptDataList> for Vec<ImmutableScriptData> {
    fn from(list: ImmutableScriptDataList) -> Vec<ImmutableScriptData> {
        list.items
            .into_iter()
            .map(|g| g.expect("Should be populated"))
            .collect()
    }
}

#[derive(Debug, Clone)]
pub struct SourceExtent {
    pub source_start: u32,
    pub source_end: u32,
    pub to_string_start: u32,
    pub to_string_end: u32,

    pub lineno: u32,
    pub column: u32,
}

impl SourceExtent {
    fn new() -> Self {
        Self {
            source_start: 0,
            source_end: 0,
            to_string_start: 0,
            to_string_end: 0,

            lineno: 0,
            column: 0,
        }
    }

    pub fn top_level_script(length: u32, lineno: u32, column: u32) -> Self {
        Self {
            source_start: 0,
            source_end: length,
            to_string_start: 0,
            to_string_end: length,

            lineno,
            column,
        }
    }
}

/// Maps to js::frontend::ScriptStencil in m-c/js/src/frontend/Stencil.h.
#[derive(Debug)]
pub struct ScriptStencil {
    // Fields for BaseScript.
    // Used by:
    //   * Global script
    //   * Eval
    //   * Module
    //   * non-lazy Function (except asm.js module)
    //   * lazy Function (cannot be asm.js module)
    /// See `BaseScript::immutableFlags_`.
    pub immutable_flags: ImmutableScriptFlags,

    /// For top level script and non-lazy function script,
    /// this is a list of GC things referred by bytecode and scope.
    ///
    /// For lazy function script, this contains the list of inner functions,
    /// followed by the list of names defined and closed over by inner script.
    /// The list of names are delimited by GCThing::Null for each scope.
    ///
    /// The order of scopes are depth-first post-order, and names inside each
    /// scope is in not defined.
    ///
    /// Trailing scopes without any names are omitted for space efficiency.
    pub gcthings: Vec<GCThing>,

    /// See `BaseScript::sharedData_`.
    pub immutable_script_data: Option<ImmutableScriptDataIndex>,

    /// The location of this script in the source.
    pub extent: SourceExtent,

    // Fields for JSFunction.
    // Used by:
    //   * non-lazy Function
    //   * lazy Function
    //   * asm.js module
    /// The explicit or implicit name of the function. The FunctionFlags
    /// indicate the kind of name.
    pub fun_name: Option<SourceAtomSetIndex>,

    /// See `JSFunction::nargs_`.
    pub fun_nargs: u16,

    /// See: `FunctionFlags`.
    pub fun_flags: FunctionFlags,

    /// If this ScriptStencil refers to a lazy child of the function being
    /// compiled, this field holds the child's immediately enclosing scope's
    /// index. Once compilation succeeds, we will store the scope pointed by
    /// this in the child's BaseScript.  (Debugger may become confused if lazy
    /// scripts refer to partially initialized enclosing scopes, so we must
    /// avoid storing the scope in the BaseScript until compilation has
    /// completed successfully.)
    pub lazy_function_enclosing_scope_index: Option<ScopeIndex>,

    /// This function is a standalone function that is not syntactically part of
    /// another script. Eg. Created by `new Function("")`.
    pub is_standalone_function: bool,

    /// This is set by the emitter of the enclosing script when a
    /// reference to this function is generated.
    pub was_function_emitted: bool,

    /// This function should be marked as a singleton. It is expected to be
    /// defined at most once. This is a heuristic only and does not affect
    /// correctness.
    pub is_singleton_function: bool,
}

impl ScriptStencil {
    fn empty_top_level_script() -> Self {
        Self {
            immutable_flags: ImmutableScriptFlags::new(),
            gcthings: Vec::new(),
            immutable_script_data: None,
            extent: SourceExtent::new(),
            fun_name: None,
            fun_nargs: 0,
            fun_flags: FunctionFlags::empty(),
            lazy_function_enclosing_scope_index: None,
            is_standalone_function: false,
            was_function_emitted: false,
            is_singleton_function: false,
        }
    }

    pub fn top_level_script(
        gcthings: Vec<GCThing>,
        immutable_script_data: ImmutableScriptDataIndex,
        extent: SourceExtent,
    ) -> Self {
        Self {
            immutable_flags: ImmutableScriptFlags::new(),
            gcthings,
            immutable_script_data: Some(immutable_script_data),
            extent,
            fun_name: None,
            fun_nargs: 0,
            fun_flags: FunctionFlags::empty(),
            lazy_function_enclosing_scope_index: None,
            is_standalone_function: false,
            was_function_emitted: false,
            is_singleton_function: false,
        }
    }

    pub fn lazy_function(
        extent: SourceExtent,
        fun_name: Option<SourceAtomSetIndex>,
        is_generator: bool,
        is_async: bool,
        fun_flags: FunctionFlags,
        lazy_function_enclosing_scope_index: Option<ScopeIndex>,
    ) -> Self {
        let mut flags = ImmutableScriptFlagsEnum::IsFunction as u32;
        if is_generator {
            flags |= ImmutableScriptFlagsEnum::IsGenerator as u32;
        }
        if is_async {
            flags |= ImmutableScriptFlagsEnum::IsAsync as u32;
        }

        Self {
            immutable_flags: ImmutableScriptFlags::from_raw(flags),
            gcthings: Vec::new(),
            immutable_script_data: None,
            extent,
            fun_name,
            fun_nargs: 0,
            fun_flags,
            lazy_function_enclosing_scope_index,
            is_standalone_function: false,
            was_function_emitted: false,
            is_singleton_function: false,
        }
    }

    pub fn is_function(&self) -> bool {
        self.immutable_flags
            .has(ImmutableScriptFlagsEnum::IsFunction)
    }

    pub fn is_non_lazy_function(&self) -> bool {
        self.is_function() && self.immutable_script_data.is_some()
    }

    pub fn is_lazy_function(&self) -> bool {
        self.is_function() && self.immutable_script_data.is_none()
    }

    pub fn set_function_has_this_binding(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::FunctionHasThisBinding);
    }

    pub fn set_has_rest(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags.set(ImmutableScriptFlagsEnum::HasRest);
    }

    pub fn set_needs_function_environment_objects(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects);
    }

    pub fn set_function_has_extra_body_var_scope(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope);
    }

    pub fn set_should_declare_arguments(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::ShouldDeclareArguments);
    }

    pub fn set_needs_args_obj(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::NeedsArgsObj);
    }

    pub fn set_has_mapped_args_obj(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::HasMappedArgsObj);
    }

    pub fn set_function_has_new_target_binding(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::FunctionHasNewTargetBinding);
    }

    pub fn is_arrow_function(&self) -> bool {
        self.is_function() && self.fun_flags.is_arrow()
    }

    pub fn set_fun_name(&mut self, name: SourceAtomSetIndex) {
        debug_assert!(self.is_function());
        self.fun_name = Some(name);
    }

    pub fn fun_name<'a>(&'a self) -> &'a Option<SourceAtomSetIndex> {
        debug_assert!(self.is_function());
        &self.fun_name
    }

    pub fn add_fun_nargs(&mut self) {
        debug_assert!(self.is_function());
        self.fun_nargs += 1;
    }

    /// source_start should point the start of parameter for functions.
    pub fn set_source_starts(&mut self, source_start: usize) {
        self.extent.source_start = source_start as u32;
    }

    /// to_string_end should point the end of function body for function,
    /// and the end of class for constructor.
    pub fn set_to_string_end(&mut self, to_string_end: usize) {
        self.extent.to_string_end = to_string_end as u32;
    }

    /// source_end should point the end of function body.
    pub fn set_source_end(&mut self, source_end: usize) {
        self.extent.source_end = source_end as u32;
    }

    pub fn set_function_emitted(&mut self) {
        self.was_function_emitted = true;
    }

    pub fn push_inner_function(&mut self, fun: ScriptStencilIndex) {
        self.immutable_flags
            .set(ImmutableScriptFlagsEnum::HasInnerFunctions);
        self.gcthings.push(GCThing::Function(fun));
    }

    pub fn push_closed_over_bindings(&mut self, name: SourceAtomSetIndex) {
        debug_assert!(self.is_lazy_function());
        self.gcthings.push(GCThing::Atom(name));
    }

    pub fn push_closed_over_bindings_delimiter(&mut self) {
        debug_assert!(self.is_lazy_function());
        self.gcthings.push(GCThing::Null);
    }
}

/// Index into ScriptStencilList.scripts.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScriptStencilIndex {
    index: usize,
}

impl ScriptStencilIndex {
    fn new(index: usize) -> Self {
        Self { index }
    }
}

impl From<ScriptStencilIndex> for usize {
    fn from(index: ScriptStencilIndex) -> usize {
        index.index
    }
}

/// List of stencil scripts.
#[derive(Debug)]
pub struct ScriptStencilList {
    scripts: Vec<ScriptStencil>,
}

impl ScriptStencilList {
    pub fn new() -> Self {
        Self {
            scripts: Vec::new(),
        }
    }

    pub fn new_with_empty_top_level() -> Self {
        Self {
            scripts: vec![ScriptStencil::empty_top_level_script()],
        }
    }

    pub fn push(&mut self, script: ScriptStencil) -> ScriptStencilIndex {
        let index = self.scripts.len();
        self.scripts.push(script);
        ScriptStencilIndex::new(index)
    }

    pub fn get<'a>(&'a self, index: ScriptStencilIndex) -> &'a ScriptStencil {
        &self.scripts[usize::from(index)]
    }

    pub fn get_mut<'a>(&'a mut self, index: ScriptStencilIndex) -> &'a mut ScriptStencil {
        &mut self.scripts[usize::from(index)]
    }

    pub fn set_top_level(&mut self, top_level: ScriptStencil) {
        self.scripts[0] = top_level;
    }
}

impl From<ScriptStencilList> for Vec<ScriptStencil> {
    fn from(list: ScriptStencilList) -> Vec<ScriptStencil> {
        list.scripts.into_iter().collect()
    }
}
