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

//! Implementation of `record` function.

use dupe::Dupe;
use starlark_derive::starlark_module;

use crate as starlark;
use crate::collections::SmallMap;
use crate::environment::GlobalsBuilder;
use crate::values::record::field::Field;
use crate::values::record::record_type::RecordType;
use crate::values::typing::TypeCompiled;
use crate::values::Heap;
use crate::values::Value;

#[starlark_module]
pub(crate) fn register_record(builder: &mut GlobalsBuilder) {
    /// A `record` type represents a set of named values, each with their own type.
    ///
    /// For example:
    ///
    /// ```python
    /// MyRecord = record(host=str.type, port=int.type)
    /// ```
    ///
    /// This above statement defines a record `MyRecord` with 2 fields, the first named `host` that must be of type `str.type`, and the second named `port` that must be of type `int.type`.
    ///
    /// Now `MyRecord` is defined, it's possible to do the following:
    ///
    /// * Create values of this type with `MyRecord(host="localhost", port=80)`. It is a runtime error if any arguments are missed, of the wrong type, or if any unexpected arguments are given.
    /// * Get the type of the record suitable for a type annotation with `MyRecord.type`.
    /// * Get the fields of the record. For example, `v = MyRecord(host="localhost", port=80)` will provide `v.host == "localhost"` and `v.port == 80`. Similarly, `dir(v) == ["host", "port"]`.
    ///
    /// It is also possible to specify default values for parameters using the `field` function.
    ///
    /// For example:
    ///
    /// ```python
    /// MyRecord = record(host=str.type, port=field(int.type, 80))
    /// ```
    ///
    /// Now the `port` field can be omitted, defaulting to `80` is not present (for example, `MyRecord(host="localhost").port == 80`).
    ///
    /// Records are stored deduplicating their field names, making them more memory efficient than dictionaries.
    fn record<'v>(
        #[starlark(kwargs)] kwargs: SmallMap<String, Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<RecordType<'v>> {
        // Every Value must either be a field or a value (the type)
        let mut mp = SmallMap::with_capacity(kwargs.len());
        for (k, v) in kwargs.into_iter_hashed() {
            let field = match Field::from_value(v) {
                None => Field::new(TypeCompiled::new(v, heap)?, None),
                Some(v) => v.dupe(),
            };
            mp.insert_hashed(k, field);
        }
        Ok(RecordType::new(mp))
    }

    /// Creates a field record. Used as an argument to the `record` function.
    ///
    /// ```
    /// # starlark::assert::is_true(r#"
    /// rec_type = record(host=field(str.type), port=field(int.type), mask=field(int.type, default=255))
    /// rec = rec_type(host="localhost", port=80)
    /// rec.port == 80
    /// rec.mask == 255
    /// # "#);
    /// ```
    fn field<'v>(
        #[starlark(require = pos)] typ: Value<'v>,
        default: Option<Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<Field<'v>> {
        // We compile the type even if we don't have a default to raise the error sooner
        let compiled = TypeCompiled::new(typ, heap)?;
        if let Some(d) = default {
            compiled.check_type(d, Some("default"))?;
        }
        Ok(Field::new(compiled, default))
    }
}

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

    #[test]
    fn test_record() {
        assert::pass(
            r#"
rec_type = record(host=str.type, port=int.type)
rec1 = rec_type(host = "test", port=80)
rec2 = rec_type(host = "test", port=90)
assert_eq(rec1, rec1)
assert_eq(rec1 == rec2, False)
assert_eq(rec1.host, "test")
assert_eq(rec1.port, 80)
assert_eq(dir(rec1), ["host", "port"])
"#,
        );
        assert::fails(
            r#"
rec_type = record(host=str.type, port=int.type)
rec_type(host=1, port=80)
"#,
            &[
                "Value `1` of type `int` does not match the type annotation `str.type` for argument `host`",
            ],
        );
        assert::fails(
            r#"
rec_type = record(host=str.type, port=int.type)
rec_type(port=80)
"#,
            &["Missing parameter", "`host`"],
        );
        assert::fails(
            r#"
rec_type = record(host=str.type, port=int.type)
rec_type(host="localhost", port=80, mask=255)
"#,
            &["extra named", "mask"],
        );
        assert::pass(
            r#"
rec_type = record(host=str.type, port=int.type)
def foo(x: rec_type.type) -> "rec_type":
    return x
foo(rec_type(host="localhost", port=80))"#,
        );
        assert::pass(
            r#"
v = [record(host=str.type, port=int.type)]
v_0 = v[0]
def foo(y: v_0.type) -> "record":
    # TODO(nga): fails at compile time.
    return noop(y)
foo(v[0](host="localhost", port=80))"#,
        );
        assert::pass(
            r#"
rec_type = record(host=str.type, port=field(int.type, 80), mask=int.type)
assert_eq(rec_type(host="localhost", mask=255), rec_type(host="localhost", port=80, mask=255))"#,
        );
        // Make sure the default value is heap allocated (used to fail with a GC issue)
        assert::pass(
            r#"
heap_string = "test{}".format(42)
rec_type = record(test_gc=field(str.type, heap_string))
assert_eq(rec_type().test_gc, "test42")"#,
        );
    }

    #[test]
    fn test_record_equality() {
        assert::pass(
            r#"
rec_type = record(host=str.type, port=field(int.type, 80))
assert_eq(rec_type(host="s"), rec_type(host="s"))
assert_eq(rec_type(host="s"), rec_type(host="s", port=80))
assert_ne(rec_type(host="s"), rec_type(host="t"))
"#,
        );

        let mut a = Assert::new();
        a.module(
            "m",
            r#"
rec_type = record(host=str.type, port=field(int.type, 80))
rec_val = rec_type(host="s")
"#,
        );
        a.pass(
            r#"
load('m', 'rec_type', 'rec_val')
assert_eq(rec_val, rec_type(host="s"))
assert_ne(rec_val, rec_type(host="t"))
"#,
        );

        a = Assert::new();
        a.module(
            "m",
            r#"
rt = record(host=str.type)
"#,
        );
        a.pass(
            r#"
load('m', r1='rt')
rt = record(host=str.type)
diff = record(host=str.type)
assert_eq(r1(host="test"), rt(host="test"))
assert_ne(r1(host="test"), diff(host="test"))
"#,
        );
    }

    #[test]
    fn test_field_invalid() {
        assert::fails(
            "field(str.type, None)",
            &["does not match the type", "`default`"],
        );
        assert::fails("field(True)", &["`True`", "not a valid type"]);
    }
}


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

use allocative::Allocative;
use display_container::fmt_keyed_container;
use either::Either;
use serde::Serialize;
use starlark_derive::starlark_value;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::collections::Hashed;
use crate::collections::SmallMap;
use crate::collections::StarlarkHasher;
use crate::starlark_complex_value;
use crate::values::comparison::equals_slice;
use crate::values::record::field::FieldGen;
use crate::values::record::record_type::record_fields;
use crate::values::record::record_type::FrozenRecordType;
use crate::values::record::record_type::RecordType;
use crate::values::types::exported_name::ExportedName;
use crate::values::Freeze;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Trace;
use crate::values::Value;
use crate::values::ValueLike;

/// An actual record.
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct RecordGen<V> {
    pub(crate) typ: V, // Must be RecordType
    pub(crate) values: Box<[V]>,
}

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

starlark_complex_value!(pub Record);

impl<'v, V: ValueLike<'v>> RecordGen<V> {
    /// `type(x)` for records.
    pub const TYPE: &'static str = "record";

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

    fn get_record_fields(&self) -> &'v SmallMap<String, FieldGen<Value<'v>>> {
        record_fields(self.get_record_type())
    }

    /// Iterate over the elements in the record.
    pub fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = (&'v str, V)> + 'a
    where
        'v: 'a,
    {
        self.get_record_fields()
            .keys()
            .map(String::as_str)
            .zip(self.values.iter().copied())
    }
}

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

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        match Record::from_value(other) {
            Some(other) if self.typ.equals(other.typ)? => {
                equals_slice(&self.values, &other.values, |x, y| x.equals(*y))
            }
            _ => Ok(false),
        }
    }

    fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option<Value<'v>> {
        self.get_attr_hashed(Hashed::new(attribute), heap)
    }

    fn get_attr_hashed(&self, attribute: Hashed<&str>, _heap: &'v Heap) -> Option<Value<'v>> {
        let i = self.get_record_fields().get_index_of_hashed(attribute)?;
        Some(self.values[i].to_value())
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.typ.write_hash(hasher)?;
        for v in &*self.values {
            v.write_hash(hasher)?;
        }
        Ok(())
    }

    fn dir_attr(&self) -> Vec<String> {
        self.get_record_fields().keys().cloned().collect()
    }
}

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


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

//! A `record` type, comprising of a fixed set of fields.
//!
//! Calling `record()` produces a `RecordType`. Calling `RecordType` produces a [`Record`].
//! The field names of the record are only stored once, potentially reducing memory usage.
//! Created in Starlark using the `record()` function, which accepts keyword arguments.
//! The keys become field names, and values are the types. Calling the resulting
//! function produces an actual record.
//!
//! ```
//! # starlark::assert::is_true(r#"
//! IpAddress = record(host=str.type, port=int.type)
//! rec = IpAddress(host="localhost", port=80)
//! rec.port == 80
//! # "#);
//! ```
//!
//! It is also possible to use `field(type, default)` type to give defaults:
//!
//! ```
//! # starlark::assert::is_true(r#"
//! IpAddress = record(host=str.type, port=field(int.type, 80))
//! rec = IpAddress(host="localhost")
//! rec.port == 80
//! # "#);
//! ```

