/*
 * 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.
 */

//! Outside of `i32` range int.

mod convert;

use std::cmp::Ordering;
use std::hash::Hash;

use allocative::Allocative;
use num_bigint::BigInt;
use num_bigint::Sign;
use num_traits::cast::ToPrimitive;
use serde::Serialize;
use starlark_derive::starlark_value;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::collections::StarlarkHasher;
use crate::values::num::NumRef;
use crate::values::types::inline_int::InlineInt;
use crate::values::types::int_or_big::StarlarkInt;
use crate::values::types::int_or_big::StarlarkIntRef;
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;

/// `int` implementation for larger integers.
#[derive(
    Clone,
    Debug,
    Default,
    derive_more::Display,
    ProvidesStaticType,
    Ord,
    PartialOrd,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
#[display(fmt = "{}", value)]
pub struct StarlarkBigInt {
    /// `value` is strictly either smaller than `i32::MIN` or larger than `i32::MAX`.
    /// Many operation implementations depend on this fact.
    /// For example, `non_zero_int << positive_big_int` is considered to be overflow
    /// without checking the actual value of `positive_big_int`.
    #[allocative(skip)] // TODO(nga): do not skip.
    value: BigInt,
}

impl StarlarkBigInt {
    pub(crate) fn unchecked_new(value: BigInt) -> Self {
        debug_assert!(
            InlineInt::try_from(&value).is_err(),
            "BigInt must be outside of `InlineInt` range"
        );
        Self { value }
    }

    pub(crate) fn get(&self) -> &BigInt {
        &self.value
    }

    pub(crate) fn to_f64(&self) -> f64 {
        // `to_f64` is infallible.
        self.value.to_f64().unwrap()
    }

    pub(crate) fn to_i32(&self) -> Option<i32> {
        // Avoid calling `to_i32` if the value is known to be out of range.
        if InlineInt::smaller_than_i32() {
            let v = self.value.to_i32();
            if let Some(v) = v {
                debug_assert!(InlineInt::try_from(v).is_err());
            }
            v
        } else {
            None
        }
    }

    pub(crate) fn cmp_small_big(a: InlineInt, b: &StarlarkBigInt) -> Ordering {
        let a_sign = a.signum();
        let b_sign = match b.value.sign() {
            Sign::Plus => 2,
            Sign::Minus => -2,
            Sign::NoSign => 0,
        };
        // Sign comparison is enough because `StarlarkBigInt` is out of range of `i32`.
        a_sign.cmp(&b_sign)
    }

    pub(crate) fn cmp_big_small(a: &StarlarkBigInt, b: InlineInt) -> Ordering {
        Self::cmp_small_big(b, a).reverse()
    }

    pub(crate) fn unpack_integer<'v, I: TryFrom<&'v BigInt>>(&'v self) -> Option<I> {
        I::try_from(&self.value).ok()
    }
}

impl PartialEq<i32> for StarlarkBigInt {
    fn eq(&self, _other: &i32) -> bool {
        false
    }
}

impl Serialize for StarlarkBigInt {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.value.to_string())
    }
}

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

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

#[starlark_value(type = "int")]
impl<'v> StarlarkValue<'v> for StarlarkBigInt {
    fn to_bool(&self) -> bool {
        // `StarlarkBigInt` is non-zero.
        true
    }

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

    fn plus(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        // This unnecessarily allocates, could return `self`.
        // But practically people rarely write `+NNN` except in constants,
        // and in constants we fold `+NNN` into `NNN`.
        Ok(heap.alloc(StarlarkInt::from(self.value.clone())))
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        Ok(Some(NumRef::Int(StarlarkIntRef::Big(self))) == other.unpack_num())
    }

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

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

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

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

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

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

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

    fn bit_and(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let rhs = match StarlarkIntRef::unpack_value(other) {
            Some(rhs) => rhs,
            None => return ValueError::unsupported_with(self, "&", other),
        };
        Ok(heap.alloc(StarlarkIntRef::Big(self) & rhs))
    }

    fn bit_xor(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let rhs = match StarlarkIntRef::unpack_value(other) {
            Some(rhs) => rhs,
            None => return ValueError::unsupported_with(self, "^", other),
        };
        Ok(heap.alloc(StarlarkIntRef::Big(self) ^ rhs))
    }

    fn bit_or(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let rhs = match StarlarkIntRef::unpack_value(other) {
            Some(rhs) => rhs,
            None => return ValueError::unsupported_with(self, "|", other),
        };
        Ok(heap.alloc(StarlarkIntRef::Big(self) | rhs))
    }

