/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::collections::HashMap;

use allocative::Allocative;
use starlark_map::small_map::SmallMap;

use crate::values::layout::heap::profile::alloc_counts::AllocCounts;

#[derive(Debug, Default, Clone, Allocative)]
/// Information about the data stored on a heap. Accessible through
/// the function `allocated_summary` available on [`Heap`](crate::values::Heap)
/// and [`FrozenHeap`](crate::values::FrozenHeap)
pub struct HeapSummary {
    /// For each type, give the (number of entries, size of all entries).
    /// The size may be approximate as it includes information from
    /// the approximate [`memory_size`](StarlarkValue::memory_size) function.
    pub(crate) summary: SmallMap<&'static str, AllocCounts>,
}

impl HeapSummary {
    /// (Count, total size) by type.
    pub fn summary(&self) -> HashMap<String, (usize, usize)> {
        self.summary
            .iter()
            .map(|(k, v)| ((*k).to_owned(), (v.count, v.bytes)))
            .collect()
    }

    pub(crate) fn total(&self) -> AllocCounts {
        self.summary.values().sum()
    }

    /// Total number of bytes allocated.
    pub fn total_allocated_bytes(&self) -> usize {
        self.total().bytes
    }

    pub(crate) fn add(&mut self, t: &'static str, s: AllocCounts) {
        *self.summary.entry(t).or_default() += s;
    }

    pub(crate) fn merge<'a>(heaps: impl IntoIterator<Item = &'a HeapSummary>) -> HeapSummary {
        let mut summary = SmallMap::new();
        for heap in heaps {
            for (k, v) in heap.summary.iter() {
                *summary.entry(*k).or_default() += *v;
            }
        }
        HeapSummary { summary }
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Summary of heap allocations and function times with stacks.

pub(crate) mod aggregated;
pub(crate) mod alloc_counts;
pub(crate) mod arc_str;
pub(crate) mod by_type;
pub(crate) mod string_index;
mod summary_by_function;


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use dupe::Dupe;
use starlark_map::small_set::SmallSet;

use crate::values::layout::heap::profile::arc_str::ArcStr;

/// Map strings to integers 0, 1, 2, ...
#[derive(Default, Clone, Allocative)]
pub(crate) struct StringIndex {
    strings: SmallSet<ArcStr>,
}

#[derive(Copy, Clone, Dupe, Debug, Eq, PartialEq, Hash, Allocative)]
pub(crate) struct StringId(
    /// Index in strings index.
    pub(crate) usize,
);

impl StringIndex {
    pub(crate) fn index(&mut self, s: &str) -> StringId {
        if let Some(index) = self.strings.get_index_of(s) {
            return StringId(index);
        }

        let inserted = self.strings.insert(ArcStr::from(s));
        assert!(inserted);
        StringId(self.strings.len() - 1)
    }

    pub(crate) fn get(&self, id: StringId) -> &ArcStr {
        self.strings.get_index(id.0).expect("invalid string id")
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use dupe::Dupe;
use starlark_map::small_map::SmallMap;

use crate::eval::runtime::profile::csv::CsvWriter;
use crate::eval::runtime::small_duration::SmallDuration;
use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
use crate::values::layout::heap::profile::aggregated::StackFrame;
use crate::values::layout::heap::profile::alloc_counts::AllocCounts;
use crate::values::layout::heap::profile::arc_str::ArcStr;
use crate::values::layout::heap::profile::string_index::StringId;
use crate::values::layout::heap::profile::string_index::StringIndex;

/// Information relating to a function.
#[derive(Default, Debug, Clone)]
pub(crate) struct FuncInfo {
    /// Number of times this function was called
    pub calls: usize,
    /// Who called this function (and how many times each)
    pub callers: SmallMap<ArcStr, usize>,
    /// Time spent directly in this function
    pub time: SmallDuration,
    /// Time spent directly in this function and recursive functions.
    pub time_rec: SmallDuration,
    /// Allocations made by this function
    pub alloc: SmallMap<&'static str, AllocCounts>,
}

impl FuncInfo {
    pub(crate) fn merge<'a>(xs: impl Iterator<Item = &'a Self>) -> Self {
        let mut result = Self::default();
        for x in xs {
            result.calls += x.calls;
            result.time += x.time;
            for (k, v) in x.alloc.iter() {
                *result.alloc.entry(k).or_default() += *v;
            }
        }
        // Recursive time doesn't accumulate nicely, the time is the right value
        result.time_rec = result.time;
        result
    }

    /// Total number of allocations made by this function.
    fn alloc_count(&self) -> usize {
        self.alloc.values().map(|x| x.count).sum()
    }

    /// Total number of bytes allocated by this function.
    fn alloc_bytes(&self) -> usize {
        self.alloc.values().map(|x| x.bytes).sum()
    }
}

/// We morally have two pieces of information:
/// 1. Information about each function.
/// 2. The call stack.
///
/// However, we are always updating the top of the call stack,
/// so pull out top_stack/top_info as a cache.
pub(crate) struct HeapSummaryByFunction {
    /// Information about all functions.
    info: SmallMap<ArcStr, FuncInfo>,
    /// Allocated but unused memory.
    unused_capacity: usize,
}

impl HeapSummaryByFunction {
    pub(crate) fn init(stacks: &AggregateHeapProfileInfo) -> HeapSummaryByFunction {
        let mut info = HeapSummaryByFunction {
            info: SmallMap::new(),
            unused_capacity: stacks.unused_capacity.get(),
        };
        info.init_children(&stacks.root, &ArcStr::new_static("(root)"), &stacks.strings);
        info
    }

    fn init_children(
        &mut self,
        frame: &StackFrame,
        name: &ArcStr,
        strings: &StringIndex,
    ) -> SmallDuration {
        let mut time_rec = SmallDuration::default();
        for (func, child) in &frame.callees {
            time_rec += self.init_child(*func, child, name.dupe(), strings);
        }
        time_rec
    }

    fn init_child(
        &mut self,
        func: StringId,
        frame: &StackFrame,
        caller: ArcStr,
        strings: &StringIndex,
    ) -> SmallDuration {
        let func_str = strings.get(func);
        self.info.entry(func_str.dupe()).or_default().time += frame.time_x2;
        self.info.entry(func_str.dupe()).or_default().calls += frame.calls_x2 as usize;
        *self
            .info
            .entry(func_str.dupe())
            .or_default()
            .callers
            .entry(caller)
            .or_insert(0) += 1;
        for (t, allocs) in &frame.allocs.summary {
            *self
                .info
                .entry(func_str.dupe())
                .or_default()
                .alloc
                .entry(t)
                .or_default() += *allocs;
        }

        let time_rec = frame.time_x2 + self.init_children(frame, func_str, strings);
        self.info.entry(func_str.dupe()).or_default().time_rec += time_rec;
        time_rec
    }

    fn totals(&self) -> FuncInfo {
        FuncInfo::merge(self.info.values())
    }

    pub(crate) fn info(&self) -> Vec<(&ArcStr, &FuncInfo)> {
        self.info.iter().collect::<Vec<_>>()
    }

    pub(crate) fn gen_csv(&self) -> String {
        // Add a totals column
        let totals = self.totals();
        let mut columns: Vec<(&'static str, AllocCounts)> =
            totals.alloc.iter().map(|(k, v)| (*k, *v)).collect();

        columns.sort_by_key(|x| -(x.1.count as isize));

        let mut info = self.info();
        info.sort_by_key(|x| -(x.1.time.nanos as i128));

        let unused_capacity = FuncInfo {
            calls: 0,
            callers: SmallMap::new(),
            time: SmallDuration::default(),
            time_rec: SmallDuration::default(),
            alloc: SmallMap::new(),
        };

        enum RowKind {
            Total,
            UnusedCapacity,
            Func,
        }

        let totals_str = ArcStr::new_static("TOTALS");
        let unused_capacity_str = ArcStr::new_static("UNUSED CAPACITY");
        let info = [
            (&totals_str, &totals, RowKind::Total),
            (
                &unused_capacity_str,
                &unused_capacity,
                RowKind::UnusedCapacity,
            ),
        ]
        .into_iter()
        .chain(info.into_iter().map(|(k, v)| (k, v, RowKind::Func)));

        let mut csv = CsvWriter::new(
            [
                "Function",
                "Time(s)",
                "TimeRec(s)",
                "Calls",
                "Callers",
                "TopCaller",
                "TopCallerCount",
                "Allocs",
                "AllocBytes",
            ]
            .iter()
            .copied()
            .chain(columns.iter().map(|c| c.0)),
        );
        for (rowname, info, row_kind) in info {
            let blank = ArcStr::new_static("");
            let callers = info
                .callers
                .iter()
                .max_by_key(|x| x.1)
                .unwrap_or((&blank, &0));
            assert!(
                info.calls % 2 == 0,
                "we enter calls twice, for drop and non_drop"
            );
            // We divide calls and time by two
            // because we could calls twice: for drop and non-drop bumps.
            csv.write_value(&**rowname);
            csv.write_value(info.time / 2);
            csv.write_value(info.time_rec / 2);
            csv.write_value(info.calls / 2);
            csv.write_value(info.callers.len());
            csv.write_value(callers.0.as_str());
            csv.write_value(callers.1);
            match row_kind {
                RowKind::UnusedCapacity => {
                    csv.write_value(1);
                    csv.write_value(self.unused_capacity);
                }
                _ => {
                    csv.write_value(info.alloc_count());
                    csv.write_value(info.alloc_bytes());
                }
            }
            for c in &columns {
                csv.write_value(info.alloc.get(c.0).unwrap_or(&AllocCounts::default()).count);
            }
            csv.finish_row();
        }
        csv.finish()
    }
}

#[cfg(test)]
mod tests {
    use crate::environment::Globals;
    use crate::environment::Module;
    use crate::eval::Evaluator;
    use crate::eval::ProfileMode;
    use crate::syntax::AstModule;
    use crate::syntax::Dialect;
    use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
    use crate::values::layout::heap::profile::summary_by_function::FuncInfo;
    use crate::values::layout::heap::profile::summary_by_function::HeapSummaryByFunction;

    // Test data is collected from both drop and non-drop heaps.
    #[test]
    fn drop_non_drop() {
        let ast = AstModule::parse(
            "x.star",
            "\
_ignore = {1: 2}       # allocate a dict in drop
_ignore = str([1])     # allocate a string in non_drop
        "
            .to_owned(),
            &Dialect::Extended,
        )
        .unwrap();

        let globals = Globals::standard();
        let module = Module::new();
        let mut eval = Evaluator::new(&module);
        eval.enable_profile(&ProfileMode::HeapSummaryAllocated)
            .unwrap();

        eval.eval_module(ast, &globals).unwrap();

        let stacks = AggregateHeapProfileInfo::collect(eval.heap(), None);

        let info = HeapSummaryByFunction::init(&stacks);

        // Run the assertions.
        info.gen_csv();

        assert!(info.unused_capacity > 0);

        let total = FuncInfo::merge(info.info.values());
        // from non-drop heap
        assert_eq!(total.alloc.get("string").unwrap().count, 1);
        // from drop heap
        assert_eq!(total.alloc.get("dict").unwrap().count, 1);
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

pub(crate) mod string;

use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::marker;
use std::ops::Deref;

use allocative::Allocative;
use dupe::Clone_;
use dupe::Copy_;
use dupe::Dupe_;
use serde::Serialize;

use crate as starlark;
use crate::any::AnyLifetime;
use crate::any::ProvidesStaticType;
use crate::cast;
use crate::cast::transmute;
use crate::coerce::Coerce;
use crate::coerce::CoerceKey;
use crate::typing::Ty;
use crate::values::alloc_value::AllocFrozenStringValue;
use crate::values::alloc_value::AllocStringValue;
use crate::values::int::PointerI32;
use crate::values::layout::avalue::AValue;
use crate::values::layout::heap::repr::AValueRepr;
use crate::values::string::StarlarkStr;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenHeap;
use crate::values::FrozenRef;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::StringValueLike;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;

/// [`Value`] wrapper which asserts contained value is of type `<T>`.
#[derive(Copy_, Clone_, Dupe_, ProvidesStaticType, Allocative)]
#[allocative(skip)] // Heap owns the value.
pub struct ValueTyped<'v, T: StarlarkValue<'v>>(Value<'v>, marker::PhantomData<&'v T>);
/// [`FrozenValue`] wrapper which asserts contained value is of type `<T>`.
#[derive(Copy_, Clone_, Dupe_, ProvidesStaticType, Allocative)]
#[allocative(skip)] // Heap owns the value.
pub struct FrozenValueTyped<'v, T: StarlarkValue<'v>>(FrozenValue, marker::PhantomData<&'v T>);

unsafe impl<'v, T: StarlarkValue<'v>> Coerce<ValueTyped<'v, T>> for ValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> CoerceKey<ValueTyped<'v, T>> for ValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> Coerce<Value<'v>> for ValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> CoerceKey<Value<'v>> for ValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> Coerce<FrozenValueTyped<'v, T>> for FrozenValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> CoerceKey<FrozenValueTyped<'v, T>>
    for FrozenValueTyped<'v, T>
{
}
unsafe impl<'v, T: StarlarkValue<'v>> Coerce<Value<'v>> for FrozenValueTyped<'v, T> {}
unsafe impl<'v, T: StarlarkValue<'v>> CoerceKey<Value<'v>> for FrozenValueTyped<'v, T> {}

unsafe impl<'v, 'f, T: StarlarkValue<'f>> Trace<'v> for FrozenValueTyped<'f, T> {
    fn trace(&mut self, _tracer: &Tracer<'v>) {}
}

impl<T: StarlarkValue<'static>> Freeze for FrozenValueTyped<'static, T> {
    type Frozen = Self;

    fn freeze(self, _freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        Ok(self)
    }
}

impl<'v, T: StarlarkValue<'v>> Debug for ValueTyped<'v, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_tuple("ValueTyped").field(&self.0).finish()
    }
}

impl<'v, T: StarlarkValue<'v>> Debug for FrozenValueTyped<'v, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_tuple("FrozenValueTyped").field(&self.0).finish()
    }
}

impl<'v, T: StarlarkValue<'v>> Display for ValueTyped<'v, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

impl<'v, T: StarlarkValue<'v>> Display for FrozenValueTyped<'v, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

impl<'v, T: StarlarkValue<'v>> Serialize for ValueTyped<'v, T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}

impl<'v, T: StarlarkValue<'v>> Serialize for FrozenValueTyped<'v, T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}

impl<'v, T: StarlarkValue<'v>> ValueTyped<'v, T> {
    /// Downcast.
    #[inline]
    pub fn new(value: Value<'v>) -> Option<ValueTyped<'v, T>> {
        value.downcast_ref::<T>()?;
        Some(ValueTyped(value, marker::PhantomData))
    }

    /// Construct typed value without checking the value is of type `<T>`.
    pub unsafe fn new_unchecked(value: Value<'v>) -> ValueTyped<'v, T> {
        debug_assert!(value.downcast_ref::<T>().is_some());
        ValueTyped(value, marker::PhantomData)
    }

    pub(crate) fn new_repr<A: AValue<'v, StarlarkValue = T>>(
        repr: &'v AValueRepr<A>,
    ) -> ValueTyped<'v, T> {
        ValueTyped(Value::new_repr(repr), marker::PhantomData)
    }

    /// Erase the type.
    pub fn to_value(self) -> Value<'v> {
        self.0
    }

    /// Get the reference to the pointed value.
    pub fn as_ref(self) -> &'v T {
        if PointerI32::type_is_pointer_i32::<T>() {
            unsafe {
                transmute!(
                    &PointerI32,
                    &T,
                    PointerI32::new(self.0.0.unpack_int_unchecked())
                )
            }
        } else {
            unsafe {
                self.0
                    .0
                    .unpack_ptr_no_int_unchecked()
                    .unpack_header_unchecked()
                    .payload::<T>()
            }
        }
    }
}