pub(crate) mod field;
pub(crate) mod globals;
pub(crate) mod instance;
pub(crate) mod record_type;

pub use crate::values::record::instance::Record;


/*
 * 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 display_container::fmt_keyed_container;
use either::Either;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkDocs;
use starlark_derive::Trace;
use starlark_map::small_map::SmallMap;
use starlark_map::StarlarkHasher;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::coerce;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::eval::ParametersSpec;
use crate::private::Private;
use crate::starlark_complex_values;
use crate::typing::Ty;
use crate::values::exported_name::ExportedName;
use crate::values::exported_name::FrozenExportedName;
use crate::values::exported_name::MutableExportedName;
use crate::values::function::FUNCTION_TYPE;
use crate::values::record::field::FieldGen;
use crate::values::record::Record;
use crate::values::Freeze;
use crate::values::Freezer;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::Value;
use crate::values::ValueLike;

/// The result of `record()`, being the type of records.
#[derive(
    Debug,
    Trace,
    NoSerialize,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "extension")]
pub struct RecordTypeGen<V, Typ: ExportedName> {
    pub(crate) typ: Typ,
    /// The V is the type the field must satisfy (e.g. `"string"`)
    fields: SmallMap<String, FieldGen<V>>,
    /// Creating these on every invoke is pretty expensive (profiling shows)
    /// so compute them in advance and cache.
    parameter_spec: ParametersSpec<FrozenValue>,
}

impl<'v, V: ValueLike<'v>, Typ: ExportedName> Display for RecordTypeGen<V, Typ> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_keyed_container(f, "record(", ")", "=", &self.fields)
    }
}

/// Type of a record in a heap.
pub type RecordType<'v> = RecordTypeGen<Value<'v>, MutableExportedName>;
/// Type of a record in a frozen heap.
pub type FrozenRecordType = RecordTypeGen<FrozenValue, FrozenExportedName>;

starlark_complex_values!(RecordType);

pub(crate) fn record_fields<'v>(
    x: Either<&'v RecordType<'v>, &'v FrozenRecordType>,
) -> &'v SmallMap<String, FieldGen<Value<'v>>> {
    x.either(|x| &x.fields, |x| coerce(&x.fields))
}

impl<'v> RecordType<'v> {
    pub(crate) fn new(fields: SmallMap<String, FieldGen<Value<'v>>>) -> Self {
        let parameter_spec = Self::make_parameter_spec(&fields);
        Self {
            typ: MutableExportedName::default(),
            fields,
            parameter_spec,
        }
    }

    fn make_parameter_spec(
        fields: &SmallMap<String, FieldGen<Value<'v>>>,
    ) -> ParametersSpec<FrozenValue> {
        let mut parameters = ParametersSpec::with_capacity("record".to_owned(), fields.len());
        parameters.no_more_positional_args();
        for (name, field) in fields {
            if field.default.is_some() {
                parameters.optional(name);
            } else {
                parameters.required(name);
            }
        }
        parameters.finish()
    }
}

impl<'v> Freeze for RecordType<'v> {
    type Frozen = FrozenRecordType;
    fn freeze(self, freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        Ok(FrozenRecordType {
            typ: self.typ.freeze(freezer)?,
            fields: self.fields.freeze(freezer)?,
            parameter_spec: self.parameter_spec,
        })
    }
}

#[starlark_value(type = FUNCTION_TYPE)]
impl<'v, Typ: Allocative + 'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for RecordTypeGen<V, Typ>
where
    Self: ProvidesStaticType<'v>,
    FieldGen<V>: ProvidesStaticType<'v>,
    Typ: ExportedName,
{
    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        for (name, typ) in &self.fields {
            name.hash(hasher);
            // No need to hash typ.1, since it was computed from typ.0
            typ.write_hash(hasher)?;
        }
        Ok(())
    }

    fn invoke(
        &self,
        me: Value<'v>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let this = me;

        self.parameter_spec
            .parser(args, eval, |mut param_parser, eval| {
                let fields = record_fields(RecordType::from_value(this).unwrap());
                let mut values = Vec::with_capacity(fields.len());
                for (name, field) in fields.iter() {
                    let value = match field.default {
                        None => {
                            let v: Value = param_parser.next(name)?;
                            field.typ.check_type(v, Some(name))?;
                            v
                        }
                        Some(default) => {
                            let v: Option<Value> = param_parser.next_opt(name)?;
                            match v {
                                None => default,
                                Some(v) => {
                                    field.typ.check_type(v, Some(name))?;
                                    v
                                }
                            }
                        }
                    };
                    values.push(value);
                }
                Ok(eval.heap().alloc_complex(Record {
                    typ: this,
                    values: values.into_boxed_slice(),
                }))
            })
    }

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

    fn has_attr(&self, attribute: &str, _heap: &'v Heap) -> bool {
        attribute == "type"
    }

    fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option<Value<'v>> {
        if attribute == "type" {
            Some(
                heap.alloc(
                    self.typ
                        .borrow()
                        .as_ref()
                        .map_or(Record::TYPE, |s| s.as_str()),
                ),
            )
        } else {
            None
        }
    }

    fn eval_type(&self, _private: Private) -> Option<Ty> {
        // Very basic type, only checks the name.
        // Should also behave like a function.
        //
        // If record is not assigned to a variable (`typ` is unset), this code is unreachable.

        self.typ.borrow().as_ref().map(|t| Ty::name(t.as_str()))
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        fn eq<'v>(
            a: &RecordTypeGen<impl ValueLike<'v>, impl ExportedName>,
            b: &RecordTypeGen<impl ValueLike<'v>, impl ExportedName>,
        ) -> anyhow::Result<bool> {
            if a.typ.borrow() != b.typ.borrow() {
                return Ok(false);
            };
            if a.fields.len() != b.fields.len() {
                return Ok(false);
            };
            for ((k1, t1), (k2, t2)) in a.fields.iter().zip(b.fields.iter()) {
                // We require that the types and defaults are both equal.
                if k1 != k2
                    || !t1.typ.to_value().equals(t2.typ.to_value())?
                    || t1.default.map(ValueLike::to_value) != t2.default.map(ValueLike::to_value)
                {
                    return Ok(false);
                }
            }
            Ok(true)
        }

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

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

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

    #[test]
    fn test_record_type_as_type_pass() {
        assert::pass(
            r"
RecPass = record(a = field(int), b = field(int))

def f_pass(x: RecPass):
    return x.a

f_pass(RecPass(a = 1, b = 2))
",
        );
    }

    #[test]
    fn test_record_type_as_type_compile_time() {
        assert::fail(
            r"
RecFailCt1 = record(a = field(int), b = field(int))
RecFailCt2 = record(a = field(int), b = field(int))

def f_fail_ct(x: RecFailCt1):
    return x.a

f_fail_ct(RecFailCt2(a = 1, b = 2))
",
            // TODO(nga): this is runtime error, not compile time.
            "Value `record(a=1, b=2)` of type `record` does not match",
        );
    }

    #[test]
    fn test_record_type_as_type_runtime() {
        assert::fail(
            r"
RecFailRt1 = record(a = field(int), b = field(int))
RecFailRt2 = record(a = field(int), b = field(int))

def f_fail_rt(x: RecFailRt1):
    return x.a

noop(f_fail_rt)(RecFailRt2(a = 1, b = 2))
",
            "Value `record(a=1, b=2)` of type `record` does not match the type annotation",
        );
    }
}


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

//! Implementations of alloc and unpack traits for string.

use crate::typing::Ty;
use crate::values::alloc_value::AllocFrozenStringValue;
use crate::values::alloc_value::AllocStringValue;
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::StringValue;
use crate::values::UnpackValue;
use crate::values::Value;

impl AllocFrozenValue for String {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        self.alloc_frozen_string_value(heap).to_frozen_value()
    }
}

impl AllocFrozenStringValue for String {
    fn alloc_frozen_string_value(self, heap: &FrozenHeap) -> FrozenStringValue {
        heap.alloc_str(self.as_str())
    }
}

impl<'a> AllocFrozenValue for &'a str {
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        self.alloc_frozen_string_value(heap).to_frozen_value()
    }
}

impl<'a> AllocFrozenStringValue for &'a str {
    fn alloc_frozen_string_value(self, heap: &FrozenHeap) -> FrozenStringValue {
        heap.alloc_str(self)
    }
}

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

impl<'v> AllocStringValue<'v> for String {
    fn alloc_string_value(self, heap: &'v Heap) -> StringValue<'v> {
        heap.alloc_str(self.as_str())
    }
}

impl StarlarkTypeRepr for char {
    fn starlark_type_repr() -> Ty {
        String::starlark_type_repr()
    }
}

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

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

impl StarlarkTypeRepr for &'_ String {
    fn starlark_type_repr() -> Ty {
        String::starlark_type_repr()
    }
}

impl<'v> AllocValue<'v> for &'_ String {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        self.alloc_string_value(heap).to_value()
    }
}

impl<'v> AllocStringValue<'v> for &'_ String {
    fn alloc_string_value(self, heap: &'v Heap) -> StringValue<'v> {
        heap.alloc_str(self.as_str())
    }
}

impl<'v> AllocValue<'v> for &'_ str {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        self.alloc_string_value(heap).to_value()
    }
}

impl<'v> AllocStringValue<'v> for &'_ str {
    fn alloc_string_value(self, heap: &'v Heap) -> StringValue<'v> {
        heap.alloc_str(self)
    }
}

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

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

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

    fn unpack_value(value: Value<'v>) -> Option<Self> {
        value.unpack_str().map(ToOwned::to_owned)
    }
}


/*
 * 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::mem;
use std::str::FromStr;

use anyhow::Context as _;

use crate::collections::string_pool::StringPool;
use crate::values::dict::Dict;
use crate::values::Heap;
use crate::values::StringValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

/// Try parse `"aaa{}bbb"` and return `("aaa", "bbb")`.
pub(crate) fn parse_format_one(s: &str) -> Option<(String, String)> {
    let mut parser = FormatParser {
        format_str: s,
        rem_input: s,
    };
    let mut before = String::with_capacity(s.len());
    loop {
        match parser.next().ok()?? {
            FormatToken::Text(text) => before.push_str(text),
            FormatToken::Capture("") => break,
            FormatToken::Capture(_) => return None,
        }
    }

    let mut after = String::with_capacity(s.len() - before.len());
    loop {
        match parser.next().ok()? {
            Some(FormatToken::Text(text)) => after.push_str(text),
            Some(FormatToken::Capture(_)) => return None,
            None => break,
        }
    }

    Some((before, after))
}

/// Evaluate `"<before>{}<after>".format(arg)`.
pub(crate) fn format_one<'v>(
    before: &str,
    arg: Value<'v>,
    after: &str,
    heap: &'v Heap,
) -> StringValue<'v> {
    match StringValue::new(arg) {
        Some(arg) => heap.alloc_str_concat3(before, &arg, after),
        None => {
            let mut result = String::with_capacity(before.len() + after.len() + 10);
            result.push_str(before);
            arg.collect_repr(&mut result);
            result.push_str(after);
            heap.alloc_str(&result)
        }
    }
}

/// The format string can either have explicit indices,
/// or grab things sequentially, but not both.
/// FormatArgs knows which we are doing and keeps them in mind.
struct FormatArgs<'v, T: Iterator<Item = Value<'v>>> {
    // Initially we have the iterator set and the args empty.
    // If we ever ask by index, we decant the iterator into args.
    iterator: T,
    args: Vec<Value<'v>>,
    by_index: bool,
    by_order: bool,
}

impl<'v, T: Iterator<Item = Value<'v>>> FormatArgs<'v, T> {
    fn new(iterator: T) -> Self {
        Self {
            iterator,
            args: Vec::new(),
            by_index: false,
            by_order: false,
        }
    }

    fn next_ordered(&mut self) -> anyhow::Result<Value<'v>> {
        if self.by_index {
            Err(anyhow::anyhow!(
                "Cannot mix manual field specification and automatic field numbering in format string",
            ))
        } else {
            self.by_order = true;
            match self.iterator.next() {
                None => Err(anyhow::anyhow!("Not enough parameters in format string")),
                Some(x) => Ok(x),
            }
        }
    }

    fn by_index(&mut self, index: usize) -> anyhow::Result<Value<'v>> {
        if self.by_order {
            Err(anyhow::anyhow!(
                "Cannot mix manual field specification and automatic field numbering in format string",
            ))
        } else {
            if !self.by_index {
                self.args.extend(&mut self.iterator);
                self.by_index = true;
            }
            match self.args.get(index) {
                None => Err(ValueError::IndexOutOfBound(index as i32).into()),
                Some(v) => Ok(*v),
            }
        }
    }
}

/// Parser for `.format()` arguments.
struct FormatParser<'a> {
    /// The original format string.
    format_str: &'a str,
    /// Remaining part.
    rem_input: &'a str,
}

/// Token in the format string.
#[derive(Debug)]
enum FormatToken<'a> {
    /// Text to copy verbatim to the output.
    Text(&'a str),
    /// Format part inside curly braces.
    Capture(&'a str),
}

impl<'a> FormatParser<'a> {
    /// Parse the next token from the format string.
    fn next(&mut self) -> anyhow::Result<Option<FormatToken<'a>>> {
        let mut i = 0;

        while i < self.rem_input.len() {
            match self.rem_input.as_bytes()[i] {
                b'{' | b'}' if i != 0 => {
                    let (text, rem) = self.rem_input.split_at(i);
                    self.rem_input = rem;
                    return Ok(Some(FormatToken::Text(text)));
                }
                b'{' => {
                    assert!(i == 0);
                    if self.rem_input.starts_with("{{") {
                        self.rem_input = &self.rem_input[2..];
                        return Ok(Some(FormatToken::Text("{")));
                    }
                    i = 1;
                    while i < self.rem_input.len() {
                        match self.rem_input.as_bytes()[i] {
                            b'}' => {
                                let capture = &self.rem_input[1..i];
                                self.rem_input = &self.rem_input[i + 1..];
                                return Ok(Some(FormatToken::Capture(capture)));
                            }
                            b'{' => {
                                break;
                            }
                            _ => i += 1,
                        }
                    }
                    return Err(anyhow::anyhow!(
                        "Unmatched '{{' in format string `{}`",
                        self.format_str
                    ));
                }
                b'}' => {
                    assert!(i == 0);
                    if self.rem_input.starts_with("}}") {
                        self.rem_input = &self.rem_input[2..];
                        return Ok(Some(FormatToken::Text("}")));
                    }
                    return Err(anyhow::anyhow!(
                        "Standalone '}}' in format string `{}`",
                        self.format_str
                    ));
                }
                _ => i += 1,
            }
        }

        if i == 0 {
            Ok(None)
        } else {
            let text = mem::take(&mut self.rem_input);
            Ok(Some(FormatToken::Text(text)))
        }
    }
}

pub(crate) fn format<'v>(
    this: &str,
    args: impl Iterator<Item = Value<'v>>,
    kwargs: Dict<'v>,
    string_pool: &mut StringPool,
    heap: &'v Heap,
) -> anyhow::Result<StringValue<'v>> {
    let mut parser = FormatParser {
        format_str: this,
        rem_input: this,
    };
    let mut result = string_pool.alloc();
    let mut args = FormatArgs::new(args);
    while let Some(token) = parser.next()? {
        match token {
            FormatToken::Text(text) => result.push_str(text),
            FormatToken::Capture(capture) => {
                format_capture(capture, &mut args, &kwargs, &mut result)?
            }
        }
    }
    let r = heap.alloc_str(&result);
    string_pool.release(result);
    Ok(r)
}

fn format_capture<'v, T: Iterator<Item = Value<'v>>>(
    capture: &str,
    args: &mut FormatArgs<'v, T>,
    kwargs: &Dict,
    result: &mut String,
) -> anyhow::Result<()> {
    let (n, conv) = {
        if let Some((n, conv)) = capture.split_once('!') {
            (n, conv)
        } else {
            (capture, "s")
        }
    };
    let conv_s = |x: Value, result: &mut String| x.collect_str(result);
    let conv_r = |x: Value, result: &mut String| x.collect_repr(result);
    let conv: &dyn Fn(Value, &mut String) = match conv {
        "s" => &conv_s,
        "r" => &conv_r,
        c => {
            return Err(anyhow::anyhow!(
                concat!(
                    "'{}' is not a valid format string specifier, only ",
                    "'s' and 'r' are valid specifiers",
                ),
                c
            ));
        }
    };
    if n.is_empty() {
        conv(args.next_ordered()?, result);
        Ok(())
    } else if n.chars().all(|c| c.is_ascii_digit()) {
        let i = usize::from_str(n)
            .with_context(|| format!("Error parsing `{n}` as a format string index"))?;
        conv(args.by_index(i)?, result);
        Ok(())
    } else {
        if let Some(x) = n.chars().find(|c| match c {
            '.' | ',' | '[' | ']' => true,
            _ => false,
        }) {
            return Err(anyhow::anyhow!(
                "Invalid character '{}' inside replacement field",
                x
            ));
        }
        match kwargs.get_str(n) {
            None => Err(ValueError::KeyNotFound(n.to_owned()).into()),
            Some(v) => {
                conv(v, result);
                Ok(())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use starlark_map::small_map::SmallMap;

    use crate::assert;
    use crate::coerce::coerce;
    use crate::values::dict::Dict;
    use crate::values::string::dot_format::parse_format_one;
    use crate::values::string::dot_format::FormatArgs;
    use crate::values::Heap;
    use crate::values::Value;

    fn format_capture_for_test<'v, T: Iterator<Item = Value<'v>>>(
        capture: &str,
        args: &mut FormatArgs<'v, T>,
        kwargs: &Dict,
    ) -> anyhow::Result<String> {
        let mut result = String::new();
        super::format_capture(capture, args, kwargs, &mut result)?;
        Ok(result)
    }

    #[test]
    fn test_format_capture() {
        let heap = Heap::new();
        let original_args = vec![heap.alloc("1"), heap.alloc("2"), heap.alloc("3")];
        let mut args = FormatArgs::new(original_args.iter().copied());
        let mut kwargs = SmallMap::new();

        kwargs.insert_hashed(heap.alloc_str("a").get_hashed(), heap.alloc("x"));
        kwargs.insert_hashed(heap.alloc_str("b").get_hashed(), heap.alloc("y"));
        kwargs.insert_hashed(heap.alloc_str("c").get_hashed(), heap.alloc("z"));
        let kwargs = Dict::new(coerce(kwargs));
        assert_eq!(
            format_capture_for_test("", &mut args, &kwargs).unwrap(),
            "1"
        );
        assert_eq!(
            format_capture_for_test("!s", &mut args, &kwargs).unwrap(),
            "2"
        );
        assert_eq!(
            format_capture_for_test("!r", &mut args, &kwargs).unwrap(),
            "\"3\""
        );
        assert_eq!(
            format_capture_for_test("a!r", &mut args, &kwargs).unwrap(),
            "\"x\""
        );
        assert_eq!(
            format_capture_for_test("a!s", &mut args, &kwargs).unwrap(),
            "x"
        );
        assert!(format_capture_for_test("1", &mut args, &kwargs).is_err());
        let mut args = FormatArgs::new(original_args.iter().copied());
        assert_eq!(
            format_capture_for_test("1", &mut args, &kwargs).unwrap(),
            "2"
        );
        assert!(format_capture_for_test("", &mut args, &kwargs).is_err());
    }

    #[test]
    fn test_format() {
        assert::eq("'a{x}b{y}c{}'.format(1, x=2, y=3)", "'a2b3c1'")
    }

    #[test]
    fn test_parse_format_one() {
        assert_eq!(
            Some(("abc".to_owned(), "def".to_owned())),
            parse_format_one("abc{}def")
        );
        assert_eq!(
            Some(("a{b".to_owned(), "c}d{".to_owned())),
            parse_format_one("a{{b{}c}}d{{")
        );
        assert_eq!(None, parse_format_one("a{"));
        assert_eq!(None, parse_format_one("a{}{}"));
        assert_eq!(None, parse_format_one("{x}"));
    }
}


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

//! Our string operations (indexing) are O(n) because of our current representation.
//! There are plans afoot to change that, but in the meantime let's use fast algorithms
//! to make up some of the difference.

use std::cmp::min;
use std::str;

use crate::stdlib::util::convert_indices;
use crate::values::types::none::NoneOr;
use crate::values::types::string::CharIndex;

#[inline(always)]
fn is_1byte(x: u8) -> bool {
    x & 0x80 == 0
}

#[inline(always)]
fn is_1bytes(x: u64) -> bool {
    x & 0x8080808080808080 == 0
}

/// Skip at most n 1byte characters from the prefix of the string, return how many you skipped.
/// The result will be between 0 and n.
/// The string _must_ have at least n bytes in it.
fn skip_at_most_1byte(x: &str, n: usize) -> usize {
    if n == 0 {
        return 0;
    }
    debug_assert!(x.len() >= n);

    // Multi-byte UTF8 characters have 0x80 set.
    // We first process enough characters so we align on an 8-byte boundary,
    // then process 8 bytes at a time.
    // If we see a higher value, we bail to the standard Rust code.
    // It is possible to do faster with population count, but we don't expect many real UTF8 strings.
    // (c.f. https://github.com/haskell-foundation/foundation/blob/master/foundation/cbits/foundation_utf8.c)

    // Same function, but returning the end of the string
    fn f(x: &str, n: usize) -> *const u8 {
        let leading = min(x.as_ptr().align_offset(8), n);
        let trailing = (n - leading) % 8;
        let loops = (n - leading) / 8;

        // Rather than flip between string and pointer, we stick to working with the pointer
        let mut p = x.as_ptr();

        // Loop over 1 byte at a time until we reach alignment
        for _ in 0..leading {
            if is_1byte(unsafe { *p }) {
                p = unsafe { p.add(1) };
            } else {
                return p;
            }
        }

        // Loop over 8 bytes at a time, until we reach the end
        let mut p = p as *const u64;
        for _ in 0..loops {
            if is_1bytes(unsafe { *p }) {
                p = unsafe { p.add(1) };
            } else {
                return p as *const u8;
            }
        }

        // Mop up all trailing bytes
        let mut p = p as *const u8;
        for _ in 0..trailing {
            if is_1byte(unsafe { *p }) {
                p = unsafe { p.add(1) };
            } else {
                return p;
            }
        }
        return p;
    }

    unsafe { f(x, n).offset_from(x.as_ptr()) as usize }
}

/// Find the character at position `i`.
pub(crate) fn at(x: &str, i: CharIndex) -> Option<char> {
    if i.0 >= x.len() {
        // Important that skip_at_most_1byte gets called with all valid character.
        // If the index is outside the length even under the best assumptions,
        // can immediately return None.
        return None;
    }
    let n = skip_at_most_1byte(x, i.0);
    let s = unsafe { x.get_unchecked(n..) };
    s.chars().nth(i.0 - n)
}

/// Find the length of the string in characters.
/// If the length matches the length in bytes, the string must be 7bit ASCII.
pub(crate) fn len(x: &str) -> CharIndex {
    let n = skip_at_most_1byte(x, x.len());
    if n == x.len() {
        CharIndex(n) // All 1 byte
    } else {
        CharIndex(unsafe { x.get_unchecked(n..) }.chars().count() + n)
    }
}

/// Find the number of times a `needle` byte occurs within a string.
/// If the needle represents a complete character, this will be equivalent to doing
/// search for that character in the string.
pub fn count_matches_byte(x: &str, needle: u8) -> usize {
    x.as_bytes().iter().filter(|x| **x == needle).count()
}

/// Find the number of times a `needle` occurs within a string, non-overlapping.
pub fn count_matches(x: &str, needle: &str) -> usize {
    if needle.len() == 1 {
        // If we are searching for a 1-byte string, we can provide a much faster path.
        // Since it is one byte, given how UTF8 works, all the resultant slices must be UTF8 too.
        count_matches_byte(x, needle.as_bytes()[0])
    } else {
        x.matches(needle).count()
    }
}

/// Result of applying `start` and `end` to a string.
#[derive(PartialEq, Debug)]
pub(crate) struct StrIndices<'a> {
    /// Computed start char index.
    pub(crate) start: CharIndex,
    /// Substring after applying the `start` and `end` arguments.
    pub(crate) haystack: &'a str,
}

/// Split the string at given char offset. `None` if offset is out of bounds.
pub(crate) fn split_at(x: &str, i: CharIndex) -> Option<(&str, &str)> {
    if i.0 == 0 {
        return Some(("", x));
    }
    if i.0 > x.len() {
        return None;
    }
    let n = skip_at_most_1byte(x, i.0);
    let s = unsafe { x.get_unchecked(n..) };
    let mut c = s.chars();
    for _ in 0..i.0 - n {
        c.next()?;
    }
    Some(x.split_at(x.len() - c.as_str().len()))
}

/// Perform the Starlark operation `x[:i]` (`i` is an unsigned integer here).
fn split_at_end(x: &str, i: CharIndex) -> &str {
    match split_at(x, i) {
        Some((before, _)) => before,
        None => x,
    }
}

fn convert_str_indices_slow(s: &str, start: NoneOr<i32>, end: NoneOr<i32>) -> Option<StrIndices> {
    // Slow version when we need to compute full string length
    // because at least one of the indices is negative.
    debug_assert!(
        matches!(start, NoneOr::Other(start) if start < 0)
            || matches!(end, NoneOr::Other(end) if end < 0)
    );
    // If both indices are negative, we should have ruled `start > end` case before.
    debug_assert!(
        matches!((start, end), (NoneOr::Other(start), NoneOr::Other(end))
                if start >= 0 || end >= 0 || (start <= end))
            || matches!(start, NoneOr::None)
            || matches!(end, NoneOr::None)
    );
    let len = len(s);
    let (start, end) = convert_indices(len.0 as i32, start, end);
    if start > end {
        return None;
    }
    let (start, end) = (CharIndex(start), CharIndex(end));
    debug_assert!(end <= len);
    let s = if len.0 == s.len() {
        // ASCII fast path: if char len is equal to byte len,
        // we know the string is ASCII.
        unsafe { s.get_unchecked(start.0..end.0) }
    } else {
        let (_, s) = split_at(s, start).unwrap();
        let (s, _) = split_at(s, end - start).unwrap();
        s
    };
    Some(StrIndices { start, haystack: s })
}

/// Convert common `start` and `end` arguments of `str` functions like `str.find`.
#[inline(always)]
pub(crate) fn convert_str_indices(
    s: &str,
    start: NoneOr<i32>,
    end: NoneOr<i32>,
) -> Option<StrIndices> {
    match (start, end) {
        // Following cases but last optimize index computation
        // by avoiding computing the length of the string.
        (NoneOr::None, NoneOr::None) => Some(StrIndices {
            start: CharIndex(0),
            haystack: s,
        }),
        (NoneOr::Other(start), NoneOr::None) if start >= 0 => {
            let (_, s) = split_at(s, CharIndex(start as usize))?;
            Some(StrIndices {
                start: CharIndex(start as usize),
                haystack: s,
            })
        }
        (NoneOr::None, NoneOr::Other(end)) if end >= 0 => {
            let s = split_at_end(s, CharIndex(end as usize));
            Some(StrIndices {
                start: CharIndex(0),
                haystack: s,
            })
        }
        (NoneOr::Other(start), NoneOr::Other(end)) if start >= 0 && end >= start => {
            let (_, s) = split_at(s, CharIndex(start as usize))?;
            let s = split_at_end(s, CharIndex((end - start) as usize));
            Some(StrIndices {
                start: CharIndex(start as usize),
                haystack: s,
            })
        }
        (NoneOr::Other(start), NoneOr::Other(end))
            if ((start >= 0) == (end >= 0)) && start > end =>
        {
            None
        }
        (start, end) => convert_str_indices_slow(s, start, end),
    }
}

pub(crate) fn contains(haystack: &str, needle: &str) -> bool {
    if needle.is_empty() {
        true
    } else if needle.len() == 1 {
        memchr::memchr(needle.as_bytes()[0], haystack.as_bytes()).is_some()
    } else if haystack.len() < needle.len() {
        false
    } else {
        assert!(haystack.len() >= needle.len());
        // `str::contains` is very slow for short strings.
        // So use basic quadratic algorithm instead.
        let needle_0 = needle.as_bytes()[0];
        for start in 0..=haystack.len() - needle.len() {
            if haystack.as_bytes()[start] != needle_0 {
                continue;
            }
            if haystack.as_bytes()[start..].starts_with(needle.as_bytes()) {
                return true;
            }
        }
        false
    }
}

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

    use crate::values::string::fast_string::convert_str_indices;
    use crate::values::types::none::NoneOr;
    use crate::values::types::string::fast_string::StrIndices;
    use crate::values::types::string::CharIndex;

    #[test]
    fn test_convert_str_indices() {
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "abc",
            }),
            convert_str_indices("abc", NoneOr::None, NoneOr::None)
        );
        assert_eq!(
            None,
            convert_str_indices("abc", NoneOr::Other(2), NoneOr::Other(1))
        );

        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "abc",
            }),
            convert_str_indices("abc", NoneOr::Other(-10), NoneOr::Other(10))
        );
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(1),
                haystack: "",
            }),
            convert_str_indices("abc", NoneOr::Other(1), NoneOr::Other(1))
        );
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "ab",
            }),
            convert_str_indices("abc", NoneOr::Other(-10), NoneOr::Other(2))
        );
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "ab",
            }),
            convert_str_indices("abc", NoneOr::Other(-10), NoneOr::Other(-1))
        );

        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "s",
            }),
            convert_str_indices("short", NoneOr::Other(0), NoneOr::Other(-4))
        );
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(0),
                haystack: "fish",
            }),
            convert_str_indices("fish", NoneOr::None, NoneOr::Other(10))
        );
    }

    #[test]
    fn test_convert_str_indices_non_ascii() {
        assert_eq!(
            Some(StrIndices {
                start: CharIndex(6),
                haystack: "Ð¿Ð¾Ð´",
            }),
            convert_str_indices("Ð“Ð¾Ñ€Ð¾Ð´ Ð¿Ð¾Ð´ Ð¿Ð¾Ð´Ð¾ÑˆÐ²Ð¾Ð¹", NoneOr::Other(6), NoneOr::Other(9))
        )
    }

    #[test]
    fn test_convert_str_indices_trigger_debug_assertions() {
        fn none_ors() -> impl Iterator<Item = NoneOr<i32>> {
            iter::once(NoneOr::None).chain((-30..30).map(NoneOr::Other))
        }

        for s in &["", "a", "abcde", "Ð¢ÐµÐ»ÐµÐ¼Ð°Ðº"] {
            for start in none_ors() {
                for end in none_ors() {
                    let _ = convert_str_indices(s, start, end);
                }
            }
        }
    }
}


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

//! String interpolation-related code.
//! Based on <https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting>

use std::fmt::Write;

use dupe::Dupe;
use thiserror::Error;

use crate::values::float;
use crate::values::num;
use crate::values::num::NumRef;
use crate::values::string::dot_format::format_one;
use crate::values::types::tuple::value::Tuple;
use crate::values::Heap;
use crate::values::StringValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

/// Operator `%` format or evaluation errors
#[derive(Clone, Dupe, Debug, Error)]
enum StringInterpolationError {
    #[error("Too many arguments for format string")]
    TooManyParameters,
    #[error("Not enough arguments for format string")]
    NotEnoughParameters,
    #[error("Incomplete format")]
    IncompleteFormat,
    #[error("Unsupported format character: {0:?}")]
    UnsupportedFormatCharacter(char),
    #[error("Expecting format character (internal error)")]
    ExpectingFormatCharacter,
}

enum PercentSFormat {
    /// `%s`.
    Str,
    /// `%r`.
    Repr,
    /// `%d`.
    Dec,
    /// `%o`.
    Oct,
    /// `%x`.
    Hex,
    /// `%X`.
    HexUpper,
    /// `%e`.
    Exp,
    /// `%E`.
    ExpUpper,
    /// `%f` or `%F`.
    Float,
    /// `%g`.
    FloatCompact,
    /// `%G`.
    FloatCompactUpper,
}

struct PercentFormatParser<'a> {
    rem: &'a str,
}

struct Item<'a> {
    literal: &'a str,
    format: Option<PercentSFormat>,
}

impl<'a> Iterator for PercentFormatParser<'a> {
    type Item = anyhow::Result<Item<'a>>;

    #[allow(clippy::collapsible_else_if)] // Makes code more readable.
    fn next(&mut self) -> Option<Self::Item> {
        let index_of_percent = self.rem.bytes().position(|c| c == b'%');
        if let Some(index_of_percent) = index_of_percent {
            let prev_rem = self.rem;
            let (literal, rem) = self.rem.split_at(index_of_percent);
            match rem.as_bytes().get(1) {
                None => return Some(Err(StringInterpolationError::IncompleteFormat.into())),
                Some(f) => {
                    let res = match f {
                        b'%' => {
                            // Include the percent in the literal.
                            let literal = &prev_rem[..index_of_percent + 1];
                            Item {
                                literal,
                                format: None,
                            }
                        }
                        b's' => Item {
                            literal,
                            format: Some(PercentSFormat::Str),
                        },
                        b'r' => Item {
                            literal,
                            format: Some(PercentSFormat::Repr),
                        },
                        b'd' => Item {
                            literal,
                            format: Some(PercentSFormat::Dec),
                        },
                        b'o' => Item {
                            literal,
                            format: Some(PercentSFormat::Oct),
                        },
                        b'x' => Item {
                            literal,
                            format: Some(PercentSFormat::Hex),
                        },
                        b'X' => Item {
                            literal,
                            format: Some(PercentSFormat::HexUpper),
                        },
                        b'e' => Item {
                            literal,
                            format: Some(PercentSFormat::Exp),
                        },
                        b'E' => Item {
                            literal,
                            format: Some(PercentSFormat::ExpUpper),
                        },
                        b'f' | b'F' => Item {
                            literal,
                            format: Some(PercentSFormat::Float),
                        },
                        b'g' => Item {
                            literal,
                            format: Some(PercentSFormat::FloatCompact),
                        },
                        b'G' => Item {
                            literal,
                            format: Some(PercentSFormat::FloatCompactUpper),
                        },
                        _ => {
                            // Note we need to find the second character, not the second byte.
                            let Some(c) = rem.chars().nth(1) else {
                                return Some(Err(StringInterpolationError::ExpectingFormatCharacter.into()));
                            };
                            return Some(Err(
                                StringInterpolationError::UnsupportedFormatCharacter(c).into(),
                            ));
                        }
                    };
                    // We reach here only if format character is ASCII,
                    // so we can safely skip 2 bytes.
                    self.rem = &rem[2..];
                    Some(Ok(res))
                }
            }
        } else {
            if self.rem.is_empty() {
                None
            } else {
                let literal = self.rem;
                self.rem = "";
                Some(Ok(Item {
                    literal,
                    format: None,
                }))
            }
        }
    }
}

pub(crate) fn percent(format: &str, value: Value) -> anyhow::Result<String> {
    // NOTE(nga): use could reuse `Evaluator::string_pool` here, but
    //   * we don't have access to `Evaluator` in `StarlarkValue::percent`
    //   * after single %s made intrinsic, this code is not that hot now

    // random guess as a baseline capacity
    let mut res: String = String::with_capacity(format.len() + 20);

    let tuple = Tuple::from_value(value);
    let one = &[value];
    let values = match &tuple {
        Some(xs) => xs.content(),
        None => one,
    };
    let mut values = values.iter().copied();
    let mut next_value = || -> anyhow::Result<Value> {
        values
            .next()
            .ok_or_else(|| StringInterpolationError::NotEnoughParameters.into())
    };

    // because of the way format is defined, we can deal with it as bytes
    for item in (PercentFormatParser { rem: format }) {
        let item = item?;
        res.push_str(item.literal);
        match item.format {
            None => {}
            Some(PercentSFormat::Str) => {
                let arg = next_value()?;
                match arg.unpack_str() {
                    None => arg.collect_repr(&mut res),
                    Some(s) => res.push_str(s),
                }
            }
            Some(PercentSFormat::Repr) => next_value()?.collect_repr(&mut res),
            Some(PercentSFormat::Dec) => {
                let value = next_value()?;
                if let Some(num::NumRef::Float(v)) = value.unpack_num() {
                    match num::NumRef::Float(v.trunc()).as_int() {
                        None => {
                            return ValueError::unsupported(&float::StarlarkFloat(v), "%d");
                        }
                        Some(v) => write!(res, "{}", v).unwrap(),
                    }
                } else {
                    write!(res, "{}", value.to_int()?).unwrap()
                }
            }
            Some(PercentSFormat::Oct) => {
                let v = next_value()?.to_int()?;
                write!(
                    res,
                    "{}{:o}",
                    if v < 0 { "-" } else { "" },
                    v.wrapping_abs() as u64
                )
                .unwrap();
            }
            Some(PercentSFormat::Hex) => {
                let v = next_value()?.to_int()?;
                write!(
                    res,
                    "{}{:x}",
                    if v < 0 { "-" } else { "" },
                    v.wrapping_abs() as u64
                )
                .unwrap();
            }
            Some(PercentSFormat::HexUpper) => {
                let v = next_value()?.to_int()?;
                write!(
                    res,
                    "{}{:X}",
                    if v < 0 { "-" } else { "" },
                    v.wrapping_abs() as u64
                )
                .unwrap();
            }
            Some(PercentSFormat::Exp) => {
                let v = NumRef::unpack_param(next_value()?)?.as_float();
                float::write_scientific(&mut res, v, 'e', false).unwrap()
            }
            Some(PercentSFormat::ExpUpper) => {
                let v = NumRef::unpack_param(next_value()?)?.as_float();
                float::write_scientific(&mut res, v, 'E', false).unwrap()
            }
            Some(PercentSFormat::Float) => {
                let v = NumRef::unpack_param(next_value()?)?.as_float();
                float::write_decimal(&mut res, v).unwrap()
            }
            Some(PercentSFormat::FloatCompact) => {
                let v = NumRef::unpack_param(next_value()?)?.as_float();
                float::write_compact(&mut res, v, 'e').unwrap()
            }
            Some(PercentSFormat::FloatCompactUpper) => {
                let v = NumRef::unpack_param(next_value()?)?.as_float();
                float::write_compact(&mut res, v, 'E').unwrap()
            }
        }
    }
    if values.next().is_some() {
        Err(StringInterpolationError::TooManyParameters.into())
    } else {
        Ok(res)
    }
}

/// Try parse `"aaa%sbbb"` and return `("aaa", "bbb")`.
pub(crate) fn parse_percent_s_one(format: &str) -> Option<(String, String)> {
    let mut before = String::with_capacity(format.len());
    let mut chars = format.chars();
    loop {
        match chars.next()? {
            '%' => match chars.next()? {
                '%' => before.push('%'),
                's' => break,
                _ => return None,
            },
            c => before.push(c),
        }
    }
    let mut after = String::with_capacity(format.len() - before.len());
    loop {
        match chars.next() {
            Some('%') => match chars.next()? {
                '%' => after.push('%'),
                _ => return None,
            },
            Some(c) => after.push(c),
            None => break,
        }
    }
    Some((before, after))
}

/// Evaluate `"<before>%s<after>" % arg`.
pub(crate) fn percent_s_one<'v>(
    before: &str,
    arg: Value<'v>,
    after: &str,
    heap: &'v Heap,
) -> anyhow::Result<StringValue<'v>> {
    Ok(match StringValue::new(arg) {
        Some(arg) => heap.alloc_str_concat3(before, &arg, after),
        None => {
            let one = match Tuple::from_value(arg) {
                Some(tuple) => match tuple.content() {
                    [] => return Err(StringInterpolationError::NotEnoughParameters.into()),
                    [value] => *value,
                    [_, _, ..] => return Err(StringInterpolationError::TooManyParameters.into()),
                },
                None => arg,
            };
            format_one(before, one, after, heap)
        }
    })
}

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

    #[test]
    fn test_incomplete_format() {
        assert::fail("'%' % ()", "Incomplete format");
    }

    #[test]
    fn test_unsupported_format_character() {
        assert::fail("'xx%qxx' % (1,)", "Unsupported format character: 'q'");
    }

    #[test]
    fn test_parse_percent_s_one() {
        assert_eq!(
            Some(("abc".to_owned(), "def".to_owned())),
            parse_percent_s_one("abc%sdef")
        );
        assert_eq!(
            Some(("a%b".to_owned(), "c%d%".to_owned())),
            parse_percent_s_one("a%%b%sc%%d%%")
        );
        assert_eq!(None, parse_percent_s_one("a%"));
        assert_eq!(None, parse_percent_s_one("a%s%"));
        assert_eq!(None, parse_percent_s_one("a%s%s"));
        assert_eq!(None, parse_percent_s_one("%d"));
    }
}


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

//! Implementation of iterators for string type.

use allocative::Allocative;
use derive_more::Display;
use starlark_derive::starlark_value;
use starlark_derive::Freeze;
use starlark_derive::NoSerialize;
use starlark_derive::Trace;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::Coerce;
use crate::values::iter_type::StarlarkIter;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::StringValueLike;
use crate::values::Value;
use crate::values::ValueLike;
use crate::values::ValueOfUnchecked;

/// An opaque iterator over a string, produced by elems/codepoints
#[derive(
    Debug,
    Trace,
    Coerce,
    Display,
    Freeze,
    NoSerialize,
    ProvidesStaticType,
    Allocative
)]
#[display(fmt = "iterator")]
#[repr(C)]
struct StringIterableGen<'v, V: ValueLike<'v>> {
    string: V::String,
    produce_char: bool, // if not char, then int
}

pub(crate) fn iterate_chars<'v>(
    string: StringValue<'v>,
    heap: &'v Heap,
) -> ValueOfUnchecked<'v, StarlarkIter<String>> {
    ValueOfUnchecked::new(heap.alloc_complex(StringIterableGen::<'v, Value<'v>> {
        string,
        produce_char: true,
    }))
}

pub(crate) fn iterate_codepoints<'v>(
    string: StringValue<'v>,
    heap: &'v Heap,
) -> ValueOfUnchecked<'v, StarlarkIter<String>> {
    ValueOfUnchecked::new(heap.alloc_complex(StringIterableGen::<'v, Value<'v>> {
        string,
        produce_char: false,
    }))
}

#[starlark_value(type = "iterator")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for StringIterableGen<'v, V>
where
    Self: ProvidesStaticType<'v>,
{
    unsafe fn iterate(&self, _me: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        // Lazy implementation: we allocate a tuple and then iterate over it.
        let iter = if self.produce_char {
            heap.alloc_tuple_iter(self.string.as_str().chars().map(|c| heap.alloc(c)))
        } else {
            heap.alloc_tuple_iter(
                self.string
                    .as_str()
                    .chars()
                    .map(|c| heap.alloc(u32::from(c))),
            )
        };
        Ok(iter)
    }
}


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

/// Fixed length byte vector API.
pub(crate) trait Vector: Copy {
    /// Fill the vector with given byte value.
    unsafe fn splat(byte: u8) -> Self;
    /// Load the vector from given memory address.
    unsafe fn load_unaligned(ptr: *const u8) -> Self;
    /// Store the vector to given memory address.
    unsafe fn store_unaligned(self, ptr: *mut u8);
    /// **signed** element-wise comparison of the vector.
    unsafe fn cmplt(self, other: Self) -> Self;
    /// Element-wise comparison. Result elements contain 0 for false or 0xff for true.
    unsafe fn cmpeq(self, other: Self) -> Self;
    /// Bitwise or.
    unsafe fn or(self, other: Self) -> Self;
    /// Mask of the most significant bit of each element.
    /// For 16-bytes vector this instruction fills lower 16 bits of the result.
    unsafe fn movemask(self) -> u32;
}

/// Run different code depending on whether SIMD is available or not.
pub(crate) trait SwitchHaveSimd<R>
where
    Self: Sized,
{
    /// This function is called when SIMD is not available.
    fn no_simd(self) -> R;
    /// This function is called when SIMD is available.
    fn simd<V: Vector>(self) -> R;

    /// Call either `simd` or `no_simd` function.
    fn switch(self) -> R {
        // Any x86_64 supports SSE2.
        #[cfg(target_feature = "sse2")]
        {
            #[cfg(target_arch = "x86")]
            use std::arch::x86::*;
            #[cfg(target_arch = "x86_64")]
            use std::arch::x86_64::*;

            self.simd::<__m128i>()
        }
        #[cfg(not(target_feature = "sse2"))]
        {
            self.no_simd()
        }
    }
}

#[cfg(target_feature = "sse2")]
mod sse2 {
    #[cfg(target_arch = "x86")]
    use std::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use std::arch::x86_64::*;

    use crate::values::types::string::simd::Vector;

    impl Vector for __m128i {
        #[inline(always)]
        unsafe fn splat(byte: u8) -> Self {
            _mm_set1_epi8(byte as i8)
        }

        #[inline(always)]
        unsafe fn load_unaligned(ptr: *const u8) -> Self {
            _mm_loadu_si128(ptr as *const _)
        }

        #[inline(always)]
        unsafe fn store_unaligned(self, ptr: *mut u8) {
            _mm_storeu_si128(ptr as *mut _, self)
        }

        #[inline(always)]
        unsafe fn cmplt(self, other: Self) -> Self {
            _mm_cmplt_epi8(self, other)
        }

        #[inline(always)]
        unsafe fn cmpeq(self, other: Self) -> Self {
            _mm_cmpeq_epi8(self, other)
        }

        #[inline(always)]
        unsafe fn or(self, other: Self) -> Self {
            _mm_or_si128(self, other)
        }

        #[inline(always)]
        unsafe fn movemask(self) -> u32 {
            _mm_movemask_epi8(self) as u32
        }
    }
}


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

//! Generic interner for starlark strings.

use hashbrown::raw::RawTable;

use crate::collections::Hashed;
use crate::values::FrozenStringValue;

/// `[FrozenStringValue]` interner.
#[derive(Default)]
pub(crate) struct FrozenStringInterner {
    map: RawTable<FrozenStringValue>,
}

impl FrozenStringInterner {
    pub(crate) fn intern(
        &mut self,
        s: Hashed<&str>,
        alloc: impl FnOnce() -> FrozenStringValue,
    ) -> FrozenStringValue {
        match self
            .map
            .get(s.hash().promote(), |x| s == x.get_hashed_str())
        {
            Some(frozen_string) => *frozen_string,
            None => {
                let frozen_string = alloc();
                self.map.insert(s.hash().promote(), frozen_string, |x| {
                    x.get_hash().promote()
                });
                frozen_string
            }
        }
    }
}

#[cfg(test)]
mod test {
    use crate::collections::Hashed;
    use crate::values::string::intern::interner::FrozenStringInterner;
    use crate::values::FrozenHeap;

    #[test]
    fn test_intern() {
        let heap1 = FrozenHeap::new();
        let heap2 = FrozenHeap::new();
        let mut intern = FrozenStringInterner::default();

        let xx1 = intern.intern(Hashed::new("xx"), || heap1.alloc_str("xx"));
        let xx2 = intern.intern(Hashed::new("xx"), || heap2.alloc_str("xx"));
        assert!(xx1.to_value().ptr_eq(xx2.to_value()));
    }
}


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

pub(crate) mod interner;


/*
 * 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::alloc_value::AllocFrozenStringValue;
use crate::values::alloc_value::AllocStringValue;
use crate::values::structs::value::FrozenStruct;
use crate::values::structs::value::Struct;
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 struct on a heap.
///
/// # Panics
///
/// Panics if:
/// * keys are not strings
/// * keys are not unique
///
/// # Example
///
/// ```
/// use starlark::values::structs::AllocStruct;
///
/// # use starlark::values::{FrozenHeap, Heap};
/// # fn alloc(heap: &Heap, frozen_heap: &FrozenHeap) {
/// let s = heap.alloc(AllocStruct([("a", 1), ("b", 2)]));
/// let fs = frozen_heap.alloc(AllocStruct([("a", 1), ("b", 2)]));
/// # }
pub struct AllocStruct<S>(pub S);

impl AllocStruct<iter::Empty<(String, String)>> {
    /// Allocate an empty struct.
    pub const EMPTY: AllocStruct<iter::Empty<(String, String)>> = AllocStruct(iter::empty());
}

impl<'v, K, V, S> StarlarkTypeRepr for AllocStruct<S>
where
    S: IntoIterator<Item = (K, V)>,
    K: AllocStringValue<'v>,
    V: AllocValue<'v>,
{
    fn starlark_type_repr() -> Ty {
        Struct::starlark_type_repr()
    }
}

impl<'v, K, V, S> AllocValue<'v> for AllocStruct<S>
where
    S: IntoIterator<Item = (K, V)>,
    K: AllocStringValue<'v>,
    V: AllocValue<'v>,
{
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        let iter = self.0.into_iter();
        let mut fields = SmallMap::with_capacity(iter.size_hint().0);
        for (k, v) in iter {
            let k = k.alloc_string_value(heap);
            let v = v.alloc_value(heap);
            let prev = fields.insert(k, v);
            assert!(prev.is_none(), "non-unique key: {}", k);
        }
        heap.alloc(Struct::new(fields))
    }
}

impl<K, V, S> AllocFrozenValue for AllocStruct<S>
where
    S: IntoIterator<Item = (K, V)>,
    K: AllocFrozenStringValue,
    V: AllocFrozenValue,
{
    fn alloc_frozen_value(self, heap: &FrozenHeap) -> FrozenValue {
        let iter = self.0.into_iter();
        let mut fields = SmallMap::with_capacity(iter.size_hint().0);
        for (k, v) in iter {
            let k = k.alloc_frozen_string_value(heap);
            let v = v.alloc_frozen_value(heap);
            let prev = fields.insert(k, v);
            assert!(prev.is_none(), "non-unique key: {}", k);
        }
        heap.alloc(FrozenStruct::new(fields))
    }
}


/*
 * 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 struct type, an associative-map created with `struct()`.
//!
//! This struct type is related to both the [dictionary](crate::values::dict) and the
//! [record](crate::values::record) types, all being associative maps.
//!
//! * Like a record, a struct is immutable, fields can be referred to with `struct.field`, and
//!   it uses strings for keys.
//! * Like a dictionary, the struct is untyped, and manipulating structs from Rust is ergonomic.
//!
//! The `struct()` function creates a struct. It accepts keyword arguments, keys become
//! struct field names, and values become field values.
//!
//! ```
//! # starlark::assert::is_true(r#"
//! ip_address = struct(host='localhost', port=80)
//! ip_address.port == 80
//! # "#);
//! ```

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

pub use crate::values::types::structs::alloc::AllocStruct;
pub use crate::values::types::structs::of::StructOf;
pub use crate::values::types::structs::refs::StructRef;


/*
 * 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;
use std::marker::PhantomData;

use starlark_map::small_map::SmallMap;

use crate::typing::Ty;
use crate::values::structs::value::Struct;
use crate::values::structs::StructRef;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::StringValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueOf;

/// Like [`ValueOf`](crate::values::ValueOf), but only validates value types; does not construct
/// or store a map.
#[derive(Debug)]
pub struct StructOf<'v, V: UnpackValue<'v>> {
    value: ValueOf<'v, StructRef<'v>>,
    _marker: PhantomData<V>,
}

impl<'v, V: UnpackValue<'v>> StarlarkTypeRepr for StructOf<'v, V> {
    fn starlark_type_repr() -> Ty {
        Ty::name(Struct::TYPE)
    }
}

impl<'v, V: UnpackValue<'v>> UnpackValue<'v> for StructOf<'v, V> {
    fn expected() -> String {
        format!("struct with fields of type {}", V::expected())
    }

    fn unpack_value(value: Value<'v>) -> Option<StructOf<'v, V>> {
        let value = ValueOf::<StructRef>::unpack_value(value)?;
        for (_k, v) in value.typed.iter() {
            // Validate field types
            V::unpack_value(v)?;
        }
        Some(StructOf {
            value,
            _marker: marker::PhantomData,
        })
    }
}

impl<'v, V: UnpackValue<'v>> StructOf<'v, V> {
    /// Get the actual value this `StructOf` wraps.
    pub fn to_value(&self) -> Value<'v> {
        self.value.value
    }

    /// Get untyped struct reference.
    fn as_struct(&self) -> &StructRef<'v> {
        &self.value.typed
    }

    /// Collect field structs.
    pub fn to_map(&self) -> SmallMap<StringValue<'v>, V> {
        self.as_struct()
            .iter()
            .map(|(k, v)| (k, V::unpack_value(v).expect("validated at construction")))
            .collect()
    }
}


/*
 * 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 crate::typing::Ty;
use crate::values::structs::value::FrozenStruct;
use crate::values::structs::value::Struct;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::StringValue;
use crate::values::UnpackValue;
use crate::values::Value;

/// Reference to a struct allocated on the heap.
///
/// Struct implementation (for example, memory layout) may change,
/// this type provides implementation agnostics API to it.
#[derive(Debug)]
pub struct StructRef<'v>(&'v Struct<'v>);

impl<'v> StructRef<'v> {
    /// Downcast a value to a struct reference.
    pub fn from_value(value: Value<'v>) -> Option<StructRef<'v>> {
        Struct::from_value(value).map(StructRef)
    }

    /// Iterate over struct fields.
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (StringValue<'v>, Value<'v>)> + '_ {
        self.0.iter()
    }
}

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

impl<'v> UnpackValue<'v> for StructRef<'v> {
    fn unpack_value(value: Value<'v>) -> Option<Self> {
        StructRef::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::hash::Hash;
use std::hash::Hasher;

use starlark_map::StarlarkHasher;

/// Utility to compute hash of an unordered collection (e.g. unordered set).
#[derive(Default)]
pub(crate) struct UnorderedHasher {
    state: u64,
    count: u64,
}

impl UnorderedHasher {
    pub(crate) fn new() -> UnorderedHasher {
        UnorderedHasher::default()
    }
}

impl UnorderedHasher {
    pub(crate) fn write_hash(&mut self, value: u64) {
        self.state = self.state.wrapping_add(value);
        self.count = self.count.wrapping_add(1);
    }

    pub(crate) fn _write<H: Hash>(&mut self, value: H) {
        let mut hasher = StarlarkHasher::new();
        value.hash(&mut hasher);
        self.write_hash(hasher.finish());
    }

    pub(crate) fn finish(self) -> u64 {
        let mut hasher = StarlarkHasher::new();
        self.state.hash(&mut hasher);
        self.count.hash(&mut hasher);
        hasher.finish()
    }
}

#[cfg(test)]
mod tests {
    use crate::values::structs::unordered_hasher::UnorderedHasher;

    #[test]
    fn test_unordered_hasher() {
        let mut h = UnorderedHasher::new();
        h.write_hash(10);
        h.write_hash(20);
        let h0 = h.finish();

        let mut h = UnorderedHasher::new();
        h.write_hash(20);
        h.write_hash(10);
        let h1 = h.finish();

        assert_eq!(h0, h1);
    }
}


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

use allocative::Allocative;
use display_container::fmt_keyed_container;
use serde::Serialize;
use starlark_derive::starlark_value;
use starlark_derive::Freeze;
use starlark_derive::StarlarkDocs;
use starlark_derive::Trace;
use starlark_map::small_map::SmallMap;
use starlark_map::Hashed;
use starlark_map::StarlarkHasher;

use crate as starlark;
use crate::any::ProvidesStaticType;
use crate::coerce::coerce;
use crate::coerce::Coerce;
use crate::docs::DocItem;
use crate::docs::DocMember;
use crate::docs::DocObject;
use crate::docs::DocProperty;
use crate::starlark_complex_value;
use crate::values::comparison::compare_small_map;
use crate::values::comparison::equals_small_map;
use crate::values::structs::unordered_hasher::UnorderedHasher;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::StringValueLike;
use crate::values::Value;
use crate::values::ValueError;
use crate::values::ValueLike;

impl<'v, V: ValueLike<'v>> StructGen<'v, V> {
    /// The result of calling `type()` on a struct.
    pub(crate) const TYPE: &'static str = "struct";

    /// Create a new [`Struct`].
    pub(crate) fn new(fields: SmallMap<V::String, V>) -> Self {
        Self { fields }
    }

    /// Iterate over the elements in the struct.
    pub(crate) fn iter<'a>(&'a self) -> impl ExactSizeIterator<Item = (StringValue<'v>, V)> + 'a
    where
        'v: 'a,
    {
        self.fields
            .iter()
            .map(|(name, value)| (name.to_string_value(), *value))
    }
}

starlark_complex_value!(pub(crate) Struct<'v>);

/// The result of calling `struct()`.
#[derive(
    Clone,
    Default,
    Debug,
    Trace,
    Freeze,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(builtin = "extension")]
#[repr(C)]
pub(crate) struct StructGen<'v, V: ValueLike<'v>> {
    /// The fields in a struct.
    pub(crate) fields: SmallMap<V::String, V>,
}

unsafe impl<'v> Coerce<StructGen<'v, Value<'v>>> for StructGen<'static, FrozenValue> {}

impl<'v, V: ValueLike<'v>> Display for StructGen<'v, V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_keyed_container(
            f,
            "struct(",
            ")",
            "=",
            self.iter().map(|(k, v)| (k.as_str(), v)),
        )
    }
}

#[starlark_value(type = Struct::TYPE)]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for StructGen<'v, V>
where
    Self: ProvidesStaticType<'v>,
{
    fn collect_repr_cycle(&self, collector: &mut String) {
        collector.push_str("struct(...)");
    }

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

    fn compare(&self, other: Value<'v>) -> anyhow::Result<Ordering> {
        match Struct::from_value(other) {
            None => ValueError::unsupported_with(self, "cmp()", other),
            Some(other) => compare_small_map(
                coerce(&self.fields),
                &other.fields,
                |k| k.as_str(),
                |x, y| x.compare(*y),
            ),
        }
    }

    fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option<Value<'v>> {
        self.get_attr_hashed(Hashed::new(attribute), heap)
    }

    fn get_attr_hashed(&self, attribute: Hashed<&str>, _heap: &'v Heap) -> Option<Value<'v>> {
        coerce(&self.fields).get_hashed(attribute).copied()
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        // Must use unordered hash because equality is unordered,
        // and `a = b  =>  hash(a) = hash(b)`.
        let mut unordered_hasher = UnorderedHasher::new();

        for (k, v) in self.fields.iter_hashed() {
            // Should hash key and value together, so two structs
            // `a=1 b=2` and `a=2 b=1` would produce different hashes.
            let mut entry_hasher = StarlarkHasher::new();
            k.hash().hash(&mut entry_hasher);
            v.write_hash(&mut entry_hasher)?;
            unordered_hasher.write_hash(entry_hasher.finish());
        }

        hasher.write_u64(unordered_hasher.finish());

        Ok(())
    }

    fn dir_attr(&self) -> Vec<String> {
        self.fields.keys().map(|x| x.as_str().to_owned()).collect()
    }

    fn documentation(&self) -> Option<DocItem> {
        let members = self
            .fields
            .iter()
            .map(|(k, v)| {
                let name = k.as_str().to_owned();
                match v.to_value().documentation() {
                    Some(DocItem::Function(f)) => (name, DocMember::Function(f)),
                    _ => (
                        name,
                        DocMember::Property(DocProperty {
                            docs: None,
                            typ: None,
                        }),
                    ),
                }
            })
            .collect();
        Some(DocItem::Object(DocObject {
            docs: None,
            members,
        }))
    }
}

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

#[cfg(test)]
mod tests {

    use crate::assert;

    #[test]
    fn test_repr() {
        assert::eq("repr(struct(a=1, b=[]))", "'struct(a=1, b=[])'");
        assert::eq("str(struct(a=1, b=[]))", "'struct(a=1, b=[])'");
    }

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

    #[test]
    fn test_to_json_cycle() {
        assert::fail(
            "l = []; s = struct(f=l); l.append(s); json.encode(s)",
            "Cycle detected when serializing value of type `struct` to JSON",
        );
    }

    #[test]
    fn test_to_json() {
        assert::all_true(
            r#"
json.encode(struct(key = None)) == '{"key":null}'
json.encode(struct(key = True)) == '{"key":true}'
json.encode(struct(key = False)) == '{"key":false}'
json.encode(struct(key = 42)) == '{"key":42}'
json.encode(struct(key = 'value')) == '{"key":"value"}'
json.encode(struct(key = 'value"')) == '{"key":"value\\\""}'
json.encode(struct(key = 'value\\')) == '{"key":"value\\\\"}'
json.encode(struct(key = 'value/')) == '{"key":"value/"}'
json.encode(struct(key = 'value\u0008')) == '{"key":"value\\b"}'
json.encode(struct(key = 'value\u000C')) == '{"key":"value\\f"}'
json.encode(struct(key = 'value\n')) == '{"key":"value\\n"}'
json.encode(struct(key = 'value\r')) == '{"key":"value\\r"}'
json.encode(struct(key = 'value\t')) == '{"key":"value\\t"}'
json.encode(struct(foo = 42, bar = "some")) == '{"foo":42,"bar":"some"}'
json.encode(struct(foo = struct(bar = "some"))) == '{"foo":{"bar":"some"}}'
json.encode(struct(foo = ["bar/", "some"])) == '{"foo":["bar/","some"]}'
json.encode(struct(foo = [struct(bar = "some")])) == '{"foo":[{"bar":"some"}]}'
"#,
        );
    }

    #[test]
    fn test_comparison_bug() {
        // TODO(nga): this should be false, because `a < b`,
        //   and comparisons are usually lexicographic.
        // TODO(nga): Also, since structs are ordered, but not sorted,
        //   comparisons on structs should be forbidden
        //   (because it is too expensive to sort keys on each comparison).
        assert::is_true("struct(b=1) < struct(a=1, x=1)")
    }
}


/*
 * 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::tuple::TupleRef;
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 tuple.
///
/// Note, for tuples of fixed sizes there are implementations for `(A,)` or `(A, B)`.
///
/// # Example
///
/// ```
/// # use starlark::values::{FrozenHeap, Heap};
/// # use starlark::values::tuple::AllocTuple;
/// # fn alloc(heap: &Heap, frozen_heap: &FrozenHeap) {
/// let l = heap.alloc(AllocTuple([1, 2, 3]));
/// let ls = frozen_heap.alloc(AllocTuple([1, 2, 3]));
/// # }
/// ```
pub struct AllocTuple<T>(pub T);

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

impl<'v, T> StarlarkTypeRepr for AllocTuple<T>
where
    T: IntoIterator,
    T::Item: AllocValue<'v>,
{
    fn starlark_type_repr() -> Ty {
        Ty::name(TupleRef::TYPE)
    }
}

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

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

#[cfg(test)]
mod tests {
    use crate::values::tuple::alloc::AllocTuple;
    use crate::values::tuple::FrozenTupleRef;
    use crate::values::tuple::TupleRef;
    use crate::values::FrozenHeap;
    use crate::values::Heap;

    #[test]
    fn test_alloc_tuple() {
        let heap = Heap::new();

        let a = heap.alloc(AllocTuple([""; 0]));
        let b = heap.alloc(AllocTuple([1, 2, 3].iter().copied().filter(|_| false)));
        assert_eq!(0, TupleRef::from_value(a).unwrap().content().len());
        assert!(a.ptr_eq(b));

        // Fixed length iterator.
        let c = heap.alloc(AllocTuple([1, 2]));
        assert_eq!(2, TupleRef::from_value(c).unwrap().content().len());

        // Iterator of unknown length.
        let d = heap.alloc(AllocTuple([1, 2, 3].iter().copied().filter(|c| *c > 1)));
        assert_eq!(2, TupleRef::from_value(d).unwrap().content().len());
    }

    #[test]
    fn test_alloc_frozen_tuple() {
        let heap = FrozenHeap::new();

        let a = heap.alloc(AllocTuple([""; 0]));
        let b = heap.alloc(AllocTuple([1, 2, 3].iter().copied().filter(|_| false)));
        assert_eq!(0, TupleRef::from_frozen_value(a).unwrap().content().len());
        assert!(a.to_value().ptr_eq(b.to_value()));

        // Fixed length iterator.
        let c = heap.alloc(AllocTuple([1, 2]));
        assert_eq!(
            2,
            FrozenTupleRef::from_frozen_value(c)
                .unwrap()
                .content()
                .len()
        );

        // Iterator of unknown length.
        let d = heap.alloc(AllocTuple([1, 2, 3].iter().copied().filter(|c| *c > 1)));
        assert_eq!(
            2,
            FrozenTupleRef::from_frozen_value(d)
                .unwrap()
                .content()
                .len()
        );
    }
}


/*
 * 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, an immutable sequence of values.

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

pub use crate::values::types::tuple::alloc::AllocTuple;
pub use crate::values::types::tuple::refs::FrozenTupleRef;
pub use crate::values::types::tuple::refs::TupleRef;