    fn bit_not(&self, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(!StarlarkIntRef::Big(self)))
    }

    fn left_shift(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, "<<", other),
            Some(other) => Ok(heap.alloc(StarlarkIntRef::Big(self).left_shift(other)?)),
        }
    }

    fn right_shift(&self, other: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match StarlarkIntRef::unpack_value(other) {
            None => ValueError::unsupported_with(self, ">>", other),
            Some(other) => Ok(heap.alloc(StarlarkIntRef::Big(self).right_shift(other)?)),
        }
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        NumRef::Int(StarlarkIntRef::Big(self))
            .get_hash_64()
            .hash(hasher);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::hash::Hasher;

    use num_bigint::BigInt;

    use crate::assert;
    use crate::collections::StarlarkHasher;
    use crate::values::float::StarlarkFloat;
    use crate::values::types::bigint::StarlarkBigInt;
    use crate::values::StarlarkValue;

    #[test]
    fn test_parse() {
        assert::eq(
            "'1234567890112233445566778899'",
            "str(1234567890112233445566778899)",
        );
        assert::eq(
            "'1234567890112233445566778899'",
            "str(0x3fd35eb6d519aff76f50e13)",
        );
        assert::eq(
            "'1234567890112233445566778899'",
            "str(0o776465726665214657756675207023)",
        );
        assert::eq(
            "'1234567890112233445566778899'",
            "str(0b11111111010011010111101011011011010101000\
                1100110101111111101110110111101010000111000010011)",
        );
    }

    #[test]
    fn test_str() {
        assert::eq(
            "'1234567890112233445566778899'",
            "str(1234567890112233445566778899)",
        );
    }

    #[test]
    fn test_repr() {
        assert::eq(
            "'1234567890112233445566778899'",
            "repr(1234567890112233445566778899)",
        );
    }

    #[test]
    fn test_equals() {
        assert::eq("10000000000000000000000", "10000000000000000000000");
        assert::eq("10000000000000000000000", "10000000000000000000000.0");
        assert::eq("10000000000000000000000.0", "10000000000000000000000");
    }

    #[test]
    fn test_plus() {
        assert::eq("10000000000000000000000", "+10000000000000000000000");
    }

    #[test]
    fn test_compare_big_big() {
        assert::is_true("10000000000000000000000 < 20000000000000000000000");
        assert::is_true("-20000000000000000000000 < -10000000000000000000000");
        assert::is_true("20000000000000000000000 > 10000000000000000000000");
        assert::is_true("-10000000000000000000000 > -20000000000000000000000");
    }

    #[test]
    fn test_compare_big_small() {
        assert::is_true("1 < 10000000000000000000000");
        assert::is_true("-1 < 10000000000000000000000");
        assert::is_true("1 > -10000000000000000000000");
        assert::is_true("-1 > -10000000000000000000000");
        assert::is_true("10000000000000000000000 > 1");
        assert::is_true("10000000000000000000000 > -1");
        assert::is_true("-10000000000000000000000 < 1");
        assert::is_true("-10000000000000000000000 < -1");
    }

    #[test]
    fn test_compare_big_float() {
        assert::is_true("1.0 < 10000000000000000000000");
        assert::is_true("-1.0 < 10000000000000000000000");
        assert::is_true("1.0 > -10000000000000000000000");
        assert::is_true("-1.0 > -10000000000000000000000");
        assert::is_true("10000000000000000000000 > 1.0");
        assert::is_true("10000000000000000000000 > -1.0");
        assert::is_true("-10000000000000000000000 < 1.0");
        assert::is_true("-10000000000000000000000 < -1.0");
    }

    #[test]
    fn test_add_big() {
        assert::eq(
            "300000000000000000009",
            "100000000000000000004 + 200000000000000000005",
        );
        assert::eq("7", "100000000000000000007 + -100000000000000000000");
        assert::eq(
            "200000000000000000005",
            "300000000000000000009 - 100000000000000000004",
        );
        assert::eq("7", "100000000000000000007 - 100000000000000000000");
    }

    #[test]
    fn test_add_big_small() {
        assert::eq("100000000000000000017", "100000000000000000000 + 17");
        assert::eq("100000000000000000017", "17 + 100000000000000000000");
        assert::eq("100000000000000000000", "100000000000000000017 - 17");
        assert::eq("-100000000000000000017", "17 - 100000000000000000034");
    }

    #[test]
    fn test_add_big_float() {
        assert::eq("2e20", "100000000000000000000 + 1e20");
        assert::eq("2e20", "1e20 + 100000000000000000000");
        assert::eq("2e20", "300000000000000000000 - 1e20");
        assert::eq("2e20", "3e20 - 100000000000000000000");
    }

    #[test]
    fn test_mul_big() {
        assert::eq(
            "60000000000000000000000000000000000000000",
            "200000000000000000000 * 300000000000000000000",
        );
    }

    #[test]
    fn test_mul_big_small() {
        assert::eq("600000000000000000000", "200000000000000000000 * 3");
        assert::eq("600000000000000000000", "3 * 200000000000000000000");
    }

    #[test]
    fn test_mul_big_float() {
        assert::eq("6e20", "200000000000000000000 * 3.0");
        assert::eq("6e20", "3.0 * 200000000000000000000");
    }

    #[test]
    fn test_div_big() {
        assert::eq(
            "2e20",
            "60000000000000000000000000000000000000000 / 300000000000000000000",
        );
    }

    #[test]
    fn test_div_big_small() {
        assert::eq("2e20", "600000000000000000000 / 3");
        assert::eq("2e-20", "6 / 300000000000000000000");
    }

    #[test]
    fn test_div_big_float() {
        assert::eq("2e20", "600000000000000000000 / 3.0");
        assert::eq("2e-20", "6.0 / 300000000000000000000");
    }

    #[test]
    fn test_floor_div_big() {
        assert::eq("2", "600000000000000000000 // 300000000000000000000");
    }

    #[test]
    fn test_floor_div_big_small() {
        assert::eq("200000000000000000000", "600000000000000000000 // 3");
        assert::eq("0", "3 // 600000000000000000000");
    }

    #[test]
    fn test_floor_div_big_float() {
        assert::eq("2e20", "600000000000000000000 / 3.0");
        assert::eq("2e-20", "6.0 / 300000000000000000000");
    }

    #[test]
    fn test_percent_big() {
        assert::eq("7", "600000000000000000007 % 200000000000000000000");
    }

    #[test]
    fn test_percent_big_small() {
        assert::eq("7", "600000000000000000007 % 20");
        assert::eq("3", "3 % 600000000000000000000");
    }

    #[test]
    fn test_percent_big_float() {
        assert::eq("1e20", "100000000000000000000 % 1e50");
        assert::eq("10.0", "10.0 % 100000000000000000000");
    }

    #[test]
    fn test_bit_and_big() {
        assert::eq(
            "0x10000000000000000000000",
            "0x30000000000000000000000 & 0x90000000000000000000000",
        );
    }

    #[test]
    fn test_bit_and_big_small() {
        assert::eq("1", "0x60000000000000000000003 & 0x9");
        assert::eq("1", "0x9 & 0x60000000000000000000003");
    }

    #[test]
    fn test_bit_and_float() {
        assert::fail("0x60000000000000000000000 & 1.0", "not supported");
        assert::fail("1.0 & 0x60000000000000000000000", "not supported");
    }

    #[test]
    fn test_bit_or_big() {
        assert::eq(
            "0x70000000000000000000000",
            "0x30000000000000000000000 | 0x50000000000000000000000",
        );
    }

    #[test]
    fn test_bit_or_big_small() {
        assert::eq(
            "0x60000000000000000000009",
            "0x60000000000000000000000 | 0x9",
        );
        assert::eq(
            "0x60000000000000000000009",
            "0x9 | 0x60000000000000000000000",
        );
    }

    #[test]
    fn test_bit_or_float() {
        assert::fail("0x60000000000000000000000 | 1.0", "not supported");
        assert::fail("1.0 | 0x60000000000000000000000", "not supported");
    }

    #[test]
    fn test_bit_xor_big() {
        assert::eq(
            "0x60000000000000000000000",
            "0x30000000000000000000000 ^ 0x50000000000000000000000",
        );
    }

    #[test]
    fn test_bit_xor_big_small() {
        assert::eq(
            "0x60000000000000000000000",
            "0x60000000000000000000009 ^ 0x9",
        );
        assert::eq(
            "0x60000000000000000000000",
            "0x9 ^ 0x60000000000000000000009",
        );
    }

    #[test]
    fn test_bit_xor_float() {
        assert::fail("0x60000000000000000000000 ^ 1.0", "not supported");
        assert::fail("1.0 ^ 0x60000000000000000000000", "not supported");
    }

    #[test]
    fn test_bit_not() {
        assert::eq(
            "-0x10000000000000000000000000000001",
            "~0x10000000000000000000000000000000",
        );
    }

    #[test]
    fn test_left_shift() {
        assert::fail(
            "0x10000000000000000000000000000000 << 0x10000000000000000000000000000000",
            "Integer overflow",
        );
        assert::fail(
            "0x10000000000000000000000000000000 << -0x10000000000000000000000000000000",
            "Negative left shift",
        );
    }

    #[test]
    fn test_left_shift_small() {
        assert::eq(
            "0x20000000000000000000000000000000",
            "0x10000000000000000000000000000000 << 1",
        );
        assert::fail(
            "0x10000000000000000000000000000000 << -1",
            "Negative left shift",
        );
        assert::fail(
            "1 << 0x10000000000000000000000000000000",
            "Integer overflow",
        );
        assert::fail(
            "1 << -0x10000000000000000000000000000000",
            "Negative left shift",
        );
        assert::eq("0", "0 << 0x10000000000000000000000000000000");
        assert::eq("1267650600228229401496703205376", "1 << 100");
        assert::eq("-1267650600228229401496703205376", "-1 << 100");
    }

    #[test]
    fn test_left_shift_float() {
        assert::fail("0x10000000000000000000000000000000 << 1.0", "not supported");
        assert::fail("1.0 << 0x10000000000000000000000000000000", "not supported");
    }

    #[test]
    fn test_right_shift() {
        assert::eq(
            "0",
            "0x20000000000000000000000000000000 >> 0x20000000000000000000000000000000",
        );
        assert::eq(
            "-1",
            "-0x20000000000000000000000000000000 >> 0x20000000000000000000000000000000",
        );
        assert::fail(
            "0x20000000000000000000000000000000 >> -0x20000000000000000000000000000000",
            "Negative right shift",
        );
    }

    #[test]
    fn test_right_shift_small() {
        assert::eq(
            "0x10000000000000000000000000000000",
            "0x20000000000000000000000000000000 >> 1",
        );
        assert::fail(
            "0x20000000000000000000000000000000 >> -1",
            "Negative right shift",
        );
        assert::eq("0", "1 >> 0x20000000000000000000000000000000");
        assert::eq("-1", "-1 >> 0x20000000000000000000000000000000");
        assert::fail(
            "1 >> -0x10000000000000000000000000000000",
            "Negative right shift",
        );
    }

    #[test]
    fn test_right_shift_float() {
        assert::fail("0x20000000000000000000000000000000 >> 1.0", "not supported");
        assert::fail("1.0 >> 0x20000000000000000000000000000000", "not supported");
    }

    #[test]
    fn test_int_function() {
        assert::eq(
            "123456789012345678901234567890",
            "int(123456789012345678901234567890)",
        );
    }

    #[test]
    fn test_hash() {
        let mut hash1 = StarlarkHasher::new();
        let mut hash2 = StarlarkHasher::new();
        StarlarkFloat(1e20).write_hash(&mut hash1).unwrap();
        StarlarkBigInt::unchecked_new(BigInt::from(10).pow(20))
            .write_hash(&mut hash2)
            .unwrap();
        assert_eq!(hash1.finish(), hash2.finish());
    }
}


/*
 * 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.
 */

use std::iter;

use starlark_map::small_map::SmallMap;

use crate::typing::Ty;
use crate::values::dict::value::FrozenDictData;
use crate::values::dict::Dict;
use crate::values::layout::value::ValueLike;
use crate::values::type_repr::DictType;
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::Value;

/// Utility to allocate a dict from iterator.
///
/// Iterator must be a list of pairs (key, value).
/// Duplicate keys are allowed, last key wins.
///
/// # Panics
///
/// Panics if a key is not hashable.
///
/// # Example
///
/// ```
/// use starlark::values::dict::AllocDict;
///
/// # use starlark::values::{FrozenHeap, Heap};
/// # fn alloc(heap: &Heap, frozen_heap: &FrozenHeap) {
/// let l = heap.alloc(AllocDict([("a", 1), ("b", 2), ("c", 3)]));
/// let ls = frozen_heap.alloc(AllocDict([("a", 1), ("b", 2), ("c", 3)]));
/// # }
/// ```
pub struct AllocDict<D>(pub D);

impl AllocDict<iter::Empty<(FrozenValue, FrozenValue)>> {
    /// Allocate an empty dict.
    pub const EMPTY: AllocDict<iter::Empty<(FrozenValue, FrozenValue)>> = AllocDict(iter::empty());
}

impl<D, K, V> StarlarkTypeRepr for AllocDict<D>
where
    D: IntoIterator<Item = (K, V)>,
    K: StarlarkTypeRepr,
    V: StarlarkTypeRepr,
{
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<'v, D, K, V> AllocValue<'v> for AllocDict<D>
where
    D: IntoIterator<Item = (K, V)>,
    K: AllocValue<'v>,
    V: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        let iter = self.0.into_iter();
        let mut map = SmallMap::with_capacity(iter.size_hint().0);
        for (k, v) in iter {
            map.insert_hashed(
                k.alloc_value(heap).get_hashed().unwrap(),
                v.alloc_value(heap),
            );
        }
        heap.alloc(Dict::new(map))
    }
}

impl<D, K, V> AllocFrozenValue for AllocDict<D>
where
    D: IntoIterator<Item = (K, V)>,
    K: AllocFrozenValue,
    V: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        let iter = self.0.into_iter();
        let mut map = SmallMap::with_capacity(iter.size_hint().0);
        for (k, v) in iter {
            map.insert_hashed(
                k.alloc_frozen_value(heap).get_hashed().unwrap(),
                v.alloc_frozen_value(heap),
            );
        }
        heap.alloc(FrozenDictData { content: map })
    }
}