impl<'v, T: StarlarkValue<'v>> FrozenValueTyped<'v, T> {
    /// Construct `FrozenValueTyped` without checking that the value is of correct type.
    pub unsafe fn new_unchecked(value: FrozenValue) -> FrozenValueTyped<'v, T> {
        debug_assert!(value.downcast_ref::<T>().is_some());
        FrozenValueTyped(value, marker::PhantomData)
    }

    /// Downcast.
    #[inline]
    pub fn new(value: FrozenValue) -> Option<FrozenValueTyped<'v, T>> {
        value.downcast_ref::<T>()?;
        Some(FrozenValueTyped(value, marker::PhantomData))
    }

    pub(crate) fn new_repr<A: AValue<'v, StarlarkValue = T>>(
        repr: &'v AValueRepr<A>,
    ) -> FrozenValueTyped<'v, T> {
        // drop lifetime: `FrozenValue` is not (yet) parameterized with lifetime.
        let header = unsafe { cast::ptr_lifetime(&repr.header) };
        FrozenValueTyped(FrozenValue::new_ptr(header, A::IS_STR), marker::PhantomData)
    }

    /// Erase the type.
    pub fn to_frozen_value(self) -> FrozenValue {
        self.0
    }

    /// Convert to the value.
    #[inline]
    pub fn to_value(self) -> Value<'v> {
        self.0.to_value()
    }

    /// Convert to the value.
    pub fn to_value_typed(self) -> ValueTyped<'v, T> {
        unsafe { ValueTyped::new_unchecked(self.0.to_value()) }
    }

    /// Get the reference to the pointed value.
    #[inline]
    pub fn as_ref(self) -> &'v T {
        if PointerI32::type_is_pointer_i32::<T>() {
            unsafe {
                transmute!(
                    &PointerI32,
                    &T,
                    PointerI32::new(self.0.0.unpack_int_unchecked())
                )
            }
        } else if T::static_type_id() == StarlarkStr::static_type_id() {
            unsafe {
                self.0
                    .0
                    .unpack_ptr_no_int_unchecked()
                    .unpack_header_unchecked()
                    .payload::<T>()
            }
        } else {
            // When a frozen pointer is not str and not int,
            // unpack is does not need untagging.
            // This generates slightly more efficient machine code.
            unsafe {
                self.0
                    .0
                    .unpack_ptr_no_int_no_str_unchecked()
                    .unpack_header_unchecked()
                    .payload::<T>()
            }
        }
    }

    pub(crate) fn as_frozen_ref(self) -> FrozenRef<'v, T> {
        FrozenRef::new(self.as_ref())
    }
}

impl<'v> ValueTyped<'v, StarlarkStr> {
    /// Get the Rust string reference.
    pub fn as_str(self) -> &'v str {
        self.as_ref().as_str()
    }
}

impl<'v> FrozenValueTyped<'v, StarlarkStr> {
    /// Get the Rust string reference.
    pub fn as_str(self) -> &'v str {
        self.as_ref().as_str()
    }
}

unsafe impl<'v, T: StarlarkValue<'v>> Trace<'v> for ValueTyped<'v, T> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        tracer.trace(&mut self.0);
        // If type of value changed, dereference will produce the wrong object type.
        debug_assert!(self.0.downcast_ref::<T>().is_some());
    }
}

impl<'v, T: StarlarkValue<'v>> Deref for FrozenValueTyped<'v, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.as_ref()
    }
}

impl<'v, T: StarlarkValue<'v>> Deref for ValueTyped<'v, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.as_ref()
    }
}

impl<'v, T: StarlarkValue<'v>> StarlarkTypeRepr for ValueTyped<'v, T> {
    fn starlark_type_repr() -> Ty {
        T::starlark_type_repr()
    }
}

impl<'v, T: StarlarkValue<'v>> UnpackValue<'v> for ValueTyped<'v, T> {
    fn expected() -> String {
        T::get_type_value_static().as_str().to_owned()
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        ValueTyped::new(value)
    }
}

impl<'v, T: StarlarkValue<'v>> AllocValue<'v> for ValueTyped<'v, T> {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        self.0
    }
}

impl<'v> AllocStringValue<'v> for StringValue<'v> {
    fn alloc_string_value(self, _heap: &'v Heap) -> StringValue<'v> {
        self
    }
}

impl<'v, T: StarlarkValue<'v>> StarlarkTypeRepr for FrozenValueTyped<'v, T> {
    fn starlark_type_repr() -> Ty {
        T::starlark_type_repr()
    }
}

impl<'v, 'f, T: StarlarkValue<'f>> AllocValue<'v> for FrozenValueTyped<'f, T> {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        self.0.to_value()
    }
}

impl<'v> AllocStringValue<'v> for FrozenStringValue {
    fn alloc_string_value(self, _heap: &'v Heap) -> StringValue<'v> {
        self.to_string_value()
    }
}

impl<'v, T: StarlarkValue<'v>> AllocFrozenValue for FrozenValueTyped<'v, T> {
    fn alloc_frozen_value(self, _heap: &FrozenHeap) -> FrozenValue {
        self.0
    }
}

impl AllocFrozenStringValue for FrozenStringValue {
    fn alloc_frozen_string_value(self, _heap: &FrozenHeap) -> FrozenStringValue {
        self
    }
}

#[cfg(test)]
mod tests {
    use crate::values::int::PointerI32;
    use crate::values::FrozenValue;
    use crate::values::FrozenValueTyped;

    #[test]
    fn int() {
        let v = FrozenValueTyped::<PointerI32>::new(FrozenValue::testing_new_int(17)).unwrap();
        assert_eq!(17, v.as_ref().get().to_i32());
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::borrow::Borrow;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use dupe::Dupe;
use serde::Serialize;
use starlark_map::Equivalent;

use crate::coerce::Coerce;
use crate::coerce::CoerceKey;
use crate::collections::Hashed;
use crate::sealed::Sealed;
use crate::values::layout::static_string::VALUE_EMPTY_STRING;
use crate::values::string::StarlarkStr;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueTyped;

/// Convenient type alias.
///
/// We use `FrozenValueTyped<StarlarkStr>` often, but also we define more operations
/// on `FrozenValueTyped<StarlarkStr>` than on generic `FrozenValueTyped<T>`.
///
/// Note there's a macro `const_frozen_string!` to statically allocate `FrozenStringValue`:
///
/// ```
/// use starlark::const_frozen_string;
/// use starlark::values::{FrozenStringValue, FrozenValue};
///
/// let fv: FrozenStringValue = const_frozen_string!("magic");
/// assert_eq!("magic", fv.as_str());
/// ```
pub type FrozenStringValue = FrozenValueTyped<'static, StarlarkStr>;

/// Convenient type alias.
///
/// We use `ValueTyped<StarlarkStr>` often, but also we define more operations
/// on `ValueTyped<StarlarkStr>` than on generic `ValueTyped<T>`.
pub type StringValue<'v> = ValueTyped<'v, StarlarkStr>;

// TODO(nga): figure out how to make these operations generic over `T`.
unsafe impl<'v> Coerce<StringValue<'v>> for FrozenStringValue {}
unsafe impl<'v> CoerceKey<StringValue<'v>> for FrozenStringValue {}

impl Borrow<str> for FrozenStringValue {
    fn borrow(&self) -> &str {
        self.as_str()
    }
}

impl<'v> Borrow<str> for StringValue<'v> {
    fn borrow(&self) -> &str {
        self.as_str()
    }
}

impl<'v> Equivalent<FrozenStringValue> for StringValue<'v> {
    fn equivalent(&self, key: &FrozenStringValue) -> bool {
        *self == key.to_string_value()
    }
}

impl<'v> Equivalent<StringValue<'v>> for FrozenStringValue {
    fn equivalent(&self, key: &StringValue<'v>) -> bool {
        self.to_string_value() == *key
    }
}

impl<'v> Default for StringValue<'v> {
    fn default() -> Self {
        FrozenStringValue::default().to_string_value()
    }
}

impl Default for FrozenStringValue {
    fn default() -> Self {
        VALUE_EMPTY_STRING.erase()
    }
}

impl FrozenStringValue {
    /// Get self along with the hash.
    pub fn get_hashed(self) -> Hashed<Self> {
        Hashed::new_unchecked(self.get_hash(), self)
    }

    /// Get the [`FrozenValue`] along with the hash.
    pub fn get_hashed_value(self) -> Hashed<FrozenValue> {
        Hashed::new_unchecked(self.get_hash(), self.to_frozen_value())
    }

    /// Get the string reference along with the hash.
    pub fn get_hashed_str(self) -> Hashed<&'static str> {
        Hashed::new_unchecked(self.get_hash(), self.as_str())
    }
}

impl<'v> StringValue<'v> {
    /// Convert a value to a [`FrozenStringValue`] using a supplied [`Freezer`].
    pub fn freeze(self, freezer: &Freezer) -> anyhow::Result<FrozenStringValue> {
        Ok(unsafe { FrozenStringValue::new_unchecked(freezer.freeze(self.to_value())?) })
    }

    /// Get self along with the hash.
    pub fn get_hashed(self) -> Hashed<Self> {
        Hashed::new_unchecked(self.get_hash(), self)
    }

    /// Get the string reference along with the hash.
    pub fn get_hashed_str(self) -> Hashed<&'v str> {
        Hashed::new_unchecked(self.get_hash(), self.as_str())
    }

    /// Get the [`Value`] along with the hash.
    pub fn get_hashed_value(self) -> Hashed<Value<'v>> {
        Hashed::new_unchecked(self.get_hash(), self.to_value())
    }

    /// If this string value is frozen, return it.
    pub fn unpack_frozen(self) -> Option<FrozenStringValue> {
        self.to_value()
            .unpack_frozen()
            .map(|s| unsafe { FrozenStringValue::new_unchecked(s) })
    }
}

/// Common type for [`StringValue`] and [`FrozenStringValue`].
pub trait StringValueLike<'v>:
    Trace<'v>
    + Freeze<Frozen = FrozenStringValue>
    + CoerceKey<StringValue<'v>>
    + Display
    + Debug
    + Default
    + Eq
    + Ord
    + Copy
    + Clone
    + Dupe
    + Serialize
    + Allocative
    + Sealed
{
    /// Convert to a [`StringValue`].
    fn to_string_value(self) -> StringValue<'v>;

    /// Convert to a [`str`].
    fn as_str(self) -> &'v str {
        self.to_string_value().as_str()
    }
}

impl<'v> Sealed for StringValue<'v> {}

impl<'v> StringValueLike<'v> for StringValue<'v> {
    fn to_string_value(self) -> StringValue<'v> {
        self
    }
}

impl Sealed for FrozenStringValue {}

impl<'v> StringValueLike<'v> for FrozenStringValue {
    fn to_string_value(self) -> StringValue<'v> {
        self.to_value_typed()
    }
}

impl<'v1, 'v2> PartialEq<StringValue<'v1>> for StringValue<'v2> {
    fn eq(&self, other: &StringValue) -> bool {
        // `PartialEq` can be implemented for other types, not just for `StarlarkStr`.
        // But at the moment of writing, we don't guarantee that `PartialEq` for `T`
        // is consistent with `StarlarkValue::equals` for `T`.
        self.to_value().ptr_eq(other.to_value()) || self.as_ref() == other.as_ref()
    }
}

impl<'v> Eq for StringValue<'v> {}

impl PartialEq for FrozenStringValue {
    fn eq(&self, other: &Self) -> bool {
        self.to_value_typed() == other.to_value_typed()
    }
}

impl Eq for FrozenStringValue {}

impl<'v> PartialEq<StringValue<'v>> for FrozenStringValue {
    fn eq(&self, other: &StringValue<'v>) -> bool {
        &self.to_value_typed() == other
    }
}

impl<'v> PartialEq<FrozenStringValue> for StringValue<'v> {
    fn eq(&self, other: &FrozenStringValue) -> bool {
        self == &other.to_value_typed()
    }
}

impl<'v> Hash for StringValue<'v> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_ref().hash(state)
    }
}

impl Hash for FrozenStringValue {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_ref().hash(state)
    }
}

impl<'v> PartialOrd for StringValue<'v> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.as_ref().partial_cmp(other.as_ref())
    }
}

impl<'v> Ord for StringValue<'v> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_ref().cmp(other.as_ref())
    }
}

impl PartialOrd for FrozenStringValue {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.as_ref().partial_cmp(other.as_ref())
    }
}