/*
 * 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 dictionary type, a mutable associative-map, which iterates in insertion order.

mod alloc;
mod of;
pub(crate) mod refcell;
mod refs;
mod traits;
pub(crate) mod value;

pub use crate::values::dict::alloc::AllocDict;
pub use crate::values::dict::of::DictOf;
pub use crate::values::dict::refs::DictMut;
pub use crate::values::dict::refs::DictRef;
pub use crate::values::dict::refs::FrozenDictRef;
pub use crate::values::dict::value::Dict;


/*
 * 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.
 */

use std::hash::Hash;
use std::marker::PhantomData;
use std::ops::Deref;

use starlark_derive::Trace;
use starlark_map::small_map::SmallMap;

use crate as starlark;
use crate::typing::Ty;
use crate::values::dict::DictRef;
use crate::values::type_repr::DictType;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::UnpackValue;
use crate::values::Value;

/// Like [`ValueOf`](crate::values::ValueOf), but only validates key and value types; does not construct
/// or store a map. Use `to_dict` to get at the map.
#[derive(Debug, Trace)]
pub struct DictOf<'v, K: UnpackValue<'v>, V: UnpackValue<'v>> {
    value: Value<'v>,
    phantom: PhantomData<(K, V)>,
}

impl<'v, K: UnpackValue<'v>, V: UnpackValue<'v>> DictOf<'v, K, V> {
    /// Get all the elements.
    // This should return an iterator, but it is not trivial to do with `ARef`.
    pub fn collect_entries(&self) -> Vec<(K, V)> {
        DictRef::from_value(self.value)
            .expect("already validated as a dict")
            .iter()
            .map(|(k, v)| {
                (
                    K::unpack_value(k).expect("already validated key"),
                    V::unpack_value(v).expect("already validated value"),
                )
            })
            .collect()
    }

    /// Number of entries.
    #[inline]
    pub fn len(&self) -> usize {
        DictRef::from_value(self.value)
            .expect("already validated as a dict")
            .len()
    }
}

impl<'v, K: UnpackValue<'v> + Hash + Eq, V: UnpackValue<'v>> DictOf<'v, K, V> {
    /// Collect all the elements to a fresh `SmallMap`.
    pub fn to_dict(&self) -> SmallMap<K, V> {
        DictRef::from_value(self.value)
            .expect("already validated as a dict")
            .iter()
            .map(|(k, v)| {
                (
                    K::unpack_value(k).expect("already validated key"),
                    V::unpack_value(v).expect("already validated value"),
                )
            })
            .collect()
    }
}

impl<'v, K: UnpackValue<'v>, V: UnpackValue<'v>> StarlarkTypeRepr for DictOf<'v, K, V> {
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<'v, K: UnpackValue<'v>, V: UnpackValue<'v>> UnpackValue<'v> for DictOf<'v, K, V> {
    fn expected() -> String {
        format!("dict mapping {} to {}", K::expected(), V::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        let dict = DictRef::from_value(value)?;
        let all_valid = dict
            .iter()
            .all(|(k, v)| K::unpack_value(k).is_some() && V::unpack_value(v).is_some());
        if all_valid {
            Some(DictOf {
                value,
                phantom: PhantomData,
            })
        } else {
            None
        }
    }
}

impl<'v, K: UnpackValue<'v> + Hash, V: UnpackValue<'v>> Deref for DictOf<'v, K, V> {
    type Target = Value<'v>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}


/*
 * 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.
 */

use std::cell::Ref;
use std::cell::RefCell;
use std::ptr;

/// "Unleak" previously leaked `RefCell` borrow (which is `Ref`).
#[inline]
pub(crate) unsafe fn unleak_borrow<T: ?Sized>(ref_cell: &RefCell<T>) {
    // Note this call contains a runtime assertion that the `RefCell` is borrowed.
    let r = ref_cell.borrow();
    unsafe {
        // Drop `b` twice to decrement the borrow counter.
        drop::<Ref<T>>(ptr::read(&r as *const Ref<T>));
        drop::<Ref<T>>(r);
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::mem;

    #[test]
    fn test_unleak_borrow() {
        let ref_cell = RefCell::new(1);
        assert!(ref_cell.try_borrow_mut().is_ok());
        mem::forget(ref_cell.borrow());
        assert!(
            ref_cell.try_borrow_mut().is_err(),
            "RefCell is borrowed, so we cannot borrow it mutably"
        );
        unsafe { super::unleak_borrow(&ref_cell) };
        assert!(
            ref_cell.try_borrow_mut().is_ok(),
            "Borrow is unleaked, so we can borrow it mutably"
        );

        // Now do the same twice.

        mem::forget(ref_cell.borrow());
        mem::forget(ref_cell.borrow());

        assert!(
            ref_cell.try_borrow_mut().is_err(),
            "RefCell is borrowed, so we cannot borrow it mutably"
        );
        unsafe { super::unleak_borrow(&ref_cell) };
        assert!(
            ref_cell.try_borrow_mut().is_err(),
            "RefCell is still borrowed"
        );
        unsafe { super::unleak_borrow(&ref_cell) };
        assert!(ref_cell.try_borrow_mut().is_ok());
    }
}


/*
 * 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.
 */

use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::ops::Deref;
use std::ops::DerefMut;

use either::Either;

use crate::coerce::coerce;
use crate::typing::Ty;
use crate::values::dict::value::DictGen;
use crate::values::dict::value::FrozenDictData;
use crate::values::dict::Dict;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::FrozenValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

/// Borrowed `Dict`.
pub struct DictRef<'v> {
    pub(crate) aref: Either<Ref<'v, Dict<'v>>, &'v Dict<'v>>,
}

/// Mutably borrowed `Dict`.
pub struct DictMut<'v> {
    pub(crate) aref: RefMut<'v, Dict<'v>>,
}

/// Reference to frozen `Dict`.
pub struct FrozenDictRef {
    dict: &'static FrozenDictData,
}

impl<'v> DictRef<'v> {
    /// Downcast the value to a dict.
    pub fn from_value(x: Value<'v>) -> Option<DictRef<'v>> {
        if x.unpack_frozen().is_some() {
            x.downcast_ref::<DictGen<FrozenDictData>>()
                .map(|x| DictRef {
                    aref: Either::Right(coerce(&x.0)),
                })
        } else {
            let ptr = x.downcast_ref::<DictGen<RefCell<Dict<'v>>>>()?;
            Some(DictRef {
                aref: Either::Left(ptr.0.borrow()),
            })
        }
    }
}

impl<'v> DictMut<'v> {
    /// Downcast the value to a mutable dict reference.
    #[inline]
    pub fn from_value(x: Value<'v>) -> anyhow::Result<DictMut> {
        #[derive(thiserror::Error, Debug)]
        #[error("Value is not dict, value type: `{0}`")]
        struct NotDictError(&'static str);

        #[cold]
        #[inline(never)]
        fn error<'v>(x: Value<'v>) -> anyhow::Error {
            if x.downcast_ref::<DictGen<FrozenDictData>>().is_some() {
                ValueError::CannotMutateImmutableValue.into()
            } else {
                NotDictError(x.get_type()).into()
            }
        }

        let ptr = x.downcast_ref::<DictGen<RefCell<Dict<'v>>>>();
        match ptr {
            None => Err(error(x)),
            Some(ptr) => match ptr.0.try_borrow_mut() {
                Ok(x) => Ok(DictMut { aref: x }),
                Err(_) => Err(ValueError::MutationDuringIteration.into()),
            },
        }
    }
}

impl FrozenDictRef {
    /// Downcast to frozen dict.
    pub fn from_frozen_value(x: FrozenValue) -> Option<FrozenDictRef> {
        x.downcast_ref::<DictGen<FrozenDictData>>()
            .map(|x| FrozenDictRef { dict: &x.0 })
    }

    /// Get value by a string key.
    pub fn get_str(&self, key: &str) -> Option<FrozenValue> {
        self.dict.get_str(key)
    }

    /// Iterate over dict entries.
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (FrozenValue, FrozenValue)> {
        self.dict.iter()
    }
}

impl<'v> Deref for DictRef<'v> {
    type Target = Dict<'v>;

    fn deref(&self) -> &Self::Target {
        &self.aref
    }
}

impl<'v> Deref for DictMut<'v> {
    type Target = Dict<'v>;

    fn deref(&self) -> &Self::Target {
        &self.aref
    }
}

impl<'v> DerefMut for DictMut<'v> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.aref
    }
}

impl<'v> StarlarkTypeRepr for DictRef<'v> {
    fn starlark_type_repr() -> Ty {
        Dict::<'v>::starlark_type_repr()
    }
}

impl<'v> UnpackValue<'v> for DictRef<'v> {
    fn expected() -> String {
        "dict".to_owned()
    }

    fn unpack_value(value: Value<'v>) -> Option<DictRef<'v>> {
        DictRef::from_value(value)
    }
}


/*
 * 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.
 */

use std::collections::BTreeMap;
use std::hash::Hash;

use crate::collections::SmallMap;
use crate::typing::Ty;
use crate::values::dict::AllocDict;
use crate::values::dict::DictRef;
use crate::values::type_repr::DictType;
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::UnpackValue;
use crate::values::Value;

// SmallMap

impl<'v, K: AllocValue<'v>, V: AllocValue<'v>> AllocValue<'v> for SmallMap<K, V> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        AllocDict(self).alloc_value(heap)
    }
}

impl<K: AllocFrozenValue, V: AllocFrozenValue> AllocFrozenValue for SmallMap<K, V> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        AllocDict(self).alloc_frozen_value(heap)
    }
}

impl<'a, 'v, K: 'a + StarlarkTypeRepr, V: 'a + StarlarkTypeRepr> AllocValue<'v>
    for &'a SmallMap<K, V>
where
    &'a K: AllocValue<'v>,
    &'a V: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        AllocDict(self).alloc_value(heap)
    }
}

impl<'a, K: 'a + StarlarkTypeRepr, V: 'a + StarlarkTypeRepr> AllocFrozenValue for &'a SmallMap<K, V>
where
    &'a K: AllocFrozenValue,
    &'a V: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        AllocDict(self).alloc_frozen_value(heap)
    }
}

impl<'a, K: StarlarkTypeRepr, V: StarlarkTypeRepr> StarlarkTypeRepr for &'a SmallMap<K, V> {
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<K: StarlarkTypeRepr, V: StarlarkTypeRepr> StarlarkTypeRepr for SmallMap<K, V> {
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<'v, K: UnpackValue<'v> + Hash + Eq, V: UnpackValue<'v>> UnpackValue<'v> for SmallMap<K, V> {
    fn expected() -> String {
        format!("dict mapping {} to {}", K::expected(), V::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        let dict = DictRef::from_value(value)?;
        let it = dict.iter();
        let mut r = SmallMap::with_capacity(it.len());
        for (k, v) in it {
            r.insert(K::unpack_value(k)?, V::unpack_value(v)?);
        }
        Some(r)
    }
}

// BTreeMap

impl<'v, K: AllocValue<'v>, V: AllocValue<'v>> AllocValue<'v> for BTreeMap<K, V> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        AllocDict(self).alloc_value(heap)
    }
}

impl<K: AllocFrozenValue, V: AllocFrozenValue> AllocFrozenValue for BTreeMap<K, V> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        AllocDict(self).alloc_frozen_value(heap)
    }
}

impl<'a, 'v, K: 'a + StarlarkTypeRepr, V: 'a + StarlarkTypeRepr> AllocValue<'v>
    for &'a BTreeMap<K, V>
where
    &'a K: AllocValue<'v>,
    &'a V: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        AllocDict(self).alloc_value(heap)
    }
}

impl<'a, K: 'a + StarlarkTypeRepr, V: 'a + StarlarkTypeRepr> AllocFrozenValue for &'a BTreeMap<K, V>
where
    &'a K: AllocFrozenValue,
    &'a V: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        AllocDict(self).alloc_frozen_value(heap)
    }
}

impl<'a, K: StarlarkTypeRepr, V: StarlarkTypeRepr> StarlarkTypeRepr for &'a BTreeMap<K, V> {
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<K: StarlarkTypeRepr, V: StarlarkTypeRepr> StarlarkTypeRepr for BTreeMap<K, V> {
    fn starlark_type_repr() -> Ty {
        DictType::<K, V>::starlark_type_repr()
    }
}

impl<'v, K: UnpackValue<'v> + Ord, V: UnpackValue<'v>> UnpackValue<'v> for BTreeMap<K, V> {
    fn expected() -> String {
        format!("dict mapping {} to {}", K::expected(), V::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        let dict = DictRef::from_value(value)?;
        let mut r = BTreeMap::new();
        for (k, v) in dict.iter() {
            r.insert(K::unpack_value(k)?, V::unpack_value(v)?);
        }
        Some(r)
    }
}


/*
 * 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.
 */

use std::any::TypeId;
use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;
use std::mem;
use std::ops::Deref;

use allocative::Allocative;
use display_container::fmt_keyed_container;
use serde::Serialize;
use starlark_derive::starlark_value;
use starlark_derive::StarlarkDocs;
use starlark_map::Equivalent;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::cast::transmute;
use crate::coerce::coerce;
use crate::coerce::Coerce;
use crate::collections::Hashed;
use crate::collections::SmallMap;
use crate::environment::Methods;
use crate::environment::MethodsStatic;
use crate::hint::unlikely;
use crate::typing::Ty;
use crate::values::comparison::equals_small_map;
use crate::values::dict::refcell::unleak_borrow;
use crate::values::dict::DictOf;
use crate::values::dict::DictRef;
use crate::values::error::ValueError;
use crate::values::layout::avalue::VALUE_EMPTY_FROZEN_DICT;
use crate::values::string::hash_string_value;
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::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(
    Clone,
    Default,
    Trace,
    Debug,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "standard")]
pub(crate) struct DictGen<T>(pub(crate) T);

impl<'v, T: DictLike<'v>> Display for DictGen<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_keyed_container(f, "{", "}", ": ", self.0.content().iter())
    }
}

impl<'v> Display for Dict<'v> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_keyed_container(f, "{", "}", ": ", self.iter())
    }
}

/// Define the dict type.
#[derive(Clone, Default, Trace, Debug, ProvidesStaticType, Allocative)]
#[repr(transparent)]
pub struct Dict<'v> {
    /// The data stored by the dictionary. The keys must all be hashable values.
    content: SmallMap<Value<'v>, Value<'v>>,
}

impl<'v> StarlarkTypeRepr for Dict<'v> {
    fn starlark_type_repr() -> Ty {
        DictOf::<Value<'v>, Value<'v>>::starlark_type_repr()
    }
}

#[derive(Clone, Default, Debug, ProvidesStaticType, Allocative)]
#[repr(transparent)]
pub(crate) struct FrozenDictData {
    /// The data stored by the dictionary. The keys must all be hashable values.
    pub(crate) content: SmallMap<FrozenValue, FrozenValue>,
}

/// Alias is used in `StarlarkDocs` derive.
pub(crate) type FrozenDict = DictGen<FrozenDictData>;

unsafe impl<'v> Coerce<Dict<'v>> for FrozenDictData {}

impl<'v> AllocValue<'v> for Dict<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex(DictGen(RefCell::new(self)))
    }
}

impl AllocFrozenValue for FrozenDictData {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        if self.content.is_empty() {
            FrozenValue::new_repr(&VALUE_EMPTY_FROZEN_DICT)
        } else {
            heap.alloc_simple(DictGen(self))
        }
    }
}

impl<'v> Dict<'v> {
    pub(crate) fn is_dict_type(x: TypeId) -> bool {
        x == TypeId::of::<DictGen<FrozenDictData>>()
            || x == TypeId::of::<DictGen<RefCell<Dict<'static>>>>()
    }

    pub(crate) unsafe fn from_value_unchecked_mut(x: Value<'v>) -> RefMut<'v, Self> {
        let dict = &x.downcast_ref_unchecked::<DictGen<RefCell<Dict<'v>>>>().0;
        dict.borrow_mut()
    }
}

/// Helper type for lookups, not useful.
#[derive(Eq, PartialEq)]
pub struct ValueStr<'a>(pub(crate) &'a str);

impl<'a> Hash for ValueStr<'a> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        hash_string_value(self.0, state)
    }
}

impl<'v> Equivalent<Value<'v>> for ValueStr<'_> {
    fn equivalent(&self, key: &Value<'v>) -> bool {
        key.unpack_str() == Some(self.0)
    }
}

impl Equivalent<FrozenValue> for ValueStr<'_> {
    fn equivalent(&self, key: &FrozenValue) -> bool {
        key.unpack_str() == Some(self.0)
    }
}

impl<'v> Dict<'v> {
    /// The result of calling `type()` on dictionaries.
    pub const TYPE: &'static str = "dict";

    /// Dict type string as Starlark frozen string value.
    pub fn get_type_value_static() -> FrozenStringValue {
        DictGen::<FrozenDictData>::get_type_value_static()
    }

    /// Create a new [`Dict`].
    pub fn new(content: SmallMap<Value<'v>, Value<'v>>) -> Self {
        Self { content }
    }

    /// Number of elements in the dict.
    pub fn len(&self) -> usize {
        self.content.len()
    }

    /// Is the dict empty?
    pub fn is_empty(&self) -> bool {
        self.content.is_empty()
    }