impl Ord for FrozenStringValue {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_ref().cmp(other.as_ref())
    }
}

#[cfg(test)]
mod tests {
    use crate::collections::Hashed;
    use crate::values::FrozenHeap;
    use crate::values::FrozenStringValue;
    use crate::values::FrozenValue;
    use crate::values::Heap;
    use crate::values::StringValue;
    use crate::values::Value;
    use crate::values::ValueLike;

    #[test]
    fn test_string_hashes() {
        let expected = Hashed::new("xyz").hash();

        let heap = Heap::new();
        let s: StringValue = heap.alloc_str("xyz");
        assert_eq!(expected, Hashed::new(s).hash());
        let v: Value = heap.alloc_str("xyz").to_value();
        assert_eq!(expected, v.get_hashed().unwrap().hash());

        let heap = FrozenHeap::new();
        let fs: FrozenStringValue = heap.alloc_str("xyz");
        assert_eq!(expected, Hashed::new(fs).hash());
        let fv: FrozenValue = heap.alloc_str("xyz").to_frozen_value();
        assert_eq!(expected, fv.get_hashed().unwrap().hash());
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! A type [`StarlarkAny`] which can cheaply wrap any Rust value into a [`Value`].
//!
//! This is intended to be a low cost way to quickly wrap Rust types without much boilerplate.
//! For more advanced uses you should define an instance of [`StarlarkValue`].
//!
//! To use this type, usually you will return a [`StarlarkAny`] from a module function,
//! and consume it in another. As an example, we can cheaply wrap the
//! [`Duration`](std::time::Duration) type.
//!
//! ```
//! #[macro_use]
//! extern crate starlark;
//! # fn main() {
//! use starlark::assert::Assert;
//! use starlark::environment::GlobalsBuilder;
//! use starlark::values::Value;
//! use starlark::values::any::StarlarkAny;
//! use std::fmt;
//! use std::fmt::Display;
//! use std::time::Instant;
//!
//! #[derive(Debug)]
//! struct MyInstant(Instant);
//!
//! impl Display for MyInstant {
//!     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//!         write!(f, "{:?}", self)
//!     }
//! }
//!
//! #[starlark_module]
//! fn globals(builder: &mut GlobalsBuilder) {
//!     fn start() -> anyhow::Result<StarlarkAny<MyInstant>> {
//!         Ok(StarlarkAny::new(MyInstant(Instant::now())))
//!     }
//!
//!     fn elapsed(x: Value) -> anyhow::Result<String> {
//!         Ok(StarlarkAny::<MyInstant>::get(x).unwrap().0.elapsed().as_secs_f64().to_string())
//!     }
//! }
//!
//! let mut a = Assert::new();
//! a.globals_add(globals);
//! a.pass(r#"
//! duration = start()
//! y = 100
//! for x in range(100):
//!     y += x
//! print(elapsed(duration))
//! "#);
//! # }
//! ```

use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;

use allocative::Allocative;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::values::AllocValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Value;
use crate::values::ValueLike;

/// A type that can be passed around as a Starlark [`Value`], but in most
/// ways is uninteresting/opaque to Starlark. Constructed with
/// [`new`](StarlarkAny::new) and decomposed with [`get`](StarlarkAny::get).
#[derive(ProvidesStaticType, NoSerialize, Allocative)]
#[allocative(bound = "")]
pub struct StarlarkAny<T: Debug + Display + Send + Sync + 'static>(
    #[allocative(skip)] // TODO(nga): do not skip.
    pub  T,
);

#[starlark_value(type = "any")]
impl<'v, T: Debug + Display + Send + Sync + 'static> StarlarkValue<'v> for StarlarkAny<T> {}

impl<'v, T: Debug + Display + Send + Sync + 'static> AllocValue<'v> for StarlarkAny<T> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_simple(self)
    }
}

impl<T: Debug + Display + Send + Sync + 'static> Debug for StarlarkAny<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Debug::fmt(&self.0, f)
    }
}

impl<T: Debug + Display + Send + Sync + 'static> Display for StarlarkAny<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

impl<T: Debug + Display + Send + Sync + 'static> StarlarkAny<T> {
    /// Create a new [`StarlarkAny`] value. Such a value can be allocated on a heap with
    /// `heap.alloc(StarlarkAny::new(x))`.
    pub fn new(x: T) -> Self {
        StarlarkAny(x)
    }

    /// Extract from a [`Value`] that contains a [`StarlarkAny`] underneath. Returns [`None`] if
    /// the value does not match the expected type.
    pub fn get<'v>(x: Value<'v>) -> Option<&'v T> {
        let x: &StarlarkAny<T> = x.downcast_ref()?;
        Some(&x.0)
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Utility to heap allocate arrays of values.

use std::fmt;
use std::fmt::Debug;
use std::mem;
use std::ptr;

use allocative::Allocative;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::values::StarlarkValue;

#[derive(derive_more::Display, ProvidesStaticType, NoSerialize, Allocative)]
#[repr(C)]
#[display(fmt = "{:?}", self)]
#[allocative(bound = "")]
pub(crate) struct AnyArray<T: Debug + 'static> {
    pub(crate) len: usize,
    #[allocative(skip)] // TODO(nga): do not skip.
    content: [T; 0],
}

impl<T: Debug + 'static> AnyArray<T> {
    /// This function is unsafe because it does not initialize content array,
    /// but drops in in destructor.
    pub(crate) unsafe fn new(len: usize) -> AnyArray<T> {
        AnyArray { len, content: [] }
    }

    fn as_slice(&self) -> &[T] {
        unsafe { std::slice::from_raw_parts(self.content.as_ptr(), self.len) }
    }

    pub(crate) fn offset_of_content() -> usize {
        memoffset::offset_of!(Self, content)
    }
}

impl<T: Debug + 'static> Debug for AnyArray<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("AnyArray").field(&self.as_slice()).finish()
    }
}

// Currently we always allocate in `drop` heap, which is a bit inefficient.
// But we don't allocate much.
impl<T: Debug + 'static> Drop for AnyArray<T> {
    fn drop(&mut self) {
        unsafe {
            ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
                self.content.as_mut_ptr(),
                self.len,
            ));
        }
    }
}

// We rely on allocation of `AnyArray` in correct heap (drop or non-drop).
// This struct has zero length array of `T`, so check it actually declares it has drop.
const _: () = assert!(mem::needs_drop::<AnyArray<String>>());

#[starlark_value(type = "AnyArray")]
impl<'v, T: Debug + 'static> StarlarkValue<'v> for AnyArray<T> {}

#[cfg(test)]
mod tests {
    use std::sync::atomic::AtomicU32;
    use std::sync::atomic::Ordering;
    use std::sync::Arc;

    use dupe::Dupe;

    use crate::values::FrozenHeap;

    #[test]
    fn test_drop() {
        let counter1 = Arc::new(AtomicU32::new(0));
        let counter2 = Arc::new(AtomicU32::new(0));

        #[derive(Debug, Clone, Dupe)]
        struct IncrementOnDrop(Arc<AtomicU32>);

        impl Drop for IncrementOnDrop {
            fn drop(&mut self) {
                self.0.fetch_add(1, Ordering::SeqCst);
            }
        }

        let heap = FrozenHeap::new();
        let values = heap.alloc_any_slice_display_from_debug(&[
            IncrementOnDrop(counter1.dupe()),
            IncrementOnDrop(counter1.dupe()),
            IncrementOnDrop(counter2.dupe()),
            IncrementOnDrop(counter1.dupe()),
            IncrementOnDrop(counter2.dupe()),
        ]);

        assert_eq!(5, values.len());

        assert!(Arc::ptr_eq(&counter1, &values[0].0));
        assert!(Arc::ptr_eq(&counter1, &values[1].0));
        assert!(Arc::ptr_eq(&counter2, &values[2].0));
        assert!(Arc::ptr_eq(&counter1, &values[3].0));
        assert!(Arc::ptr_eq(&counter2, &values[4].0));

        // First drop happens when we clone values for the allocation.
        assert_eq!(3, counter1.load(Ordering::SeqCst));
        assert_eq!(2, counter2.load(Ordering::SeqCst));

        drop(heap);

        assert_eq!(6, counter1.load(Ordering::SeqCst));
        assert_eq!(4, counter2.load(Ordering::SeqCst));
    }

    #[test]
    fn test_allocation_size() {
        let heap = FrozenHeap::new();
        heap.alloc_any_slice_display_from_debug(&[1, 2, 3]);
        let quake = heap.alloc_str("quake");
        // Test array allocation did not overwrite the string.
        assert_eq!(quake.as_str(), "quake");
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Array type used in implementation of `List`.
//!
//! This object is used internally, and not visible outside of `starlark` crate.

use std::cell::UnsafeCell;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::ptr;
use std::slice;

use allocative::Allocative;
use serde::Serialize;
use starlark_derive::starlark_value;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::private::Private;
use crate::values::types::list::value::display_list;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Value;

/// Fixed-capacity list.
///
/// Mutation operations (like `insert`) panic if there's not enough remaining capacity.
#[derive(ProvidesStaticType, Allocative)]
#[repr(C)]
pub(crate) struct Array<'v> {
    // We use `u32` to save some space.
    // `UnsafeCell` is to make this type `Sync` to put an empty array instance into
    // a static variable.
    /// Current number of elements in the array.
    #[allocative(skip)]
    len: UnsafeCell<u32>,
    /// Fixed capacity.
    capacity: u32,
    /// Number of active iterators: when iterator count is non-zero, we cannot modify the array.
    /// Note we track the number of iterators here, but we don't prevent modification here
    /// while iterator count is non-zero: `List` does that.
    // TODO(nga): consider merging this field with `capacity` to save space:
    //   `iter_count_cap >= 0` means capacity
    //   `iter_count_cap < 0` means `-iter_count_cap` active iterators,
    //     and iterator object holds the capacity.
    #[allocative(skip)]
    iter_count: UnsafeCell<u32>,
    content: [Value<'v>; 0],
}

impl<'v> Debug for Array<'v> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("Array")
            .field("len", &self.len())
            .field("capacity", &self.capacity)
            .field("iter_count", unsafe { &*self.iter_count.get() })
            .field("content", &self.content())
            .finish()
    }
}