    /// Iterate through the key/value pairs in the dictionary.
    pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = (Value<'v>, Value<'v>)> + 'a {
        self.content.iter().map(|(l, r)| (*l, *r))
    }

    /// Iterate through the key/value pairs in the dictionary, but retaining the hash of the keys.
    pub fn iter_hashed<'a>(&'a self) -> impl Iterator<Item = (Hashed<Value<'v>>, Value<'v>)> + 'a
    where
        'v: 'a,
    {
        self.content.iter_hashed().map(|(l, r)| (l.copied(), *r))
    }

    /// Iterator over keys.
    pub fn keys<'a>(&'a self) -> impl Iterator<Item = Value<'v>> + 'a {
        self.content.keys().copied()
    }

    /// Iterator over keys.
    pub fn values<'a>(&'a self) -> impl Iterator<Item = Value<'v>> + 'a {
        self.content.values().copied()
    }

    /// Get the value associated with a particular key. Will be [`Err`] if the key is not hashable,
    /// and otherwise [`Some`] if the key exists in the dictionary and [`None`] otherwise.
    pub fn get(&self, key: Value<'v>) -> anyhow::Result<Option<Value<'v>>> {
        Ok(self.get_hashed(key.get_hashed()?))
    }

    /// Lookup the value by the given prehashed key.
    pub fn get_hashed(&self, key: Hashed<Value<'v>>) -> Option<Value<'v>> {
        self.content.get_hashed_by_value(key).copied()
    }

    /// Get the value associated with a particular string. Equivalent to allocating the
    /// string on the heap, turning it into a value, and looking up using that.
    pub fn get_str(&self, key: &str) -> Option<Value<'v>> {
        self.content.get(&ValueStr(key)).copied()
    }

    /// Like [`Dict::get_str`], but where you already have the hash.
    pub fn get_str_hashed(&self, key: Hashed<&str>) -> Option<Value<'v>> {
        self.content
            .get_hashed_by_value(Hashed::new_unchecked(key.hash(), ValueStr(key.key())))
            .copied()
    }

    /// Try to coerce all keys to strings.
    pub(crate) fn downcast_ref_key_string(&self) -> Option<&SmallMap<StringValue<'v>, Value<'v>>> {
        for &key in self.content.keys() {
            if unlikely(!key.is_str()) {
                return None;
            }
        }

        // Scary part: `SmallMap` has the same repr for `Value` and `StringValue`,
        // and we just checked above that all keys are strings.

        fn _assert_coerce<'v>(
            s: SmallMap<StringValue<'v>, Value<'v>>,
        ) -> SmallMap<Value<'v>, Value<'v>> {
            coerce(s)
        }

        Some(unsafe {
            transmute!(&SmallMap<Value, Value>, &SmallMap<StringValue, Value>, &self.content)
        })
    }

    /// Reserve capacity to insert `additional` elements without reallocating.
    pub fn reserve(&mut self, additional: usize) {
        self.content.reserve(additional);
    }

    /// Insert a key/value pair into the dictionary.
    pub fn insert_hashed(&mut self, key: Hashed<Value<'v>>, value: Value<'v>) {
        self.content.insert_hashed(key, value);
    }

    /// Remove given key from the dictionary.
    pub fn remove_hashed(&mut self, key: Hashed<Value<'v>>) -> Option<Value<'v>> {
        self.content.remove_hashed(key.as_ref())
    }

    /// Remove all elements from the dictionary.
    pub fn clear(&mut self) {
        self.content.clear();
    }
}

impl FrozenDictData {
    /// Iterate through the key/value pairs in the dictionary.
    pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = (FrozenValue, FrozenValue)> + 'a {
        self.content.iter().map(|(l, r)| (*l, *r))
    }

    /// Get the value associated with a particular string. Equivalent to allocating the
    /// string on the heap, turning it into a value, and looking up using that.
    pub fn get_str(&self, key: &str) -> Option<FrozenValue> {
        self.content.get(&ValueStr(key)).copied()
    }
}

impl<'v> Freeze for DictGen<RefCell<Dict<'v>>> {
    type Frozen = DictGen<FrozenDictData>;
    fn freeze(self, freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        let content = self.0.into_inner().content.freeze(freezer)?;
        Ok(DictGen(FrozenDictData { content }))
    }
}

trait DictLike<'v>: Debug + Allocative {
    type ContentRef<'a>: Deref<Target = SmallMap<Value<'v>, Value<'v>>>
    where
        Self: 'a,
        'v: 'a;
    fn content<'a>(&'a self) -> Self::ContentRef<'a>;
    // These functions are unsafe for the same reason
    // `StarlarkValue` iterator functions are unsafe.
    unsafe fn iter_start(&self);
    unsafe fn content_unchecked(&self) -> &SmallMap<Value<'v>, Value<'v>>;
    unsafe fn iter_stop(&self);
    fn set_at(&self, index: Hashed<Value<'v>>, value: Value<'v>) -> anyhow::Result<()>;
}

impl<'v> DictLike<'v> for RefCell<Dict<'v>> {
    type ContentRef<'a> = Ref<'a, SmallMap<Value<'v>, Value<'v>>> where Self: 'a, 'v: 'a;

    fn content<'a>(&'a self) -> Ref<'a, SmallMap<Value<'v>, Value<'v>>> {
        Ref::map(self.borrow(), |x| &x.content)
    }

    #[inline]
    unsafe fn iter_start(&self) {
        mem::forget(self.borrow());
    }

    #[inline]
    unsafe fn iter_stop(&self) {
        unleak_borrow(self);
    }

    #[inline]
    unsafe fn content_unchecked(&self) -> &SmallMap<Value<'v>, Value<'v>> {
        // SAFETY: this function contract is, caller must ensure that the value is borrowed.
        &self.try_borrow_unguarded().ok().unwrap_unchecked().content
    }

    fn set_at(&self, index: Hashed<Value<'v>>, alloc_value: Value<'v>) -> anyhow::Result<()> {
        match self.try_borrow_mut() {
            Ok(mut xs) => {
                xs.content.insert_hashed(index, alloc_value);
                Ok(())
            }
            Err(_) => Err(ValueError::MutationDuringIteration.into()),
        }
    }
}

impl<'v> DictLike<'v> for FrozenDictData {
    type ContentRef<'a> = &'a SmallMap<Value<'v>, Value<'v>> where Self: 'a, 'v: 'a;

    fn content<'a>(&'a self) -> &'a SmallMap<Value<'v>, Value<'v>> {
        coerce(&self.content)
    }

    unsafe fn iter_start(&self) {}

    unsafe fn iter_stop(&self) {}

    unsafe fn content_unchecked(&self) -> &SmallMap<Value<'v>, Value<'v>> {
        coerce(&self.content)
    }

    fn set_at(&self, _index: Hashed<Value<'v>>, _value: Value<'v>) -> anyhow::Result<()> {
        Err(ValueError::CannotMutateImmutableValue.into())
    }
}

pub(crate) fn dict_methods() -> Option<&'static Methods> {
    static RES: MethodsStatic = MethodsStatic::new();
    RES.methods(crate::stdlib::dict::dict_methods)
}

#[starlark_value(type = Dict::TYPE)]
impl<'v, T: DictLike<'v> + 'v> StarlarkValue<'v> for DictGen<T>
where
    Self: ProvidesStaticType<'v>,
{
    fn get_methods() -> Option<&'static Methods> {
        dict_methods()
    }

    fn collect_repr(&self, r: &mut String) {
        // Fast path as repr() for dicts is quite hot
        r.push('{');
        for (i, (name, value)) in self.0.content().iter().enumerate() {
            if i != 0 {
                r.push_str(", ");
            }
            name.collect_repr(r);
            r.push_str(": ");
            value.collect_repr(r);
        }
        r.push('}');
    }

    fn collect_repr_cycle(&self, collector: &mut String) {
        collector.push_str("{...}");
    }

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

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        match DictRef::from_value(other) {
            None => Ok(false),
            Some(other) => {
                equals_small_map(&*self.0.content(), &other.content, |x, y| x.equals(*y))
            }
        }
    }

    fn at(&self, index: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match self.0.content().get_hashed_by_value(index.get_hashed()?) {
            Some(v) => Ok(v.to_value()),
            None => Err(ValueError::KeyNotFound(index.to_repr()).into()),
        }
    }

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

    fn is_in(&self, other: Value<'v>) -> anyhow::Result<bool> {
        Ok(self
            .0
            .content()
            .contains_key_hashed_by_value(other.get_hashed()?))
    }

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

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

    unsafe fn iter_next(&self, index: usize, _heap: &'v Heap) -> Option<Value<'v>> {
        self.0.content_unchecked().keys().nth(index).copied()
    }

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

    fn set_at(&self, index: Value<'v>, alloc_value: Value<'v>) -> anyhow::Result<()> {
        let index = index.get_hashed()?;
        self.0.set_at(index, alloc_value)
    }

    fn bit_or(&self, rhs: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let rhs = DictRef::from_value(rhs)
            .map_or_else(|| ValueError::unsupported_with(self, "|", rhs), Ok)?;
        if self.0.content().is_empty() {
            return Ok(heap.alloc(rhs.clone()));
        }
        // Might be faster if we preallocate the capacity, but then copying in the LHS
        // is more expensive and might oversize given the behaviour on duplicates.
        // If this becomes a bottleneck, benchmark.
        let mut items = self.0.content().clone();
        for (k, v) in rhs.iter_hashed() {
            items.insert_hashed(k, v);
        }
        Ok(heap.alloc(Dict::new(items)))
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::assert;
    use crate::collections::SmallMap;
    use crate::values::Heap;

    #[test]
    fn test_mutate_dict() {
        assert::is_true(
            r#"
x = {1: 2, 2: 4}
b1 = str(x) == "{1: 2, 2: 4}"
x[2] = 3
b2 = str(x) == "{1: 2, 2: 3}"
x[(3,4)] = 5
b3 = str(x) == "{1: 2, 2: 3, (3, 4): 5}"
b1 and b2 and b3
"#,
        );
    }

    #[test]
    fn test_get_str() -> anyhow::Result<()> {
        let heap = Heap::new();
        let k1 = heap.alloc_str("hello").get_hashed();
        let k2 = heap.alloc_str("world").get_hashed();
        let mut sm = SmallMap::new();
        sm.insert_hashed(k1, heap.alloc(12));
        sm.insert_hashed(k2, heap.alloc(56));
        let d = Dict::new(coerce(sm));

        assert_eq!(d.get(heap.alloc("hello"))?.unwrap().unpack_i32(), Some(12));
        assert_eq!(d.get(heap.alloc("foo"))?, None);
        assert_eq!(d.get_str("hello").unwrap().unpack_i32(), Some(12));
        assert_eq!(d.get_str("foo"), None);
        Ok(())
    }

    #[test]
    fn test_repr_cycle() {
        assert::eq("d = {}; d[17] = d; repr(d)", "'{17: {...}}'");
        assert::eq("d = {}; d[17] = d; str(d)", "'{17: {...}}'");
    }
}


/*
 * 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.
 */

use std::iter;

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::Value;

/// Utility to allocate a list from iterator.
///
/// # Example
///
/// ```
/// use starlark::values::list::AllocList;
///
/// # use starlark::values::{FrozenHeap, Heap};
/// # fn alloc(heap: &Heap, frozen_heap: &FrozenHeap) {
/// let l = heap.alloc(AllocList([1, 2, 3]));
/// let ls = frozen_heap.alloc(AllocList([1, 2, 3]));
/// # }
/// ```
pub struct AllocList<L>(pub L);

impl AllocList<iter::Empty<FrozenValue>> {
    /// Allocate an empty list.
    pub const EMPTY: AllocList<iter::Empty<FrozenValue>> = AllocList(iter::empty());
}

impl<L> StarlarkTypeRepr for AllocList<L>
where
    L: IntoIterator,
    L::Item: StarlarkTypeRepr,
{
    fn starlark_type_repr() -> Ty {
        Vec::<L::Item>::starlark_type_repr()
    }
}

impl<'v, L> AllocValue<'v> for AllocList<L>
where
    L: IntoIterator,
    L::Item: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_list_iter(self.0.into_iter().map(|x| heap.alloc(x)))
    }
}