impl<'v> Array<'v> {
    pub(crate) fn offset_of_content() -> usize {
        memoffset::offset_of!(Self, content)
    }

    /// Create an array with specified length and capacity.
    /// This function is `unsafe` because it does not populate array content.
    pub(crate) const unsafe fn new(len: u32, capacity: u32) -> Array<'v> {
        debug_assert!(len <= capacity);
        Array {
            len: UnsafeCell::new(len),
            capacity,
            iter_count: UnsafeCell::new(0),
            content: [],
        }
    }

    pub(crate) fn len(&self) -> usize {
        unsafe { *self.len.get() as usize }
    }

    pub(crate) fn capacity(&self) -> usize {
        self.capacity as usize
    }

    fn is_statically_allocated(&self) -> bool {
        self.capacity == 0
    }

    /// Remaining capacity in the array.
    pub(crate) fn remaining_capacity(&self) -> usize {
        debug_assert!(self.capacity as usize >= self.len());
        // This function is called only when modifying.
        debug_assert!(!self.iter_count_is_non_zero());
        self.capacity as usize - self.len()
    }

    /// Get an array content.
    ///
    /// Note this function takes `&self` not `&mut self`, so it does not prevent
    /// modification of the array while holding content reference.
    /// This is memory-safe, because we never overwrite content with
    /// invalid `Value` values.
    pub(crate) fn content(&self) -> &[Value<'v>] {
        unsafe { slice::from_raw_parts(self.content.as_ptr(), self.len()) }
    }

    pub(crate) fn content_mut(&mut self) -> &mut [Value<'v>] {
        unsafe { slice::from_raw_parts_mut(self.content.as_mut_ptr(), self.len()) }
    }

    /// Pointer to an element at given offset.
    fn ptr_at(&self, index: usize) -> *const Value<'v> {
        unsafe { self.content.as_ptr().add(index) }
    }

    /// Pointer to an element at given offset.
    fn mut_ptr_at(&self, index: usize) -> *mut Value<'v> {
        self.ptr_at(index) as *mut Value
    }

    unsafe fn get_unchecked(&self, index: usize) -> Value<'v> {
        debug_assert!(index < self.len());
        *self.ptr_at(index)
    }

    pub(crate) fn set_at(&self, index: usize, value: Value<'v>) {
        debug_assert!(!self.iter_count_is_non_zero());
        assert!(index < self.len());
        unsafe {
            *self.mut_ptr_at(index) = value;
        }
    }

    /// Has at leave one iterator over the array.
    pub(crate) fn iter_count_is_non_zero(&self) -> bool {
        unsafe { *self.iter_count.get() != 0 }
    }

    pub(crate) fn inc_iter_count(&self) {
        // When array is statically allocated, `iter_count` variable
        // is shared between threads.
        if !self.is_statically_allocated() {
            unsafe {
                *self.iter_count.get() += 1;
            };
        }
    }

    pub(crate) fn dec_iter_count(&self) {
        unsafe {
            if !self.is_statically_allocated() {
                debug_assert!(*self.iter_count.get() >= 1);
                *self.iter_count.get() -= 1;
            } else {
                debug_assert!(*self.iter_count.get() == 0);
            }
        }
    }

    pub(crate) fn insert(&self, index: usize, value: Value<'v>) {
        assert!(self.remaining_capacity() >= 1);
        assert!(index <= self.len());
        unsafe {
            ptr::copy(
                self.ptr_at(index),
                self.mut_ptr_at(index + 1),
                self.len() - index,
            );
            *self.mut_ptr_at(index) = value;
            *self.len.get() += 1;
        }
    }

    pub(crate) fn push(&self, value: Value<'v>) {
        assert!(self.remaining_capacity() >= 1);
        unsafe {
            *self.mut_ptr_at(self.len()) = value;
            *self.len.get() += 1;
        }
    }

    /// `self.extend_from_within(..)`.
    pub(crate) fn double(&self) {
        assert!(self.remaining_capacity() >= self.len());
        unsafe {
            ptr::copy_nonoverlapping(self.ptr_at(0), self.mut_ptr_at(self.len()), self.len());
            *self.len.get() *= 2;
        }
    }

    pub(crate) fn extend(&self, iter: impl IntoIterator<Item = Value<'v>>) {
        for item in iter {
            self.push(item);
        }
    }

    pub(crate) fn extend_from_slice(&self, slice: &[Value<'v>]) {
        assert!(self.remaining_capacity() >= slice.len());
        unsafe {
            ptr::copy_nonoverlapping(slice.as_ptr(), self.mut_ptr_at(self.len()), slice.len());
            *self.len.get() += slice.len() as u32;
        }
    }

    pub(crate) fn clear(&self) {
        debug_assert!(!self.iter_count_is_non_zero());
        unsafe {
            *self.len.get() = 0;
        }
    }

    pub(crate) fn remove(&self, index: usize) -> Value<'v> {
        debug_assert!(!self.iter_count_is_non_zero());
        unsafe {
            assert!(index < self.len());
            let r = self.get_unchecked(index);
            ptr::copy(
                self.ptr_at(index + 1),
                self.mut_ptr_at(index),
                self.len() - 1 - index,
            );
            *self.len.get() -= 1;
            r
        }
    }
}

/// This type is not visible to user, but still add meaningful `Display` for consistency.
impl<'v> Display for Array<'v> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "array(")?;
        display_list(self.content(), f)?;
        write!(f, ", cap={})", self.capacity)?;
        Ok(())
    }
}

#[starlark_value(type = "array")]
impl<'v> StarlarkValue<'v> for Array<'v> {
    fn is_special(_: Private) -> bool
    where
        Self: Sized,
    {
        true
    }

    fn length(&self) -> anyhow::Result<i32> {
        Ok(self.len() as i32)
    }

    unsafe fn iter_next(&self, index: usize, _heap: &'v Heap) -> Option<Value<'v>> {
        debug_assert!(self.len() == 0 || self.iter_count_is_non_zero());
        self.content().get(index).copied()
    }

    unsafe fn iter_stop(&self) {
        self.dec_iter_count();
    }

    unsafe fn iter_size_hint(&self, index: usize) -> (usize, Option<usize>) {
        debug_assert!(index <= self.len());
        let rem = self.len() - index;
        (rem, Some(rem))
    }
}

impl<'v> Serialize for Array<'v> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_seq(self.content().iter())
    }
}

#[cfg(test)]
mod tests {
    use crate::values::Heap;
    use crate::values::Value;

    #[test]
    fn debug() {
        let heap = Heap::new();
        let array = heap.alloc_array(10);
        array.push(heap.alloc(23));
        // Just check it does not crash.
        drop(array.to_string());
    }

    #[test]
    fn display() {
        let heap = Heap::new();
        let array = heap.alloc_array(10);
        array.push(heap.alloc(29));
        array.push(Value::new_none());
        assert_eq!("array([29, None], cap=10)", array.to_string());
    }

    #[test]
    fn push() {
        let heap = Heap::new();
        let array = heap.alloc_array(10);
        array.push(heap.alloc(17));
        array.push(heap.alloc(19));
        assert_eq!(heap.alloc(19), array.content()[1]);
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! The boolean type (`False` and `True`).
//!
//! Can be created with [`new_bool`](Value::new_bool) and unwrapped with [`unpack_bool`](Value::unpack_bool).
//! Unlike most Starlark values, these aren't actually represented on the [`Heap`], but as special values.

use std::cmp::Ordering;
use std::fmt;
use std::fmt::Display;
use std::hash::Hasher;

use allocative::Allocative;
use serde::Serialize;
use starlark_derive::starlark_value;
use starlark_derive::StarlarkDocs;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::collections::StarlarkHashValue;
use crate::collections::StarlarkHasher;
use crate::private::Private;
use crate::typing::Ty;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueError;

/// The result of calling `type()` on booleans.
pub const BOOL_TYPE: &str = "bool";

// We have to alias bool so we can have a Display that uses True/False.
#[derive(ProvidesStaticType, Debug, Serialize, StarlarkDocs, Allocative)]
#[starlark_docs(builtin = "standard")]
#[serde(transparent)]
pub(crate) struct StarlarkBool(pub(crate) bool);

impl Display for StarlarkBool {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.0 {
            write!(f, "True")
        } else {
            write!(f, "False")
        }
    }
}

impl<'v> AllocValue<'v> for bool {
    fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
        Value::new_bool(self)
    }
}

impl AllocFrozenValue for bool {
    fn alloc_frozen_value(self, _heap: &FrozenHeap) -> FrozenValue {
        FrozenValue::new_bool(self)
    }
}

impl StarlarkTypeRepr for bool {
    fn starlark_type_repr() -> Ty {
        StarlarkBool::get_type_starlark_repr()
    }
}

impl UnpackValue<'_> for bool {
    fn unpack_value(value: Value) -> Option<Self> {
        value.unpack_bool()
    }
}

/// Define the bool type
#[starlark_value(type = BOOL_TYPE)]
impl<'v> StarlarkValue<'v> for StarlarkBool {
    fn is_special(_: Private) -> bool
    where
        Self: Sized,
    {
        true
    }

    fn collect_repr(&self, s: &mut String) {
        // repr() for bool is quite hot, so optimise it
        if self.0 {
            s.push_str("True")
        } else {
            s.push_str("False")
        }
    }

    fn to_bool(&self) -> bool {
        self.0
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        hasher.write_u8(if self.0 { 1 } else { 0 });
        Ok(())
    }

    fn get_hash(&self, _private: Private) -> anyhow::Result<StarlarkHashValue> {
        // These constants are just two random numbers.
        Ok(StarlarkHashValue::new_unchecked(if self.0 {
            0xa4acba08
        } else {
            0x71e8ba71
        }))
    }

    fn equals(&self, other: Value) -> anyhow::Result<bool> {
        // We always compare values for pointer equality before calling `equals`,
        // and there are only two instances of `StarlarkBool`.
        // So if we are here, values are definitely not equal.
        debug_assert!(!matches!(other.unpack_bool(), Some(other) if other == self.0));
        Ok(false)
    }

    fn compare(&self, other: Value) -> anyhow::Result<Ordering> {
        if let Some(other) = other.unpack_bool() {
            Ok(self.0.cmp(&other))
        } else {
            ValueError::unsupported_with(self, "<>", other)
        }
    }

    fn typechecker_ty(&self, _private: Private) -> Option<Ty> {
        Some(Ty::bool())
    }
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Fixed set enumerations, with runtime checking of validity.
//!
//! Calling `enum()` produces an [`EnumType`]. Calling the [`EnumType`] creates an [`EnumValue`].
//!
//! The implementation ensures that each value of the enumeration is only stored once,
//! so they may also provide (modest) memory savings. Created in starlark with the
//! `enum` function:
//!
//! ```
//! # starlark::assert::pass(r#"
//! Colors = enum("Red", "Green", "Blue")
//! val = Colors("Red")
//! assert_eq(val.value, "Red")
//! assert_eq(val.index, 0)
//! assert_eq(Colors[0], val)
//! assert_eq(Colors.type, "Colors")
//! assert_eq([v.value for v in Colors], ["Red", "Green", "Blue"])
//! # "#);
//! ```

use std::cell::UnsafeCell;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;

use allocative::Allocative;
use derivative::Derivative;
use display_container::fmt_container;
use either::Either;
use serde::Serialize;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;
use starlark_map::Equivalent;
use thiserror::Error;

use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::collections::SmallMap;
use crate::collections::StarlarkHasher;
use crate::environment::Methods;
use crate::environment::MethodsBuilder;
use crate::environment::MethodsStatic;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::starlark_complex_value;
use crate::starlark_complex_values;
use crate::values::function::FUNCTION_TYPE;
use crate::values::index::convert_index;
use crate::values::types::exported_name::ExportedName;
use crate::values::types::exported_name::FrozenExportedName;
use crate::values::types::exported_name::MutableExportedName;
use crate::values::Freeze;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueLike;
use crate::{self as starlark};

#[derive(Error, Debug)]
enum EnumError {
    #[error("enum values must all be distinct, but repeated `{0}`")]
    DuplicateEnumValue(String),
    #[error("Unknown enum element `{0}`, given to `{1}`")]
    InvalidElement(String, String),
}

/// The type of an enumeration, created by `enum()`.
#[derive(
    Debug,
    Trace,
    Coerce,
    Freeze,
    NoSerialize,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "extension")]
#[repr(C)]
// Deliberately store fully populated values
// for each entry, so we can produce enum values with zero allocation.
pub struct EnumTypeGen<V, Typ: ExportedName> {
    typ: Typ,
    // The key is the value of the enumeration
    // The value is a value of type EnumValue
    #[allocative(skip)] // TODO(nga): do not skip.
    elements: UnsafeCell<SmallMap<V, V>>,
}

unsafe impl<V: Send, Typ: ExportedName + Send> Send for EnumTypeGen<V, Typ> {}
unsafe impl<V: Sync, Typ: ExportedName + Sync> Sync for EnumTypeGen<V, Typ> {}

impl<V: Display, Typ: ExportedName> Display for EnumTypeGen<V, Typ> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_container(f, "enum(", ")", self.elements().iter().map(|(k, _v)| k))
    }
}

/// Unfrozen enum type.
pub type EnumType<'v> = EnumTypeGen<Value<'v>, MutableExportedName>;
/// Frozen enum type.
pub type FrozenEnumType = EnumTypeGen<FrozenValue, FrozenExportedName>;

/// A value from an enumeration.
#[derive(
    Clone,
    Derivative,
    Trace,
    Coerce,
    Freeze,
    ProvidesStaticType,
    Allocative
)]
#[repr(C)]
#[derivative(Debug)]
pub struct EnumValueGen<V> {
    // Must ignore value.typ or type.elements, since they are circular
    #[derivative(Debug = "ignore")]
    typ: V, // Must be EnumType it points back to (so it can get the type)
    value: V,   // The value of this enumeration
    index: i32, // The index in the enumeration
}

impl<V: Display> Display for EnumValueGen<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.value.fmt(f)
    }
}

starlark_complex_values!(EnumType);
starlark_complex_value!(pub EnumValue);

impl<'v> EnumType<'v> {
    pub(crate) fn new(elements: Vec<Value<'v>>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        // We are constructing the enum and all elements in one go.
        // They both point at each other, which adds to the complexity.
        let typ = heap.alloc(EnumType {
            typ: MutableExportedName::default(),
            elements: UnsafeCell::new(SmallMap::new()),
        });

        let mut res = SmallMap::with_capacity(elements.len());
        for (i, x) in elements.iter().enumerate() {
            let v = heap.alloc(EnumValue {
                typ,
                index: i as i32,
                value: *x,
            });
            if res.insert_hashed(x.get_hashed()?, v).is_some() {
                return Err(EnumError::DuplicateEnumValue(x.to_string()).into());
            }
        }

        // Here we tie the cycle
        let t = typ.downcast_ref::<EnumType>().unwrap();
        unsafe {
            // SAFETY: we own unique reference to `t`.
            *t.elements.get() = res;
        }
        Ok(typ)
    }
}

impl<'v, V: ValueLike<'v>> EnumValueGen<V> {
    /// The result of calling `type()` on an enum value.
    pub const TYPE: &'static str = "enum";

    fn get_enum_type(&self) -> Either<&'v EnumType<'v>, &'v FrozenEnumType> {
        // Safe to unwrap because we always ensure typ is EnumType
        EnumType::from_value(self.typ.to_value()).unwrap()
    }
}

impl<V, Typ: ExportedName> EnumTypeGen<V, Typ> {
    pub(crate) fn elements(&self) -> &SmallMap<V, V> {
        // Safe because we never mutate the elements after construction.
        unsafe { &*self.elements.get() }
    }
}

impl<'v, Typ, V> EnumTypeGen<V, Typ>
where
    Value<'v>: Equivalent<V>,
    Typ: ExportedName,
    V: ValueLike<'v> + 'v,
{
    pub(crate) fn construct(&self, val: Value<'v>) -> anyhow::Result<V> {
        match self.elements().get_hashed_by_value(val.get_hashed()?) {
            Some(v) => Ok(*v),
            None => Err(EnumError::InvalidElement(val.to_str(), self.to_string()).into()),
        }
    }
}

#[starlark_value(type = FUNCTION_TYPE)]
impl<'v, Typ: Allocative + 'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for EnumTypeGen<V, Typ>
where
    Self: ProvidesStaticType<'v>,
    Typ: ExportedName,
    Value<'v>: Equivalent<V>,
{
    fn invoke(
        &self,
        _me: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        args.no_named_args()?;
        let val = args.positional1(eval.heap())?;
        Ok(self.construct(val)?.to_value())
    }

    fn length(&self) -> anyhow::Result<i32> {
        Ok(self.elements().len() as i32)
    }

    fn at(&self, index: Value, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let i = convert_index(index, self.elements().len() as i32)? as usize;
        // Must be in the valid range since convert_index checks that, so just unwrap
        Ok(self
            .elements()
            .get_index(i)
            .map(|x| *x.1)
            .unwrap()
            .to_value())
    }

    unsafe fn iterate(&self, me: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(me)
    }

    unsafe fn iter_size_hint(&self, index: usize) -> (usize, Option<usize>) {
        debug_assert!(index <= self.elements().len());
        let rem = self.elements().len() - index;
        (rem, Some(rem))
    }

    unsafe fn iter_next(&self, index: usize, _heap: &'v Heap) -> Option<Value<'v>> {
        self.elements().values().nth(index).map(|v| v.to_value())
    }

    unsafe fn iter_stop(&self) {}

    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(enum_type_methods)
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        fn eq<'v>(
            a: &EnumTypeGen<impl ValueLike<'v>, impl ExportedName>,
            b: &EnumTypeGen<impl ValueLike<'v>, impl ExportedName>,
        ) -> anyhow::Result<bool> {
            if a.typ.borrow() != b.typ.borrow() {
                return Ok(false);
            }
            if a.elements().len() != b.elements().len() {
                return Ok(false);
            }
            for (k1, k2) in a.elements().keys().zip(b.elements().keys()) {
                if !k1.to_value().equals(k2.to_value())? {
                    return Ok(false);
                }
            }
            Ok(true)
        }

        match EnumType::from_value(other) {
            Some(Either::Left(other)) => eq(self, other),
            Some(Either::Right(other)) => eq(self, other),
            _ => Ok(false),
        }
    }

    fn export_as(&self, variable_name: &str, _eval: &mut Evaluator<'v, '_>) {
        self.typ.try_export_as(variable_name);
    }
}