impl<L> AllocFrozenValue for AllocList<L>
where
    L: IntoIterator,
    L::Item: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_list_iter(self.0.into_iter().map(|x| heap.alloc(x)))
    }
}


/*
 * 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 list type, a mutable sequence of values.

pub(crate) mod alloc;
mod of;
mod refs;
pub(crate) mod value;

pub use crate::values::types::list::alloc::AllocList;
pub use crate::values::types::list::of::ListOf;
pub use crate::values::types::list::refs::ListRef;


/*
 * 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.
 */

use std::marker::PhantomData;
use std::ops::Deref;

use starlark_derive::Trace;

use crate as starlark;
use crate::typing::Ty;
use crate::values::list::ListRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::UnpackValue;
use crate::values::Value;

/// Like `ValueOf`, but only validates item types; does not construct or store a
/// vec. Use `to_vec` to get a Vec.
#[derive(Debug, Trace)]
pub struct ListOf<'v, V: UnpackValue<'v>> {
    value: Value<'v>,
    phantom: PhantomData<V>,
}

impl<'v, V: UnpackValue<'v>> ListOf<'v, V> {
    /// Collect the list elements into a `Vec`.
    pub fn to_vec(&self) -> Vec<V> {
        ListRef::from_value(self.value)
            .expect("already validated as a list")
            .iter()
            .map(|v| V::unpack_value(v).expect("already validated value"))
            .collect()
    }
}

impl<'v, V: UnpackValue<'v>> StarlarkTypeRepr for ListOf<'v, V> {
    fn starlark_type_repr() -> Ty {
        Vec::<V>::starlark_type_repr()
    }
}

impl<'v, V: UnpackValue<'v>> UnpackValue<'v> for ListOf<'v, V> {
    fn expected() -> String {
        format!("list of {}", V::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        let list = ListRef::from_value(value)?;
        if list.iter().all(|v| V::unpack_value(v).is_some()) {
            Some(ListOf {
                value,
                phantom: PhantomData,
            })
        } else {
            None
        }
    }
}

impl<'v, V: UnpackValue<'v>> Deref for ListOf<'v, V> {
    type Target = Value<'v>;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}


/*
 * 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.
 */

use std::fmt;
use std::fmt::Display;
use std::ops::Deref;

use crate as starlark;
use crate::coerce::coerce;
use crate::typing::Ty;
use crate::values::list::value::display_list;
use crate::values::list::value::FrozenListData;
use crate::values::list::value::ListGen;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::types::list::value::ListData;
use crate::values::Coerce;
use crate::values::FrozenValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;

/// Reference to list content (mutable or frozen).
#[repr(transparent)]
#[derive(Coerce)]
pub struct ListRef<'v> {
    pub(crate) content: [Value<'v>],
}

/// Reference to frozen list content.
#[repr(transparent)]
#[derive(Coerce)]
pub struct FrozenListRef {
    pub(crate) content: [FrozenValue],
}

impl<'v> ListRef<'v> {
    /// `type([])`, which is `"list"`.
    pub const TYPE: &'static str = ListData::TYPE;

    pub(crate) fn new<'a>(slice: &'a [Value<'v>]) -> &'a ListRef<'v> {
        coerce(slice)
    }

    /// List elements.
    pub fn content(&self) -> &[Value<'v>] {
        &self.content
    }

    /// Iterate over the elements in the list.
    pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = Value<'v>> + 'a
    where
        'v: 'a,
    {
        self.content.iter().copied()
    }

    /// Downcast the value to the list or frozen list (both are represented by `ListRef`).
    pub fn from_value(x: Value<'v>) -> Option<&'v ListRef<'v>> {
        if x.unpack_frozen().is_some() {
            x.downcast_ref::<ListGen<FrozenListData>>()
                .map(|x| ListRef::new(coerce(x.0.content())))
        } else {
            let ptr = x.downcast_ref::<ListGen<ListData>>()?;
            Some(ListRef::new(ptr.0.content()))
        }
    }

    /// Downcast the list.
    pub fn from_frozen_value<'f>(x: FrozenValue) -> Option<&'f ListRef<'f>> {
        x.downcast_ref::<ListGen<FrozenListData>>()
            .map(|x| ListRef::new(coerce(x.0.content())))
    }
}

impl FrozenListRef {
    /// `type([])`, which is `"list"`.
    pub const TYPE: &'static str = ListRef::TYPE;

    fn new(slice: &[FrozenValue]) -> &FrozenListRef {
        coerce(slice)
    }

    /// Downcast to the frozen list.
    ///
    /// This function returns `None` if the value is not a list or the list is not frozen.
    pub fn from_value(x: Value) -> Option<&'static FrozenListRef> {
        Self::from_frozen_value(x.unpack_frozen()?)
    }

    /// Downcast to the frozen list.
    ///
    /// This function returns `None` if the value is not a frozen list.
    /// (Value cannot be a mutable list because value is frozen.)
    pub fn from_frozen_value(x: FrozenValue) -> Option<&'static FrozenListRef> {
        x.downcast_ref::<ListGen<FrozenListData>>()
            .map(|x| FrozenListRef::new(x.0.content()))
    }
}

impl<'v> Deref for ListRef<'v> {
    type Target = [Value<'v>];

    fn deref(&self) -> &[Value<'v>] {
        &self.content
    }
}

impl Deref for FrozenListRef {
    type Target = [FrozenValue];

    fn deref(&self) -> &[FrozenValue] {
        &self.content
    }
}

impl<'v> Display for ListRef<'v> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        display_list(&self.content, f)
    }
}

impl Display for FrozenListRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        display_list(coerce(&self.content), f)
    }
}

impl<'v> StarlarkTypeRepr for &'v ListRef<'v> {
    fn starlark_type_repr() -> Ty {
        Vec::<Value<'v>>::starlark_type_repr()
    }
}

impl<'v> StarlarkTypeRepr for &'v FrozenListRef {
    fn starlark_type_repr() -> Ty {
        Vec::<FrozenValue>::starlark_type_repr()
    }
}

impl<'v> UnpackValue<'v> for &'v ListRef<'v> {
    fn expected() -> String {
        "list".to_owned()
    }

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

impl<'v> UnpackValue<'v> for &'v FrozenListRef {
    fn expected() -> String {
        "frozen list".to_owned()
    }

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


/*
 * 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.
 */

use std::any::TypeId;
use std::cell::Cell;
use std::cmp;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::slice;

use allocative::Allocative;
use display_container::fmt_container;
use serde::Serialize;
use starlark_derive::starlark_value;
use starlark_derive::StarlarkDocs;
use starlark_derive::Trace;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::coerce;
use crate::environment::Methods;
use crate::environment::MethodsStatic;
use crate::hint::likely;
use crate::hint::unlikely;
use crate::private::Private;
use crate::slice_vec_ext::SliceExt;
use crate::slice_vec_ext::VecExt;
use crate::typing::Ty;
use crate::values::array::Array;
use crate::values::comparison::compare_slice;
use crate::values::comparison::equals_slice;
use crate::values::error::ValueError;
use crate::values::index::apply_slice;
use crate::values::index::convert_index;
use crate::values::list::ListRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::AllocFrozenValue;
use crate::values::AllocValue;
use crate::values::FrozenHeap;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;
use crate::values::ValueTyped;

#[derive(
    Clone,
    Default,
    Trace,
    Debug,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "standard")]
#[repr(transparent)]
pub(crate) struct ListGen<T>(pub(crate) T);

/// Define the mutable list type.
#[derive(Trace, Debug, ProvidesStaticType, Allocative)]
pub(crate) struct ListData<'v> {
    /// The data stored by the list.
    #[allocative(skip)]
    pub(crate) content: Cell<ValueTyped<'v, Array<'v>>>,
}

/// Define the frozen list type.
#[derive(ProvidesStaticType, Allocative)]
#[repr(C)]
pub(crate) struct FrozenListData {
    len: usize,
    /// The data stored by the tuple.
    content: [FrozenValue; 0],
}

impl Debug for FrozenListData {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("FrozenList")
            .field("content", &self.content())
            .finish()
    }
}

/// Alias is used in `StarlarkDocs` derive.
pub(crate) type FrozenList = ListGen<FrozenListData>;

impl ListGen<FrozenListData> {
    pub(crate) fn offset_of_content() -> usize {
        memoffset::offset_of!(FrozenListData, content)
    }
}