#[starlark_module]
fn enum_type_methods(builder: &mut MethodsBuilder) {
    #[starlark(attribute)]
    fn r#type<'v>(this: Value, heap: &Heap) -> anyhow::Result<Value<'v>> {
        let this = EnumType::from_value(this).unwrap();
        let typ = match this {
            Either::Left(x) => x.typ.borrow(),
            Either::Right(x) => x.typ.borrow(),
        };
        Ok(heap.alloc(typ.as_ref().map_or(EnumValue::TYPE, |n| n.as_str())))
    }

    fn values<'v>(this: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let this = EnumType::from_value(this).unwrap();
        match this {
            Either::Left(x) => Ok(heap.alloc_list_iter(x.elements().keys().copied())),
            Either::Right(x) => Ok(heap.alloc_list_iter(x.elements().keys().map(|x| x.to_value()))),
        }
    }
}

#[starlark_value(type = EnumValue::TYPE)]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for EnumValueGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn matches_type(&self, ty: &str) -> bool {
        if ty == EnumValue::TYPE {
            return true;
        }
        match self.get_enum_type() {
            Either::Left(x) => x.typ.equal_to(ty),
            Either::Right(x) => x.typ.equal_to(ty),
        }
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        match EnumValue::from_value(other) {
            Some(other) if self.typ.equals(other.typ)? => Ok(self.index == other.index),
            _ => Ok(false),
        }
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.value.write_hash(hasher)
    }

    fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option<Value<'v>> {
        match attribute {
            "index" => Some(heap.alloc(self.index)),
            "value" => Some(self.value.to_value()),
            _ => None,
        }
    }

    fn dir_attr(&self) -> Vec<String> {
        vec!["index".to_owned(), "value".to_owned()]
    }
}

impl<'v, V: ValueLike<'v>> Serialize for EnumValueGen<V> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.value.serialize(serializer)
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Utilities for implementing [`StarlarkValue::export_as`](crate::values::StarlarkValue::export_as).
//!
//! See the trait [`ExportedName`](crate::values::exported_name::ExportedName) for more details.

use std::cell::Ref;
use std::cell::RefCell;
use std::fmt::Debug;
use std::fmt::Display;

use allocative::Allocative;
use either::Either;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::Trace;

/// Borrowed [`ExportedName`].
pub struct BorrowedExportedName<'a> {
    either: Either<&'a str, Ref<'a, str>>,
}

impl<'a> BorrowedExportedName<'a> {
    /// Dereference to a string slice.
    pub fn as_str(&self) -> &str {
        match &self.either {
            Either::Left(s) => s,
            Either::Right(s) => s,
        }
    }
}

impl<'a> PartialEq for BorrowedExportedName<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.as_str() == other.as_str()
    }
}

impl<'a> Eq for BorrowedExportedName<'a> {}

/// Type parameter for types which need to be exported with a name.
///
/// Typical use case is this:
///
/// ```
/// use allocative::Allocative;
/// use starlark::eval::Evaluator;
/// use starlark::values::exported_name::ExportedName;
/// use starlark::values::StarlarkValue;
/// use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value};
///
/// #[derive(Debug, NoSerialize, ProvidesStaticType, Allocative, derive_more::Display)]
/// #[display(fmt = "{:?}", "self")]
/// struct MyStruct<T: ExportedName + 'static> {
///    name: T,
/// }
///
/// #[starlark_value(type = "MyStruct")]
/// impl<'v, T: ExportedName> StarlarkValue<'v> for MyStruct<T> {
///   fn export_as(&self, variable_name: &str, _eval: &mut Evaluator<'v, '_>) {
///        self.name.try_export_as(variable_name);
///    }
/// }
/// ```
///
/// This is an utility implementing common pattern,
/// but it is completely optional when implementing
/// [`StarlarkValue::export_as`](crate::values::StarlarkValue::export_as).
pub trait ExportedName:
    Debug + Display + Freeze<Frozen = FrozenExportedName> + Allocative + 'static
{
    /// Borrow the name.
    fn borrow(&self) -> Option<BorrowedExportedName>;

    /// Name is equal to the given string.
    fn equal_to(&self, rhs: &str) -> bool;

    /// Try update the name.
    ///
    /// This operation is no-op, if
    /// - the name is already set (first export wins)
    /// - the name is frozen
    fn try_export_as(&self, name: &str);
}

/// Type parameter for unfrozen types.
#[derive(Debug, Allocative, Default, Trace, ProvidesStaticType)]
pub struct MutableExportedName {
    name: RefCell<Option<String>>,
}

/// Type parameter for frozen types.
#[derive(Debug, Allocative, Default, Trace, Freeze, ProvidesStaticType)]
pub struct FrozenExportedName {
    name: Option<String>,
}

impl Freeze for MutableExportedName {
    type Frozen = FrozenExportedName;

    fn freeze(self, _freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        Ok(FrozenExportedName {
            name: self.name.into_inner(),
        })
    }
}

impl Display for MutableExportedName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.name.borrow().as_deref().unwrap_or("<not_exported>")
        )
    }
}

impl Display for FrozenExportedName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name.as_deref().unwrap_or("<not_exported>"))
    }
}

impl ExportedName for MutableExportedName {
    fn borrow(&self) -> Option<BorrowedExportedName> {
        let r = Ref::filter_map(self.name.borrow(), |n| n.as_deref());
        r.ok().map(|n| BorrowedExportedName {
            either: Either::Right(n),
        })
    }

    fn equal_to(&self, rhs: &str) -> bool {
        self.name.borrow().as_deref() == Some(rhs)
    }

    fn try_export_as(&self, name: &str) {
        self.name
            .borrow_mut()
            .get_or_insert_with(|| name.to_owned());
    }
}

impl ExportedName for FrozenExportedName {
    fn borrow(&self) -> Option<BorrowedExportedName> {
        self.name.as_deref().map(|n| BorrowedExportedName {
            either: Either::Left(n),
        })
    }

    fn equal_to(&self, rhs: &str) -> bool {
        self.name.as_deref() == Some(rhs)
    }

    fn try_export_as(&self, name: &str) {
        let _ignore = name;
    }
}