impl<'v> ListData<'v> {
    #[inline]
    pub(crate) fn from_value_mut(x: Value<'v>) -> anyhow::Result<&'v Self> {
        #[derive(thiserror::Error, Debug)]
        #[error("Value is not list, value type: `{0}`")]
        struct NotListError(&'static str);

        #[cold]
        #[inline(never)]
        fn error<'v>(x: Value<'v>) -> anyhow::Error {
            if x.downcast_ref::<ListGen<FrozenListData>>().is_some() {
                ValueError::CannotMutateImmutableValue.into()
            } else {
                NotListError(x.get_type()).into()
            }
        }

        if let Some(x) = x.downcast_ref::<ListGen<ListData<'v>>>() {
            x.0.check_can_mutate()?;
            Ok(&x.0)
        } else {
            Err(error(x))
        }
    }

    pub(crate) unsafe fn from_value_unchecked_mut(x: Value<'v>) -> &'v Self {
        let list = x.downcast_ref_unchecked::<ListGen<ListData<'v>>>();
        debug_assert!(list.0.check_can_mutate().is_ok());
        &list.0
    }

    pub(crate) fn is_list_type(x: TypeId) -> bool {
        x == TypeId::of::<ListGen<ListData>>() || x == TypeId::of::<ListGen<FrozenListData>>()
    }

    /// Return an error if there's at least one iterator over the list.
    fn check_can_mutate(&self) -> anyhow::Result<()> {
        if unlikely(self.content.get().as_ref().iter_count_is_non_zero()) {
            return Err(ValueError::MutationDuringIteration.into());
        }
        Ok(())
    }

    #[cold]
    #[inline(never)]
    fn reserve_additional_slow(&self, additional: usize, heap: &'v Heap) {
        let new_cap = cmp::max(self.len() + additional, self.len() * 2);
        // Size of `Array` is 2 words and size of `List` is one word,
        // so allocating at least 4 words would not be too large waste.
        // Note `Vec` allocates 4 by default.
        // Also note `Array` removes extra capacity on GC.
        let new_cap = cmp::max(new_cap, 4);

        let new_array = heap.alloc_array(new_cap);
        new_array.extend_from_slice(self.content());
        self.content.set(new_array);
    }

    #[inline(always)]
    fn reserve_additional(&self, additional: usize, heap: &'v Heap) {
        if likely(self.content.get().as_ref().remaining_capacity() >= additional) {
            return;
        }

        self.reserve_additional_slow(additional, heap);
    }

    pub(crate) fn double(&self, heap: &'v Heap) {
        self.reserve_additional(self.len(), heap);
        self.content.get().double();
    }

    #[inline]
    pub(crate) fn extend<I: IntoIterator<Item = Value<'v>>>(&self, iter: I, heap: &'v Heap) {
        let iter = iter.into_iter();
        let (lo, hi) = iter.size_hint();
        match hi {
            Some(hi) if lo == hi => {
                // Exact size iterator.
                self.reserve_additional(lo, heap);
                // Extend will panic if upper bound is provided incorrectly.
                self.content.get().extend(iter);
            }
            Some(hi) if self.content.get().remaining_capacity() >= hi => {
                // Enough capacity for upper bound.
                // Extend will panic if upper bound is provided incorrectly.
                self.content.get().extend(iter);
            }
            _ => {
                // Default slow version.
                self.reserve_additional(iter.size_hint().0, heap);
                for item in iter {
                    self.push(item, heap);
                }
            }
        }
    }

    pub(crate) fn push(&self, value: Value<'v>, heap: &'v Heap) {
        self.reserve_additional(1, heap);
        self.content.get().push(value);
    }

    pub(crate) fn clear(&self) {
        self.content.get().clear();
    }

    pub(crate) fn insert(&self, index: usize, value: Value<'v>, heap: &'v Heap) {
        self.reserve_additional(1, heap);
        self.content.get().insert(index, value);
    }

    pub(crate) fn remove(&self, index: usize) -> Value<'v> {
        self.content.get().remove(index)
    }
}

impl<'v, V: AllocValue<'v>> AllocValue<'v> for Vec<V> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_list_iter(self.into_iter().map(|x| x.alloc_value(heap)))
    }
}

impl<V: AllocFrozenValue> AllocFrozenValue for Vec<V> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_list(&self.into_map(|x| x.alloc_frozen_value(heap)))
    }
}

impl<'a, 'v, V: 'a> StarlarkTypeRepr for &'a [V]
where
    &'a V: AllocValue<'v> + StarlarkTypeRepr,
{
    fn starlark_type_repr() -> Ty {
        Vec::<&'a V>::starlark_type_repr()
    }
}

impl<'a, 'v, V: 'a> AllocValue<'v> for &'a [V]
where
    &'a V: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_list_iter(self.iter().map(|x| x.alloc_value(heap)))
    }
}

impl<'a, V: 'a> AllocFrozenValue for &'a [V]
where
    &'a V: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        heap.alloc_list(&self.map(|x| x.alloc_frozen_value(heap)))
    }
}

impl FrozenListData {
    pub(crate) const unsafe fn new(len: usize) -> FrozenListData {
        FrozenListData { len, content: [] }
    }

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

    pub(crate) fn content(&self) -> &[FrozenValue] {
        unsafe { slice::from_raw_parts(self.content.as_ptr(), self.len) }
    }

    /// Obtain the [`FrozenListData`] pointed at by a [`FrozenValue`].
    #[allow(clippy::trivially_copy_pass_by_ref)]
    // We need a lifetime because FrozenValue doesn't contain the right lifetime
    pub fn from_frozen_value(x: &FrozenValue) -> Option<&FrozenListData> {
        x.downcast_ref::<ListGen<FrozenListData>>().map(|x| &x.0)
    }
}

impl<'v> ListData<'v> {
    /// The result of calling `type()` on lists.
    pub const TYPE: &'static str = "list";

    /// Type of list as frozen string value.
    pub fn get_type_value_static() -> FrozenStringValue {
        ListGen::<FrozenListData>::get_type_value_static()
    }

    pub(crate) fn new(content: ValueTyped<'v, Array<'v>>) -> Self {
        ListData {
            content: Cell::new(content),
        }
    }

    /// Obtain the length of the list.
    pub fn len(&self) -> usize {
        self.content.get().len()
    }

    /// List content.
    ///
    /// Note this operation does not prevent mutation of this list while
    /// holding the slice. But such mutation does not violate memory-safety.
    pub fn content(&self) -> &[Value<'v>] {
        self.content.get().as_ref().content()
    }
}

impl<'v> Display for ListData<'v> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        display_list(self.content.get().content(), f)
    }
}

impl Display for FrozenListData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        display_list(coerce(&self.content()), f)
    }
}

// This trait need to be `pub(crate)` because `ListGen<T>` is.
pub(crate) trait ListLike<'v>: Debug + Allocative {
    fn content(&self) -> &[Value<'v>];
    fn set_at(&self, i: usize, v: Value<'v>) -> anyhow::Result<()>;

    // These functions are unsafe for the same reason
    // `StarlarkValue` iterator functions are unsafe.
    unsafe fn new_iter(&self, me: Value<'v>) -> Value<'v>;
    unsafe fn iter_size_hint(&self, index: usize) -> (usize, Option<usize>);
    unsafe fn iter_next(&self, index: usize) -> Option<Value<'v>>;
    unsafe fn iter_stop(&self);
}

impl<'v> ListLike<'v> for ListData<'v> {
    fn content(&self) -> &[Value<'v>] {
        self.content.get().as_ref().content()
    }

    fn set_at(&self, i: usize, v: Value<'v>) -> anyhow::Result<()> {
        self.check_can_mutate()?;
        self.content.get().set_at(i, v);
        Ok(())
    }

    unsafe fn new_iter(&self, _me: Value<'v>) -> Value<'v> {
        self.content.get().inc_iter_count();
        self.content.get().to_value()
    }

    unsafe fn iter_size_hint(&self, _index: usize) -> (usize, Option<usize>) {
        panic!("Iteration is performed on Array")
    }

    unsafe fn iter_next(&self, _index: usize) -> Option<Value<'v>> {
        panic!("Iteration is performed on Array")
    }

    unsafe fn iter_stop(&self) {
        panic!("Iteration is performed on Array")
    }
}

impl<'v> ListLike<'v> for FrozenListData {
    fn content(&self) -> &[Value<'v>] {
        coerce(self.content())
    }

    fn set_at(&self, _i: usize, _v: Value<'v>) -> anyhow::Result<()> {
        Err(ValueError::CannotMutateImmutableValue.into())
    }

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

    unsafe fn new_iter(&self, me: Value<'v>) -> Value<'v> {
        me
    }

    unsafe fn iter_next(&self, index: usize) -> Option<Value<'v>> {
        self.content().get(index).map(|v| v.to_value())
    }

    unsafe fn iter_stop(&self) {}
}

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

pub(crate) fn display_list(xs: &[Value], f: &mut fmt::Formatter<'_>) -> fmt::Result {
    fmt_container(f, "[", "]", xs.iter())
}

pub(crate) fn list_methods() -> Option<&'static Methods> {
    static RES: MethodsStatic = MethodsStatic::new();
    RES.methods(crate::stdlib::list::list_methods)
}

#[starlark_value(type = ListData::TYPE)]
impl<'v, T: ListLike<'v> + 'v> StarlarkValue<'v> for ListGen<T>
where
    Self: ProvidesStaticType<'v> + Display,
{
    fn is_special(_: Private) -> bool
    where
        Self: Sized,
    {
        true
    }

    fn get_methods() -> Option<&'static Methods> {
        list_methods()
    }

    fn collect_repr(&self, s: &mut String) {
        // Fast path as repr() for lists is quite hot
        s.push('[');
        for (i, v) in self.0.content().iter().enumerate() {
            if i != 0 {
                s.push_str(", ");
            }
            v.collect_repr(s);
        }
        s.push(']');
    }

    fn collect_repr_cycle(&self, collector: &mut String) {
        collector.push_str("[...]");
    }

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

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        match ListRef::from_value(other) {
            None => Ok(false),
            Some(other) => equals_slice(self.0.content(), &other.content, |x, y| x.equals(*y)),
        }
    }

    fn compare(&self, other: Value<'v>) -> anyhow::Result<Ordering> {
        match ListRef::from_value(other) {
            None => ValueError::unsupported_with(self, "cmp()", other),
            Some(other) => compare_slice(self.0.content(), &other.content, |x, y| x.compare(*y)),
        }
    }

    fn at(&self, index: Value, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let i = convert_index(index, self.0.content().len() as i32)? as usize;
        Ok(self.0.content()[i])
    }

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

    fn is_in(&self, other: Value<'v>) -> anyhow::Result<bool> {
        for x in self.0.content().iter() {
            if x.equals(other)? {
                return Ok(true);
            }
        }
        Ok(false)
    }

    fn slice(
        &self,
        start: Option<Value>,
        stop: Option<Value>,
        stride: Option<Value>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        let xs = self.0.content();
        let res = apply_slice(xs, start, stop, stride)?;
        Ok(heap.alloc_list(&res))
    }

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

    unsafe fn iter_size_hint(&self, index: usize) -> (usize, Option<usize>) {
        self.0.iter_size_hint(index)
    }

    unsafe fn iter_next(&self, index: usize, _heap: &'v Heap) -> Option<Value<'v>> {
        self.0.iter_next(index)
    }

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

    fn add(&self, other: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
        ListRef::from_value(other)
            .map(|other| Ok(heap.alloc_list_concat(self.0.content(), other.content())))
    }

    fn mul(&self, other: Value, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        let l = i32::unpack_param(other)?;
        let mut result = Vec::with_capacity(self.0.content().len() * cmp::max(0, l) as usize);
        for _ in 0..l {
            result.extend(self.0.content().iter());
        }
        Ok(heap.alloc_list(&result))
    }

    fn set_at(&self, index: Value<'v>, alloc_value: Value<'v>) -> anyhow::Result<()> {
        let i = convert_index(index, self.0.content().len() as i32)? as usize;
        self.0.set_at(i, alloc_value)
    }
}

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

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

    #[test]
    fn test_to_str() {
        assert::all_true(
            r#"
str([1, 2, 3]) == "[1, 2, 3]"
str([1, [2, 3]]) == "[1, [2, 3]]"
str([]) == "[]"
"#,
        );
    }

    #[test]
    fn test_repr_cycle() {
        assert::eq("l = []; l.append(l); repr(l)", "'[[...]]'");
        assert::eq("l = []; l.append(l); str(l)", "'[[...]]'");
    }

    #[test]
    fn test_mutate_list() {
        assert::is_true(
            r#"
v = [1, 2, 3]
v[1] = 1
v[2] = [2, 3]
v == [1, 1, [2, 3]]
"#,
        );
    }

    #[test]
    fn test_arithmetic_on_list() {
        assert::all_true(
            r#"
[1, 2, 3] + [2, 3] == [1, 2, 3, 2, 3]
[1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
"#,
        );
    }

    #[test]
    fn test_value_alias() {
        assert::is_true(
            r#"
v1 = [1, 2, 3]
v2 = v1
v2[2] = 4
v1 == [1, 2, 4] and v2 == [1, 2, 4]
"#,
        );
    }

    #[test]
    fn test_mutating_imports() {
        let mut a = Assert::new();
        a.module(
            "x",
            r#"
frozen_list = [1, 2]
frozen_list += [4]
def frozen_list_result():
    return frozen_list
def list_result():
    return [1, 2, 4]
"#,
        );
        a.fail("load('x','frozen_list')\nfrozen_list += [1]", "Immutable");
        a.fail(
            "load('x','frozen_list_result')\nx = frozen_list_result()\nx += [1]",
            "Immutable",
        );
        a.is_true("load('x','list_result')\nx = list_result()\nx += [8]\nx == [1, 2, 4, 8]");
    }

    #[test]
    fn test_compare() {
        // Lexicographic comparison.
        assert::is_true("[1, 2] < [10]");
    }
}


/*
 * 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 `None` type.

mod none_or;
mod none_type;

pub use none_or::NoneOr;
pub use none_type::NoneType;


/*
 * 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 either::Either;

use crate::typing::Ty;
use crate::values::none::NoneType;
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::UnpackValue;
use crate::values::Value;

/// Equivalent of a Rust [`Option`], where `None`
/// is encoded as [`NoneType`](crate::values::none::NoneType).
/// Useful for its [`UnpackValue`] instance.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Dupe)]
pub enum NoneOr<T> {
    /// Starlark `None`.
    None,
    /// Not `None`.
    Other(T),
}

impl<T> NoneOr<T> {
    /// Convert the [`NoneOr`] to a real Rust [`Option`].
    pub fn into_option(self) -> Option<T> {
        match self {
            Self::None => None,
            Self::Other(x) => Some(x),
        }
    }

    /// Is the value a [`NoneOr::None`].
    pub fn is_none(&self) -> bool {
        matches!(self, NoneOr::None)
    }
}

impl<T: StarlarkTypeRepr> StarlarkTypeRepr for NoneOr<T> {
    fn starlark_type_repr() -> Ty {
        Either::<NoneType, T>::starlark_type_repr()
    }
}

impl<'v, T: UnpackValue<'v>> UnpackValue<'v> for NoneOr<T> {
    fn expected() -> String {
        format!("None or {}", T::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if value.is_none() {
            Some(NoneOr::None)
        } else {
            T::unpack_value(value).map(NoneOr::Other)
        }
    }
}

impl<'v, T: AllocValue<'v>> AllocValue<'v> for NoneOr<T> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        match self {
            NoneOr::None => Value::new_none(),
            NoneOr::Other(x) => x.alloc_value(heap),
        }
    }
}

impl<T: AllocFrozenValue> AllocFrozenValue for NoneOr<T> {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        match self {
            NoneOr::None => FrozenValue::new_none(),
            NoneOr::Other(x) => x.alloc_frozen_value(heap),
        }
    }
}


/*
 * 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::hash::Hasher;

use allocative::Allocative;
use derive_more::Display;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
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::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;

/// Define the None type, use [`NoneType`] in Rust.
#[derive(
    Debug,
    Clone,
    Dupe,
    ProvidesStaticType,
    Display,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "standard")]
#[display(fmt = "None")]
pub struct NoneType;

impl NoneType {
    /// The result of `type(None)`.
    pub const TYPE: &'static str = "NoneType";
}

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

    fn equals(&self, other: Value) -> anyhow::Result<bool> {
        // We always compare pointers before calling `equals`,
        // so if we are here, the other is definitely not `None`.
        debug_assert!(!other.is_none());
        Ok(false)
    }

    fn to_bool(&self) -> bool {
        false
    }
    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        // just took the result of hash(None) in macos python 2.7.10 interpreter.
        hasher.write_u64(9_223_380_832_852_120_682);
        Ok(())
    }

    fn get_hash(&self, _private: Private) -> anyhow::Result<StarlarkHashValue> {
        // Just a random number.
        Ok(StarlarkHashValue::new_unchecked(0xf9c2263d))
    }

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

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

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

impl Serialize for NoneType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_none()
    }
}

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

impl<'v> UnpackValue<'v> for NoneType {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        if value.is_none() {
            Some(NoneType)
        } else {
            None
        }
    }
}


/*
 * 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::fmt;
use std::fmt::Display;
use std::hash::Hash;

use allocative::Allocative;
use dupe::Dupe;
use starlark_derive::starlark_value;
use starlark_derive::Freeze;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;
use starlark_derive::Trace;
use starlark_map::StarlarkHasher;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::starlark_complex_value;
use crate::values::typing::TypeCompiled;
use crate::values::StarlarkValue;
use crate::values::ValueLike;

/// The result of `field()`.
#[derive(
    Clone,
    Debug,
    Dupe,
    Trace,
    Freeze,
    NoSerialize,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "extension")]
pub struct FieldGen<V> {
    pub(crate) typ: TypeCompiled<V>,
    pub(crate) default: Option<V>,
}

impl<'v, V: ValueLike<'v>> Display for FieldGen<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "field(")?;
        Display::fmt(&self.typ, f)?;
        if let Some(d) = &self.default {
            write!(f, ", ")?;
            Display::fmt(d, f)?;
        }
        write!(f, ")")
    }
}

// Manual because no instance for Option<V>
unsafe impl<From: Coerce<To>, To> Coerce<FieldGen<To>> for FieldGen<From> {}

starlark_complex_value!(pub(crate) Field);

impl<V> FieldGen<V> {
    pub(crate) fn new(typ: TypeCompiled<V>, default: Option<V>) -> Self {
        Self { typ, default }
    }
}

#[starlark_value(type = "field")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for FieldGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.typ.write_hash(hasher)?;
        self.default.is_some().hash(hasher);
        if let Some(d) = self.default {
            d.write_hash(hasher)?;
        }
        Ok(())
    }
}