/*
 * Copyright 2018 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! The floating point number type (3.14, 4e2).

use std::cmp::Ordering;
use std::fmt;
use std::fmt::Display;
use std::fmt::Write;
use std::hash::Hasher;

use allocative::Allocative;
use dupe::Dupe;
use serde::Serialize;
use starlark_derive::starlark_value;
use starlark_derive::StarlarkDocs;
use starlark_map::StarlarkHashValue;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::collections::StarlarkHasher;
use crate::private::Private;
use crate::typing::Ty;
use crate::values::num::NumRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

const WRITE_PRECISION: usize = 6;

fn write_non_finite<W: fmt::Write>(output: &mut W, f: f64) -> fmt::Result {
    debug_assert!(f.is_nan() || f.is_infinite());
    if f.is_nan() {
        write!(output, "nan")
    } else {
        write!(
            output,
            "{}inf",
            if f.is_sign_positive() { "+" } else { "-" }
        )
    }
}

pub(crate) fn write_decimal<W: fmt::Write>(output: &mut W, f: f64) -> fmt::Result {
    if !f.is_finite() {
        write_non_finite(output, f)
    } else {
        write!(output, "{:.prec$}", f, prec = WRITE_PRECISION)
    }
}

pub(crate) fn write_scientific<W: fmt::Write>(
    output: &mut W,
    f: f64,
    exponent_char: char,
    strip_trailing_zeros: bool,
) -> fmt::Result {
    if !f.is_finite() {
        write_non_finite(output, f)
    } else {
        let abs = f.abs();
        let exponent = if f == 0.0 {
            0
        } else {
            abs.log10().floor() as i32
        };
        let normal = if f == 0.0 {
            0.0
        } else {
            abs / 10f64.powf(exponent as f64)
        };

        // start with "-" for a negative number
        if f.is_sign_negative() {
            output.write_char('-')?
        }

        // use the whole integral part of normal (a single digit)
        output.write_fmt(format_args!("{}", normal.trunc()))?;

        // calculate the fractional tail for given precision
        let mut tail = (normal.fract() * 10f64.powf(WRITE_PRECISION as f64)).round() as u64;
        let mut rev_tail = [0u8; WRITE_PRECISION];
        let mut rev_tail_len = 0;
        let mut removing_trailing_zeros = strip_trailing_zeros;
        for _ in 0..WRITE_PRECISION {
            let tail_digit = tail % 10;
            if tail_digit != 0 || !removing_trailing_zeros {
                removing_trailing_zeros = false;
                rev_tail[rev_tail_len] = tail_digit as u8;
                rev_tail_len += 1;
            }
            tail /= 10;
        }

        // write fractional part
        if rev_tail_len != 0 {
            output.write_char('.')?;
        }
        for digit in rev_tail[0..rev_tail_len].iter().rev() {
            output.write_char((b'0' + digit) as char)?;
        }

        // add exponent part
        output.write_char(exponent_char)?;
        output.write_fmt(format_args!("{:+03}", exponent))
    }
}

pub(crate) fn write_compact<W: fmt::Write>(
    output: &mut W,
    f: f64,
    exponent_char: char,
) -> fmt::Result {
    if !f.is_finite() {
        write_non_finite(output, f)
    } else {
        let abs = f.abs();
        let exponent = if f == 0.0 {
            0
        } else {
            abs.log10().floor() as i32
        };

        if exponent.abs() >= WRITE_PRECISION as i32 {
            // use scientific notation if exponent is outside of our precision (but strip 0s)
            write_scientific(output, f, exponent_char, true)
        } else if f.fract() == 0.0 {
            // make sure there's a fractional part even if the number doesn't have it
            output.write_fmt(format_args!("{:.1}", f))
        } else {
            // rely on the built-in formatting otherwise
            output.write_fmt(format_args!("{}", f))
        }
    }
}

/// Runtime representation of Starlark `float` type.
#[derive(
    Clone,
    Dupe,
    Copy,
    Debug,
    ProvidesStaticType,
    Serialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "standard")]
#[serde(transparent)]
pub struct StarlarkFloat(pub f64);

impl StarlarkFloat {
    /// The result of calling `type()` on floats.
    pub const TYPE: &'static str = "float";

    pub(crate) fn compare_impl(a: f64, b: f64) -> Ordering {
        // According to the spec (https://github.com/bazelbuild/starlark/blob/689f54426951638ef5b7c41a14d8fc48e65c5f77/spec.md#floating-point-numbers)
        // All NaN values compare equal to each other, but greater than any non-NaN float value.
        if let Some(ord) = a.partial_cmp(&b) {
            ord
        } else {
            a.is_nan().cmp(&b.is_nan())
        }
    }

    pub(crate) fn floor_div_impl(a: f64, b: f64) -> anyhow::Result<f64> {
        if b == 0.0 {
            Err(ValueError::DivisionByZero.into())
        } else {
            Ok((a / b).floor())
        }
    }

    pub(crate) fn percent_impl(a: f64, b: f64) -> anyhow::Result<f64> {
        if b == 0.0 {
            Err(ValueError::DivisionByZero.into())
        } else {
            let r = a % b;
            if r == 0.0 {
                Ok(0.0)
            } else {
                Ok(if b.signum() != r.signum() { r + b } else { r })
            }
        }
    }
}

impl StarlarkTypeRepr for f64 {
    fn starlark_type_repr() -> Ty {
        StarlarkFloat::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for StarlarkFloat {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_simple(self)
    }
}

impl AllocFrozenValue for StarlarkFloat {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_simple(self)
    }
}

impl<'v> AllocValue<'v> for f64 {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(StarlarkFloat(self))
    }
}

impl AllocFrozenValue for f64 {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc(StarlarkFloat(self))
    }
}

/// Allows only a float - an int will not be accepted.
impl<'v> UnpackValue<'v> for StarlarkFloat {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        Some(*value.downcast_ref::<StarlarkFloat>()?)
    }
}

/// Allows either a float or an int. If the int is not in the range of a float, it will lose precision.
impl<'v> UnpackValue<'v> for f64 {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        value.unpack_num().map(|x| x.as_float())
    }
}

impl Display for StarlarkFloat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write_compact(f, self.0, 'e')
    }
}

#[starlark_value(type = StarlarkFloat::TYPE)]
impl<'v> StarlarkValue<'v> for StarlarkFloat {
    fn equals(&self, other: Value) -> anyhow::Result<bool> {
        Ok(Some(NumRef::Float(self.0)) == other.unpack_num())
    }

    fn collect_repr(&self, s: &mut String) {
        write!(s, "{}", self).unwrap()
    }

    fn to_bool(&self) -> bool {
        self.0 != 0.0
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        hasher.write_u64(NumRef::from(self.0).get_hash_64());
        Ok(())
    }

    fn get_hash(&self, _private: Private) -> anyhow::Result<StarlarkHashValue> {
        Ok(NumRef::Float(self.0).get_hash())
    }

    fn plus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(*self))
    }

    fn minus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(StarlarkFloat(-self.0)))
    }

    fn add(&self, other: Value, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
        Some(Ok(heap.alloc(NumRef::Float(self.0) + other.unpack_num()?)))
    }

    fn sub(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            None => ValueError::unsupported_with(self, "-", other),
            Some(other) => Ok(heap.alloc(NumRef::Float(self.0) - other)),
        }
    }

    fn mul(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => Ok(heap.alloc(NumRef::Float(self.0) * other)),
            None => ValueError::unsupported_with(self, "*", other),
        }
    }

    fn div(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            None => ValueError::unsupported_with(self, "/", other),
            Some(other) => Ok(heap.alloc(NumRef::Float(self.0).div(other)?)),
        }
    }

    fn percent(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            Some(other) => Ok(heap.alloc(NumRef::Float(self.0).percent(other)?)),
            None => ValueError::unsupported_with(self, "%", other),
        }
    }

    fn floor_div(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match other.unpack_num() {
            None => ValueError::unsupported_with(self, "//", other),
            Some(other) => Ok(heap.alloc(NumRef::Float(self.0).floor_div(other)?)),
        }
    }

    fn compare(&self, other: Value) -> anyhow::Result<Ordering> {
        match other.unpack_num() {
            None => ValueError::unsupported_with(self, "compare", other),
            Some(other) => Ok(NumRef::Float(self.0).cmp(&other)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::assert;
    use crate::assert::Assert;

    fn non_finite(f: f64) -> String {
        let mut buf = String::new();
        write_non_finite(&mut buf, f).unwrap();
        buf
    }

    #[test]
    fn test_write_non_finite() {
        assert_eq!(non_finite(f64::NAN), "nan");
        assert_eq!(non_finite(f64::INFINITY), "+inf");
        assert_eq!(non_finite(f64::NEG_INFINITY), "-inf");
    }

    fn decimal(f: f64) -> String {
        let mut buf = String::new();
        write_decimal(&mut buf, f).unwrap();
        buf
    }

    #[test]
    fn test_write_decimal() {
        assert_eq!(decimal(f64::NAN), "nan");
        assert_eq!(decimal(f64::INFINITY), "+inf");
        assert_eq!(decimal(f64::NEG_INFINITY), "-inf");

        assert_eq!(decimal(0f64), "0.000000");
        assert_eq!(decimal(std::f64::consts::PI), "3.141593");
        assert_eq!(decimal(-std::f64::consts::E), "-2.718282");
        assert_eq!(decimal(1e10), "10000000000.000000");
    }

    fn scientific(f: f64) -> String {
        let mut buf = String::new();
        write_scientific(&mut buf, f, 'e', false).unwrap();
        buf
    }

    #[test]
    fn test_write_scientific() {
        assert_eq!(scientific(f64::NAN), "nan");
        assert_eq!(scientific(f64::INFINITY), "+inf");
        assert_eq!(scientific(f64::NEG_INFINITY), "-inf");

        assert_eq!(scientific(0f64), "0.000000e+00");
        assert_eq!(scientific(-0f64), "-0.000000e+00");
        assert_eq!(scientific(1.23e45), "1.230000e+45");
        assert_eq!(scientific(-3.14e-145), "-3.140000e-145");
        assert_eq!(scientific(1e300), "1.000000e+300");
    }

    fn compact(f: f64) -> String {
        let mut buf = String::new();
        write_compact(&mut buf, f, 'e').unwrap();
        buf
    }

    #[test]
    fn test_write_compact() {
        assert_eq!(compact(f64::NAN), "nan");
        assert_eq!(compact(f64::INFINITY), "+inf");
        assert_eq!(compact(f64::NEG_INFINITY), "-inf");

        assert_eq!(compact(0f64), "0.0");
        assert_eq!(compact(std::f64::consts::PI), "3.141592653589793");
        assert_eq!(compact(-std::f64::consts::E), "-2.718281828459045");
        assert_eq!(compact(1e10), "1e+10");
        assert_eq!(compact(1.23e45), "1.23e+45");
        assert_eq!(compact(-3.14e-145), "-3.14e-145");
        assert_eq!(compact(1e300), "1e+300");
    }

    #[test]
    fn test_arithmetic_operators() {
        assert::all_true(
            r#"
+1.0 == 1.0
-1.0 == 0. - 1.
1.0 + 2.0 == 3.0
1.0 - 2.0 == -1.0
2.0 * 3.0 == 6.0
5.0 / 2.0 == 2.5
5.0 % 3.0 == 2.0
5.0 // 2.0 == 2.0
"#,
        );
    }

    #[test]
    fn test_dictionary_key() {
        assert::pass(
            r#"
x = {0: 123}
assert_eq(x[0], 123)
assert_eq(x[0.0], 123)
assert_eq(x[-0.0], 123)
assert_eq(1 in x, False)
        "#,
        );
    }

    #[test]
    fn test_comparisons() {
        let mut a = Assert::new();
        // TODO(nga): fix and enable.
        a.disable_static_typechecking();
        a.all_true(
            r#"
+0.0 == -0.0
0.0 == 0
0 == 0.0
0 < 1.0
0.0 < 1
1 > 0.0
1.0 > 0
0.0 < float("nan")
float("+inf") < float("nan")
"#,
        );
    }

    #[test]
    fn test_comparisons_by_sorting() {
        assert::eq(
            "sorted([float('inf'), float('-inf'), float('nan'), 1e300, -1e300, 1.0, -1.0, 1, -1, 1e-300, -1e-300, 0, 0.0, float('-0.0'), 1e-300, -1e-300])",
            "[float('-inf'), -1e+300, -1.0, -1, -1e-300, -1e-300, 0, 0.0, -0.0, 1e-300, 1e-300, 1.0, 1, 1e+300, float('+inf'), float('nan')]",
        );
    }
}