/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt::Result as FmtResult;
use std::fmt::Write;

use common::DirectiveName;
use common::Named;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::Argument;
use graphql_ir::Condition;
use graphql_ir::ConditionValue;
use graphql_ir::ConstantArgument;
use graphql_ir::ConstantValue;
use graphql_ir::Directive;
use graphql_ir::ExecutableDefinition;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Value;
use graphql_ir::VariableDefinition;
use graphql_ir::VariableName;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;
use schema::TypeReference;

pub fn print_ir(schema: &SDLSchema, definitions: &[ExecutableDefinition]) -> Vec<String> {
    definitions
        .iter()
        .map(|def| print_definition(schema, def))
        .collect()
}

pub fn print_definition(schema: &SDLSchema, definition: &ExecutableDefinition) -> String {
    let mut result = String::new();
    write_definition(schema, definition, &mut result).unwrap();
    result
}

pub fn print_operation(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    options: PrinterOptions,
) -> String {
    let mut result = String::new();
    write_operation(schema, operation, options, &mut result).unwrap();
    result
}

pub fn print_fragment(
    schema: &SDLSchema,
    fragment: &FragmentDefinition,
    options: PrinterOptions,
) -> String {
    let mut result = String::new();
    write_fragment(schema, fragment, options, &mut result).unwrap();
    result
}

pub fn print_selections(schema: &SDLSchema, selections: &[Selection]) -> String {
    let mut result = String::new();
    write_selections(schema, selections, &mut result).unwrap();
    result
}

pub fn print_selection(schema: &SDLSchema, selection: &Selection) -> String {
    let mut result = String::new();
    write_selection(schema, selection, &mut result).unwrap();
    result
}

pub fn print_arguments(
    schema: &SDLSchema,
    arguments: &[Argument],
    options: PrinterOptions,
) -> String {
    let mut result = String::new();
    write_arguments(schema, arguments, options, &mut result).unwrap();
    result
}

pub fn print_directives(
    schema: &SDLSchema,
    directives: &[Directive],
    options: PrinterOptions,
) -> String {
    let mut result = String::new();
    write_directives(schema, directives, options, &mut result).unwrap();
    result
}

pub fn print_value(schema: &SDLSchema, value: &Value, options: PrinterOptions) -> String {
    let mut result = String::new();
    write_value(schema, value, options, &mut result).unwrap();
    result
}

pub fn write_definition(
    schema: &SDLSchema,
    definition: &ExecutableDefinition,
    mut result: &mut impl Write,
) -> FmtResult {
    let printer = Printer::new(schema, &mut result, PrinterOptions::default());
    printer.print_definition(definition)
}

pub fn write_operation(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    options: PrinterOptions,
    mut result: &mut impl Write,
) -> FmtResult {
    let printer = Printer::new(schema, &mut result, options);
    printer.print_operation(operation)
}

pub fn write_fragment(
    schema: &SDLSchema,
    fragment: &FragmentDefinition,
    options: PrinterOptions,
    mut result: &mut impl Write,
) -> FmtResult {
    let printer = Printer::new(schema, &mut result, options);
    printer.print_fragment(fragment)
}

pub fn write_selections(
    schema: &SDLSchema,
    selections: &[Selection],
    mut result: &mut impl Write,
) -> FmtResult {
    let mut printer = Printer::new(schema, &mut result, PrinterOptions::default());
    printer.print_selections(selections, "unknown".intern())
}

pub fn write_selection(
    schema: &SDLSchema,
    selection: &Selection,
    mut result: &mut impl Write,
) -> FmtResult {
    let mut printer = Printer::new(schema, &mut result, PrinterOptions::default());
    printer.print_selection(selection, None)
}

pub fn write_arguments(
    schema: &SDLSchema,
    arguments: &[Argument],
    options: PrinterOptions,
    mut result: &mut impl Write,
) -> FmtResult {
    let mut printer = Printer::new(schema, &mut result, options);
    printer.print_arguments(arguments)
}

pub fn write_directives(
    schema: &SDLSchema,
    directives: &[Directive],
    options: PrinterOptions,
    mut result: &mut impl Write,
) -> FmtResult {
    let mut printer = Printer::new(schema, &mut result, options);
    printer.print_directives(directives, None, None)
}

pub fn write_value(
    schema: &SDLSchema,
    value: &Value,
    options: PrinterOptions,
    mut result: &mut impl Write,
) -> FmtResult {
    let mut printer = Printer::new(schema, &mut result, options);
    printer.print_value(value)
}

#[derive(Clone, Copy, Default)]
pub struct PrinterOptions {
    pub compact: bool,
    pub sort_keys: bool,
    pub json_format: bool,
    /// Print `data` from Directive nodes
    pub debug_directive_data: bool,
}

struct Printer<'schema, 'writer, W: Write> {
    schema: &'schema SDLSchema,
    writer: &'writer mut W,
    options: PrinterOptions,
    indentation: usize,
}

impl<'schema, 'writer, W: Write> Printer<'schema, 'writer, W> {
    fn new(schema: &'schema SDLSchema, writer: &'writer mut W, options: PrinterOptions) -> Self {
        Self {
            schema,
            writer,
            options,
            indentation: 0,
        }
    }

    fn print_definition(self, definition: &ExecutableDefinition) -> FmtResult {
        match definition {
            ExecutableDefinition::Operation(operation) => self.print_operation(operation),
            ExecutableDefinition::Fragment(fragment) => self.print_fragment(fragment),
        }
    }

    fn print_operation(mut self, operation: &OperationDefinition) -> FmtResult {
        write!(self.writer, "{} {}", operation.kind, operation.name.item)?;
        self.print_variable_definitions(&operation.variable_definitions)?;
        self.print_directives(&operation.directives, None, None)?;
        self.print_selections(&operation.selections, operation.name.item.0)
    }

    fn print_fragment(mut self, fragment: &FragmentDefinition) -> FmtResult {
        let fragment_name = fragment.name.item;
        let type_condition_name = self.schema.get_type_name(fragment.type_condition);
        write!(self.writer, "fragment {}", fragment_name)?;
        if fragment
            .directives
            .named(DirectiveName("argumentDefinitions".intern()))
            .is_none()
        {
            self.print_variable_definitions(&fragment.variable_definitions)?;
        }
        write!(self.writer, " on {}", type_condition_name)?;

        self.print_directives(
            &fragment.directives,
            None,
            Some(&fragment.variable_definitions),
        )?;
        self.print_selections(&fragment.selections, fragment_name.0)
    }

    fn print_selections(&mut self, selections: &[Selection], name: StringKey) -> FmtResult {
        let len = selections.len();
        if len > 0 {
            self.print_optional_space()?;
            write!(self.writer, "{{")?;
            self.indentation += 1;
            self.print_new_line(true)?;

            for (i, selection) in selections.iter().enumerate() {
                self.print_selection(selection, None)?;
                if i != len - 1 {
                    self.print_item_separator()?;
                }
            }

            self.indentation -= 1;
            self.print_new_line(true)?;
            write!(self.writer, "}}")?;
        } else {
            panic!(
                "Cannot print empty selections for {}. Please, check transforms that may produce invalid selections.",
                name
            );
        }
        Ok(())
    }

    fn print_selection(
        &mut self,
        selection: &Selection,
        conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        match selection {
            Selection::ScalarField(field) => self.print_scalar_field(field, conditions),
            Selection::LinkedField(field) => self.print_linked_field(field, conditions),
            Selection::FragmentSpread(field) => self.print_fragment_spread(field, conditions),
            Selection::InlineFragment(field) => self.print_inline_fragment(field, conditions),
            Selection::Condition(field) => self.print_condition(field, conditions),
        }
    }

    fn print_scalar_field(
        &mut self,
        field: &ScalarField,
        conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        let schema_field = self.schema.field(field.definition.item);
        self.print_alias_and_name(&field.alias, schema_field.name.item)?;
        self.print_arguments(&field.arguments)?;
        self.print_directives(&field.directives, conditions, None)
    }

    fn print_linked_field(
        &mut self,
        field: &LinkedField,
        conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        let schema_field = self.schema.field(field.definition.item);
        self.print_alias_and_name(&field.alias, schema_field.name.item)?;
        self.print_arguments(&field.arguments)?;
        self.print_directives(&field.directives, conditions, None)?;
        self.print_selections(&field.selections, schema_field.name.item)?;
        Ok(())
    }

    fn print_fragment_spread(
        &mut self,
        field: &FragmentSpread,
        conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        let fragment_name = field.fragment.item;
        write!(self.writer, "...{}", fragment_name)?;
        self.print_directives(&field.directives, conditions, None)?;
        if !field.arguments.is_empty() {
            self.print_optional_space()?;
            write!(self.writer, "@arguments")?;
            self.print_arguments(&field.arguments)
        } else {
            Ok(())
        }
    }

    fn print_inline_fragment(
        &mut self,
        field: &InlineFragment,
        conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        write!(self.writer, "...")?;
        if let Some(type_condition) = field.type_condition {
            self.print_optional_space()?;
            write!(
                self.writer,
                "on {}",
                self.schema.get_type_name(type_condition).lookup(),
            )?;
        };
        self.print_directives(&field.directives, conditions, None)?;

        let name = if let Some(type_condition) = field.type_condition {
            format!(
                "... on {}",
                self.schema.get_type_name(type_condition).lookup()
            )
            .intern()
        } else {
            "...".intern()
        };
        self.print_selections(&field.selections, name)
    }

    fn print_condition(
        &mut self,
        condition: &Condition,
        accum_conditions: Option<Vec<&Condition>>,
    ) -> FmtResult {
        let mut accum_conditions: Vec<&Condition> = accum_conditions.unwrap_or_default();
        // GraphQL spec doesn't allow repeated include or skip. See
        // https://spec.graphql.org/October2021/#sec-Directives-Are-Unique-Per-Location
        // To work around it, we extract repeated conditions into an inline fragment.
        let mut has_repeated_condition = false;
        for cond in &accum_conditions {
            if cond.passing_value == condition.passing_value {
                has_repeated_condition = true;
            }
        }
        if has_repeated_condition {
            write!(self.writer, "...")?;
            self.print_directives(&[], Some(accum_conditions), None)?;
            write!(self.writer, " {{")?;
            self.indentation += 1;
            self.print_new_line(false)?;
            accum_conditions = vec![condition];
        } else {
            accum_conditions.push(condition);
        }

        let mut is_first_selection = true;
        for selection in condition.selections.iter() {
            {
                if is_first_selection {
                    is_first_selection = false;
                } else {
                    self.print_new_line(false)?;
                }
                self.print_selection(
                    selection,
                    Some(accum_conditions.iter().rev().cloned().collect()),
                )?;
            }
        }
        if has_repeated_condition {
            self.indentation -= 1;
            self.print_new_line(false)?;
            write!(self.writer, "}}")?;
        }
        Ok(())
    }

    fn print_directives(
        &mut self,
        directives: &[Directive],
        conditions: Option<Vec<&Condition>>,
        fragment_argument_definitions: Option<&[VariableDefinition]>,
    ) -> FmtResult {
        if let Some(conditions) = conditions {
            self.print_condition_directives(conditions)?;
        }
        for directive in directives {
            if directive.name.item.0.lookup() == "argumentDefinitions" {
                self.print_argument_definitions(fragment_argument_definitions.unwrap())?;
            } else {
                self.print_directive(directive)?;
            }
        }
        Ok(())
    }

    fn print_directive(&mut self, directive: &Directive) -> FmtResult {
        self.print_optional_space()?;
        write!(self.writer, "@{}", directive.name.item)?;
        self.print_arguments(&directive.arguments)?;

        if self.options.debug_directive_data {
            if let Some(data) = &directive.data {
                for debug_line in format!("{:#?}", data).lines() {
                    self.print_new_line(false)?;
                    write!(self.writer, "# {}", debug_line)?;
                }
                self.print_new_line(false)?;
            }
        }

        Ok(())
    }

    fn print_condition_directives(&mut self, conditions: Vec<&Condition>) -> FmtResult {
        for condition in conditions {
            self.print_optional_space()?;
            write!(
                self.writer,
                "@{}",
                if condition.passing_value {
                    "include"
                } else {
                    "skip"
                }
            )?;
            write!(self.writer, "(if")?;
            self.print_colon_separator()?;
            match &condition.value {
                ConditionValue::Constant(value) => write!(self.writer, "{}", value),
                ConditionValue::Variable(variable) => self.print_variable(variable.name.item),
            }?;
            write!(self.writer, ")")?;
        }
        Ok(())
    }

    fn print_variable_definitions(
        &mut self,
        variable_definitions: &[VariableDefinition],
    ) -> FmtResult {
        if !variable_definitions.is_empty() {
            write!(self.writer, "(")?;
            self.indentation += 1;
            for (i, var_def) in variable_definitions.iter().enumerate() {
                if i == 0 {
                    self.print_new_line(true)?;
                } else {
                    self.print_item_separator()?;
                }
                self.print_variable_definition(var_def)?;
            }
            self.indentation -= 1;
            self.print_new_line(true)?;
            write!(self.writer, ")")?;
        }
        Ok(())
    }

    fn print_argument_definitions(
        &mut self,
        argument_definitions: &[VariableDefinition],
    ) -> FmtResult {
        if !argument_definitions.is_empty() {
            self.print_optional_space()?;
            write!(self.writer, "@argumentDefinitions(")?;
            self.indentation += 1;
            for (i, arg_def) in argument_definitions.iter().enumerate() {
                if i == 0 {
                    self.print_new_line(true)?;
                } else {
                    self.print_item_separator()?;
                }
                write!(self.writer, "{}", arg_def.name.item)?;
                self.print_colon_separator()?;
                write!(self.writer, "{{type")?;
                self.print_colon_separator()?;
                write!(self.writer, "\"")?;
                self.print_type(&arg_def.type_)?;
                write!(self.writer, "\"")?;
                if let Some(default_value) = &arg_def.default_value {
                    self.print_comma_separator()?;
                    write!(self.writer, "defaultValue")?;
                    self.print_colon_separator()?;
                    self.print_constant_value(&default_value.item)?;
                }
                write!(self.writer, "}}")?;
            }
            self.indentation -= 1;
            self.print_new_line(true)?;
            write!(self.writer, ")")?;
        }
        Ok(())
    }

    fn print_arguments(&mut self, arguments: &[Argument]) -> FmtResult {
        if arguments.is_empty() {
            Ok(())
        } else {
            let sorted_arguments = if self.options.sort_keys {
                let mut sorted_arguments = arguments.to_vec();
                sorted_arguments.sort_by_key(|arg| arg.name());
                Some(sorted_arguments)
            } else {
                None
            };
            let maybe_sorted_arguments = sorted_arguments
                .as_ref()
                .map_or(arguments, |v| v.as_slice());

            write!(self.writer, "(")?;
            for (i, argument) in maybe_sorted_arguments.iter().enumerate() {
                self.print_argument(argument)?;
                if i != arguments.len() - 1 {
                    self.print_comma_separator()?;
                }
            }
            write!(self.writer, ")")
        }
    }

    fn print_value(&mut self, val: &Value) -> FmtResult {
        match val {
            Value::Constant(constant_val) => self.print_constant_value(constant_val),
            Value::Variable(variable_val) => self.print_variable(variable_val.name.item),
            Value::Object(object) => {
                write!(self.writer, "{{")?;
                let mut first = true;
                let sorted_object = if self.options.sort_keys {
                    let mut maybe_sorted_object = object.clone();
                    if self.options.sort_keys {
                        maybe_sorted_object.sort_by_key(|arg| arg.name());
                    }
                    Some(maybe_sorted_object)
                } else {
                    None
                };
                let maybe_sorted_object = sorted_object.as_ref().unwrap_or(object);
                for arg in maybe_sorted_object
                    .iter()
                    .filter(|arg| !matches!(arg.value.item, Value::Constant(ConstantValue::Null())))
                {
                    if first {
                        first = false;
                    } else {
                        self.print_comma_separator()?;
                    }
                    if self.options.json_format {
                        write!(self.writer, "\"{}\":", arg.name.item)?;
                    } else {
                        write!(self.writer, "{}:", arg.name.item)?;
                    }
                    self.print_optional_space()?;
                    self.print_value(&arg.value.item)?;
                }
                write!(self.writer, "}}")
            }
            Value::List(list) => {
                write!(self.writer, "[")?;
                let mut first = true;
                for value in list
                    .iter()
                    .filter(|value| !matches!(value, Value::Constant(ConstantValue::Null())))
                {
                    if first {
                        first = false;
                    } else {
                        self.print_comma_separator()?;
                    }
                    self.print_value(value)?;
                }
                write!(self.writer, "]")
            }
        }
    }

    fn print_constant_value(&mut self, constant_val: &ConstantValue) -> FmtResult {
        match &constant_val {
            ConstantValue::String(val) => write!(self.writer, "\"{}\"", val),
            ConstantValue::Float(val) => write!(self.writer, "{}", val),
            ConstantValue::Int(val) => write!(self.writer, "{}", val),
            ConstantValue::Boolean(val) => write!(self.writer, "{}", val),
            ConstantValue::Null() => write!(self.writer, "null"),
            ConstantValue::Enum(val) => {
                if self.options.json_format {
                    write!(self.writer, "\"{}\"", val)
                } else {
                    write!(self.writer, "{}", val)
                }
            }
            ConstantValue::Object(object) => {
                write!(self.writer, "{{")?;
                let mut first = true;
                let sorted_object = if self.options.sort_keys {
                    let mut maybe_sorted_object = object.clone();
                    if self.options.sort_keys {
                        maybe_sorted_object.sort_by_key(|arg| arg.name());
                    }
                    Some(maybe_sorted_object)
                } else {
                    None
                };
                let maybe_sorted_object = sorted_object.as_ref().unwrap_or(object);
                for arg in maybe_sorted_object {
                    if first {
                        first = false;
                    } else {
                        self.print_comma_separator()?;
                    }
                    self.print_constant_object_field(arg)?;
                }
                write!(self.writer, "}}")
            }
            ConstantValue::List(list) => {
                write!(self.writer, "[")?;
                let mut first = true;
                for value in list {
                    if first {
                        first = false;
                    } else {
                        self.print_comma_separator()?;
                    }
                    self.print_constant_value(value)?;
                }
                write!(self.writer, "]")
            }
        }
    }

    fn print_alias_and_name(
        &mut self,
        alias: &Option<WithLocation<StringKey>>,
        name: StringKey,
    ) -> FmtResult {
        if let Some(alias) = alias {
            if alias.item != name {
                write!(self.writer, "{}", alias.item)?;
                self.print_colon_separator()?;
            }
        }
        write!(self.writer, "{}", name)
    }

    fn print_argument(&mut self, argument: &Argument) -> FmtResult {
        write!(self.writer, "{}", argument.name.item)?;
        self.print_colon_separator()?;
        self.print_value(&argument.value.item)
    }

    fn print_variable_definition(&mut self, var_def: &VariableDefinition) -> FmtResult {
        self.print_variable(var_def.name.item)?;
        self.print_colon_separator()?;
        self.print_type(&var_def.type_)?;

        if let Some(default_value) = &var_def.default_value {
            self.print_default_value(&default_value.item)?;
        }

        self.print_directives(&var_def.directives, None, None)
    }

    fn print_constant_object_field(&mut self, arg: &ConstantArgument) -> FmtResult {
        if self.options.json_format {
            write!(self.writer, "\"{}\"", arg.name.item)?;
        } else {
            write!(self.writer, "{}", arg.name.item)?;
        }
        self.print_colon_separator()?;
        self.print_constant_value(&arg.value.item)
    }

    fn print_variable(&mut self, variable: VariableName) -> FmtResult {
        write!(self.writer, "${}", variable)
    }

    fn print_default_value(&mut self, value: &ConstantValue) -> FmtResult {
        self.print_optional_space()?;
        write!(self.writer, "=")?;
        self.print_optional_space()?;
        self.print_constant_value(value)
    }

    fn print_type(&mut self, type_: &TypeReference<Type>) -> FmtResult {
        write!(self.writer, "{}", self.schema.get_type_string(type_))
    }

    fn print_new_line(&mut self, can_skip_from_this_location: bool) -> FmtResult {
        if self.options.compact && can_skip_from_this_location {
            return Ok(());
        }
        writeln!(self.writer)?;
        for _ in 0..self.indentation {
            write!(self.writer, "  ")?;
        }
        Ok(())
    }

    fn print_optional_space(&mut self) -> FmtResult {
        if self.options.compact {
            return Ok(());
        }
        write!(self.writer, " ")
    }

    fn print_comma_separator(&mut self) -> FmtResult {
        write!(self.writer, ",")?;
        self.print_optional_space()
    }

    fn print_colon_separator(&mut self) -> FmtResult {
        write!(self.writer, ":")?;
        self.print_optional_space()
    }

    fn print_item_separator(&mut self) -> FmtResult {
        if self.options.compact {
            write!(self.writer, ",")
        } else {
            self.print_new_line(false)
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<6d1db34f88e5cb87dff2002fd51965c4>>
 */

mod compact;

use compact::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn basic_directives() {
    let input = include_str!("compact/fixtures/basic_directives.graphql");
    let expected = include_str!("compact/fixtures/basic_directives.expected");
    test_fixture(transform_fixture, "basic_directives.graphql", "compact/fixtures/basic_directives.expected", input, expected);
}

#[test]
fn basic_query() {
    let input = include_str!("compact/fixtures/basic_query.graphql");
    let expected = include_str!("compact/fixtures/basic_query.expected");
    test_fixture(transform_fixture, "basic_query.graphql", "compact/fixtures/basic_query.expected", input, expected);
}

#[test]
fn basic_var_defs() {
    let input = include_str!("compact/fixtures/basic_var_defs.graphql");
    let expected = include_str!("compact/fixtures/basic_var_defs.expected");
    test_fixture(transform_fixture, "basic_var_defs.graphql", "compact/fixtures/basic_var_defs.expected", input, expected);
}

#[test]
fn compact_test() {
    let input = include_str!("compact/fixtures/compact_test.graphql");
    let expected = include_str!("compact/fixtures/compact_test.expected");
    test_fixture(transform_fixture, "compact_test.graphql", "compact/fixtures/compact_test.expected", input, expected);
}

#[test]
fn empty_args() {
    let input = include_str!("compact/fixtures/empty_args.graphql");
    let expected = include_str!("compact/fixtures/empty_args.expected");
    test_fixture(transform_fixture, "empty_args.graphql", "compact/fixtures/empty_args.expected", input, expected);
}

#[test]
fn kitchen_sink() {
    let input = include_str!("compact/fixtures/kitchen-sink.graphql");
    let expected = include_str!("compact/fixtures/kitchen-sink.expected");
    test_fixture(transform_fixture, "kitchen-sink.graphql", "compact/fixtures/kitchen-sink.expected", input, expected);
}

#[test]
fn single_value_array_of_objects() {
    let input = include_str!("compact/fixtures/single-value-array-of-objects.graphql");
    let expected = include_str!("compact/fixtures/single-value-array-of-objects.expected");
    test_fixture(transform_fixture, "single-value-array-of-objects.graphql", "compact/fixtures/single-value-array-of-objects.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<7cba6c2fe0d54fa5abd9ec9b9a70bb05>>
 */

mod operation_printer;

use operation_printer::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn field_arguments() {
    let input = include_str!("operation_printer/fixtures/field-arguments.graphql");
    let expected = include_str!("operation_printer/fixtures/field-arguments.expected");
    test_fixture(transform_fixture, "field-arguments.graphql", "operation_printer/fixtures/field-arguments.expected", input, expected);
}

#[test]
fn multiple_queries_with_same_fragment() {
    let input = include_str!("operation_printer/fixtures/multiple-queries-with-same-fragment.graphql");
    let expected = include_str!("operation_printer/fixtures/multiple-queries-with-same-fragment.expected");
    test_fixture(transform_fixture, "multiple-queries-with-same-fragment.graphql", "operation_printer/fixtures/multiple-queries-with-same-fragment.expected", input, expected);
}

#[test]
fn query_variables() {
    let input = include_str!("operation_printer/fixtures/query-variables.graphql");
    let expected = include_str!("operation_printer/fixtures/query-variables.expected");
    test_fixture(transform_fixture, "query-variables.graphql", "operation_printer/fixtures/query-variables.expected", input, expected);
}

#[test]
fn query_with_fragment_spreads() {
    let input = include_str!("operation_printer/fixtures/query-with-fragment-spreads.graphql");
    let expected = include_str!("operation_printer/fixtures/query-with-fragment-spreads.expected");
    test_fixture(transform_fixture, "query-with-fragment-spreads.graphql", "operation_printer/fixtures/query-with-fragment-spreads.expected", input, expected);
}

#[test]
fn query_with_nested_fragment_srpeads() {
    let input = include_str!("operation_printer/fixtures/query-with-nested-fragment-srpeads.graphql");
    let expected = include_str!("operation_printer/fixtures/query-with-nested-fragment-srpeads.expected");
    test_fixture(transform_fixture, "query-with-nested-fragment-srpeads.graphql", "operation_printer/fixtures/query-with-nested-fragment-srpeads.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<ac613948ae97fa3d5385399cf284e3fe>>
 */

mod print_ast;

use print_ast::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn basic_arg_defs() {
    let input = include_str!("print_ast/fixtures/basic_arg_defs.graphql");
    let expected = include_str!("print_ast/fixtures/basic_arg_defs.expected");
    test_fixture(transform_fixture, "basic_arg_defs.graphql", "print_ast/fixtures/basic_arg_defs.expected", input, expected);
}

#[test]
fn basic_arg_defs_type() {
    let input = include_str!("print_ast/fixtures/basic_arg_defs_type.graphql");
    let expected = include_str!("print_ast/fixtures/basic_arg_defs_type.expected");
    test_fixture(transform_fixture, "basic_arg_defs_type.graphql", "print_ast/fixtures/basic_arg_defs_type.expected", input, expected);
}

#[test]
fn basic_directives() {
    let input = include_str!("print_ast/fixtures/basic_directives.graphql");
    let expected = include_str!("print_ast/fixtures/basic_directives.expected");
    test_fixture(transform_fixture, "basic_directives.graphql", "print_ast/fixtures/basic_directives.expected", input, expected);
}

#[test]
fn basic_fragment() {
    let input = include_str!("print_ast/fixtures/basic_fragment.graphql");
    let expected = include_str!("print_ast/fixtures/basic_fragment.expected");
    test_fixture(transform_fixture, "basic_fragment.graphql", "print_ast/fixtures/basic_fragment.expected", input, expected);
}

#[test]
fn basic_inline_fragments() {
    let input = include_str!("print_ast/fixtures/basic_inline_fragments.graphql");
    let expected = include_str!("print_ast/fixtures/basic_inline_fragments.expected");
    test_fixture(transform_fixture, "basic_inline_fragments.graphql", "print_ast/fixtures/basic_inline_fragments.expected", input, expected);
}

#[test]
fn basic_list_object_values() {
    let input = include_str!("print_ast/fixtures/basic_list_object_values.graphql");
    let expected = include_str!("print_ast/fixtures/basic_list_object_values.expected");
    test_fixture(transform_fixture, "basic_list_object_values.graphql", "print_ast/fixtures/basic_list_object_values.expected", input, expected);
}

#[test]
fn basic_query() {
    let input = include_str!("print_ast/fixtures/basic_query.graphql");
    let expected = include_str!("print_ast/fixtures/basic_query.expected");
    test_fixture(transform_fixture, "basic_query.graphql", "print_ast/fixtures/basic_query.expected", input, expected);
}

#[test]
fn basic_query_with_float() {
    let input = include_str!("print_ast/fixtures/basic_query_with_float.graphql");
    let expected = include_str!("print_ast/fixtures/basic_query_with_float.expected");
    test_fixture(transform_fixture, "basic_query_with_float.graphql", "print_ast/fixtures/basic_query_with_float.expected", input, expected);
}

#[test]
fn basic_var_defs() {
    let input = include_str!("print_ast/fixtures/basic_var_defs.graphql");
    let expected = include_str!("print_ast/fixtures/basic_var_defs.expected");
    test_fixture(transform_fixture, "basic_var_defs.graphql", "print_ast/fixtures/basic_var_defs.expected", input, expected);
}

#[test]
fn basic_var_defs_with_directives() {
    let input = include_str!("print_ast/fixtures/basic_var_defs_with_directives.graphql");
    let expected = include_str!("print_ast/fixtures/basic_var_defs_with_directives.expected");
    test_fixture(transform_fixture, "basic_var_defs_with_directives.graphql", "print_ast/fixtures/basic_var_defs_with_directives.expected", input, expected);
}

#[test]
fn conditions() {
    let input = include_str!("print_ast/fixtures/conditions.graphql");
    let expected = include_str!("print_ast/fixtures/conditions.expected");
    test_fixture(transform_fixture, "conditions.graphql", "print_ast/fixtures/conditions.expected", input, expected);
}

#[test]
fn empty_args() {
    let input = include_str!("print_ast/fixtures/empty_args.graphql");
    let expected = include_str!("print_ast/fixtures/empty_args.expected");
    test_fixture(transform_fixture, "empty_args.graphql", "print_ast/fixtures/empty_args.expected", input, expected);
}

#[test]
fn kitchen_sink() {
    let input = include_str!("print_ast/fixtures/kitchen-sink.graphql");
    let expected = include_str!("print_ast/fixtures/kitchen-sink.expected");
    test_fixture(transform_fixture, "kitchen-sink.graphql", "print_ast/fixtures/kitchen-sink.expected", input, expected);
}

#[test]
fn lowercase_enum_fragment_arg() {
    let input = include_str!("print_ast/fixtures/lowercase-enum-fragment-arg.graphql");
    let expected = include_str!("print_ast/fixtures/lowercase-enum-fragment-arg.expected");
    test_fixture(transform_fixture, "lowercase-enum-fragment-arg.graphql", "print_ast/fixtures/lowercase-enum-fragment-arg.expected", input, expected);
}

#[test]
fn nested_conditions() {
    let input = include_str!("print_ast/fixtures/nested_conditions.graphql");
    let expected = include_str!("print_ast/fixtures/nested_conditions.expected");
    test_fixture(transform_fixture, "nested_conditions.graphql", "print_ast/fixtures/nested_conditions.expected", input, expected);
}

#[test]
fn single_value_array_of_objects() {
    let input = include_str!("print_ast/fixtures/single-value-array-of-objects.graphql");
    let expected = include_str!("print_ast/fixtures/single-value-array-of-objects.expected");
    test_fixture(transform_fixture, "single-value-array-of-objects.graphql", "print_ast/fixtures/single-value-array-of-objects.expected", input, expected);
}

#[test]
fn string_enum_arg_invalid() {
    let input = include_str!("print_ast/fixtures/string-enum-arg.invalid.graphql");
    let expected = include_str!("print_ast/fixtures/string-enum-arg.invalid.expected");
    test_fixture(transform_fixture, "string-enum-arg.invalid.graphql", "print_ast/fixtures/string-enum-arg.invalid.expected", input, expected);
}

#[test]
fn string_enum_fragment_arg() {
    let input = include_str!("print_ast/fixtures/string-enum-fragment-arg.graphql");
    let expected = include_str!("print_ast/fixtures/string-enum-fragment-arg.expected");
    test_fixture(transform_fixture, "string-enum-fragment-arg.graphql", "print_ast/fixtures/string-enum-fragment-arg.expected", input, expected);
}

#[test]
fn string_enum_fragment_arg_with_complex_input() {
    let input = include_str!("print_ast/fixtures/string-enum-fragment-arg-with-complex-input.graphql");
    let expected = include_str!("print_ast/fixtures/string-enum-fragment-arg-with-complex-input.expected");
    test_fixture(transform_fixture, "string-enum-fragment-arg-with-complex-input.graphql", "print_ast/fixtures/string-enum-fragment-arg-with-complex-input.expected", input, expected);
}

#[test]
fn unknown_enum_arg_invalid() {
    let input = include_str!("print_ast/fixtures/unknown-enum-arg.invalid.graphql");
    let expected = include_str!("print_ast/fixtures/unknown-enum-arg.invalid.expected");
    test_fixture(transform_fixture, "unknown-enum-arg.invalid.graphql", "print_ast/fixtures/unknown-enum-arg.invalid.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<d33bba31770d0dda6327e188acc496a6>>
 */

mod print;

use print::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn basic_arg_defs() {
    let input = include_str!("print/fixtures/basic_arg_defs.graphql");
    let expected = include_str!("print/fixtures/basic_arg_defs.expected");
    test_fixture(transform_fixture, "basic_arg_defs.graphql", "print/fixtures/basic_arg_defs.expected", input, expected);
}

#[test]
fn basic_directives() {
    let input = include_str!("print/fixtures/basic_directives.graphql");
    let expected = include_str!("print/fixtures/basic_directives.expected");
    test_fixture(transform_fixture, "basic_directives.graphql", "print/fixtures/basic_directives.expected", input, expected);
}

#[test]
fn basic_fragment() {
    let input = include_str!("print/fixtures/basic_fragment.graphql");
    let expected = include_str!("print/fixtures/basic_fragment.expected");
    test_fixture(transform_fixture, "basic_fragment.graphql", "print/fixtures/basic_fragment.expected", input, expected);
}

#[test]
fn basic_inline_fragments() {
    let input = include_str!("print/fixtures/basic_inline_fragments.graphql");
    let expected = include_str!("print/fixtures/basic_inline_fragments.expected");
    test_fixture(transform_fixture, "basic_inline_fragments.graphql", "print/fixtures/basic_inline_fragments.expected", input, expected);
}

#[test]
fn basic_list_object_values() {
    let input = include_str!("print/fixtures/basic_list_object_values.graphql");
    let expected = include_str!("print/fixtures/basic_list_object_values.expected");
    test_fixture(transform_fixture, "basic_list_object_values.graphql", "print/fixtures/basic_list_object_values.expected", input, expected);
}

#[test]
fn basic_query() {
    let input = include_str!("print/fixtures/basic_query.graphql");
    let expected = include_str!("print/fixtures/basic_query.expected");
    test_fixture(transform_fixture, "basic_query.graphql", "print/fixtures/basic_query.expected", input, expected);
}

#[test]
fn basic_var_defs() {
    let input = include_str!("print/fixtures/basic_var_defs.graphql");
    let expected = include_str!("print/fixtures/basic_var_defs.expected");
    test_fixture(transform_fixture, "basic_var_defs.graphql", "print/fixtures/basic_var_defs.expected", input, expected);
}

#[test]
fn conditions() {
    let input = include_str!("print/fixtures/conditions.graphql");
    let expected = include_str!("print/fixtures/conditions.expected");
    test_fixture(transform_fixture, "conditions.graphql", "print/fixtures/conditions.expected", input, expected);
}

#[test]
fn empty_args() {
    let input = include_str!("print/fixtures/empty_args.graphql");
    let expected = include_str!("print/fixtures/empty_args.expected");
    test_fixture(transform_fixture, "empty_args.graphql", "print/fixtures/empty_args.expected", input, expected);
}

#[test]
fn fragment_variables() {
    let input = include_str!("print/fixtures/fragment_variables.graphql");
    let expected = include_str!("print/fixtures/fragment_variables.expected");
    test_fixture(transform_fixture, "fragment_variables.graphql", "print/fixtures/fragment_variables.expected", input, expected);
}

#[test]
fn kitchen_sink() {
    let input = include_str!("print/fixtures/kitchen-sink.graphql");
    let expected = include_str!("print/fixtures/kitchen-sink.expected");
    test_fixture(transform_fixture, "kitchen-sink.graphql", "print/fixtures/kitchen-sink.expected", input, expected);
}

#[test]
fn nested_conditions() {
    let input = include_str!("print/fixtures/nested_conditions.graphql");
    let expected = include_str!("print/fixtures/nested_conditions.expected");
    test_fixture(transform_fixture, "nested_conditions.graphql", "print/fixtures/nested_conditions.expected", input, expected);
}

#[test]
fn single_value_array_of_objects() {
    let input = include_str!("print/fixtures/single-value-array-of-objects.graphql");
    let expected = include_str!("print/fixtures/single-value-array-of-objects.expected");
    test_fixture(transform_fixture, "single-value-array-of-objects.graphql", "print/fixtures/single-value-array-of-objects.expected", input, expected);
}

#[test]
fn string_enum_arg_invalid() {
    let input = include_str!("print/fixtures/string-enum-arg.invalid.graphql");
    let expected = include_str!("print/fixtures/string-enum-arg.invalid.expected");
    test_fixture(transform_fixture, "string-enum-arg.invalid.graphql", "print/fixtures/string-enum-arg.invalid.expected", input, expected);
}

#[test]
fn string_enum_fragment_arg() {
    let input = include_str!("print/fixtures/string-enum-fragment-arg.graphql");
    let expected = include_str!("print/fixtures/string-enum-fragment-arg.expected");
    test_fixture(transform_fixture, "string-enum-fragment-arg.graphql", "print/fixtures/string-enum-fragment-arg.expected", input, expected);
}

#[test]
fn string_enum_fragment_arg_with_complex_input() {
    let input = include_str!("print/fixtures/string-enum-fragment-arg-with-complex-input.graphql");
    let expected = include_str!("print/fixtures/string-enum-fragment-arg-with-complex-input.expected");
    test_fixture(transform_fixture, "string-enum-fragment-arg-with-complex-input.graphql", "print/fixtures/string-enum-fragment-arg-with-complex-input.expected", input, expected);
}

#[test]
fn unknown_enum_arg_invalid() {
    let input = include_str!("print/fixtures/unknown-enum-arg.invalid.graphql");
    let expected = include_str!("print/fixtures/unknown-enum-arg.invalid.expected");
    test_fixture(transform_fixture, "unknown-enum-arg.invalid.graphql", "print/fixtures/unknown-enum-arg.invalid.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::Arc;

use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_ir::build;
use graphql_ir::node_identifier::LocationAgnosticPartialEq;
use graphql_ir::ExecutableDefinition;
use graphql_ir::Program;
use graphql_syntax::parse_executable;
use graphql_text_printer::print_full_operation;
use graphql_text_printer::PrinterOptions;
use relay_test_schema::TEST_SCHEMA;
use relay_transforms::RelayLocationAgnosticBehavior;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let initial_ast = parse_executable(fixture.content, source_location).unwrap();
    let initial_ir = build(&TEST_SCHEMA, &initial_ast.definitions).unwrap();
    let initial_ir_copy = initial_ir.clone();
    let program = Program::from_definitions(Arc::clone(&TEST_SCHEMA), initial_ir.clone());
    let options = PrinterOptions {
        compact: true,
        ..Default::default()
    };

    // Print the IR into a GraphQL string for the fixture
    let output = initial_ir
        .into_iter()
        .filter_map(|definition| match definition {
            ExecutableDefinition::Operation(operation) => Some(operation),
            _ => None,
        })
        .map(|operation| print_full_operation(&program, &operation, options))
        .collect::<Vec<String>>()
        .join("\n\n");

    // Roundtrip the output back into an IR
    let roundtrip_ast = parse_executable(output.as_str(), SourceLocationKey::Generated).unwrap();
    let roundtrip_ir = build(&TEST_SCHEMA, &roundtrip_ast.definitions).unwrap();

    // Check the roundtripped IR matches the initial IR to ensure we printed a valid schema
    assert!(roundtrip_ir.location_agnostic_eq::<RelayLocationAgnosticBehavior>(&initial_ir_copy));

    Ok(output)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::Arc;

use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_ir::build;
use graphql_ir::ExecutableDefinition;
use graphql_ir::Program;
use graphql_syntax::parse_executable;
use graphql_text_printer::print_full_operation;
use relay_test_schema::TEST_SCHEMA;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let ast = parse_executable(fixture.content, source_location).unwrap();
    let ir = build(&TEST_SCHEMA, &ast.definitions).unwrap();
    let program = Program::from_definitions(Arc::clone(&TEST_SCHEMA), ir);

    build(&TEST_SCHEMA, &ast.definitions)
        .map(|definitions| {
            definitions
                .into_iter()
                .filter_map(|definition| {
                    if let ExecutableDefinition::Operation(operation) = definition {
                        Some(print_full_operation(
                            &program,
                            &operation,
                            Default::default(),
                        ))
                    } else {
                        None
                    }
                })
                .collect::<Vec<String>>()
                .join("\n\n\n\n")
        })
        .map_err(|errors| {
            errors
                .into_iter()
                .map(|error| format!("{:?}", error))
                .collect::<Vec<_>>()
                .join("\n\n")
        })
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_ir::build;
use graphql_syntax::parse_executable_with_features;
use graphql_syntax::FragmentArgumentSyntaxKind;
use graphql_syntax::ParserFeatures;
use graphql_text_printer::print_ir;
use relay_test_schema::TEST_SCHEMA;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let ast = parse_executable_with_features(
        fixture.content,
        source_location,
        ParserFeatures {
            fragment_argument_capability:
                FragmentArgumentSyntaxKind::SpreadArgumentsAndFragmentVariableDefinitions,
        },
    )
    .unwrap();
    build(&TEST_SCHEMA, &ast.definitions)
        .map(|definitions| print_ir(&TEST_SCHEMA, &definitions).join("\n\n"))
        .map_err(|errors| {
            errors
                .into_iter()
                .map(|error| format!("{:?}", error))
                .collect::<Vec<_>>()
                .join("\n\n")
        })
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_syntax::parse_executable;
use graphql_text_printer::print_executable_definition_ast;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);
    let ast = parse_executable(fixture.content, source_location).unwrap();

    Ok(ast
        .definitions
        .iter()
        .map(|definition| print_executable_definition_ast(definition))
        .collect::<Vec<String>>()
        .join("\n"))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod subscription;
mod watchman_file;

pub use subscription::WatchmanFileSourceResult;
pub use subscription::WatchmanFileSourceSubscription;
pub use subscription::WatchmanFileSourceSubscriptionNextChange;
pub use watchman_file::WatchmanFile;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::process::Command;

use log::debug;
use serde_bser::value::Value;
use watchman_client::prelude::*;
use watchman_client::Subscription as WatchmanSubscription;
use watchman_client::SubscriptionData;

use crate::WatchmanFile;

#[derive(Debug)]
pub enum WatchmanFileSourceSubscriptionNextChange {
    Result(WatchmanFileSourceResult),
    /// This value indicated the beginning of the source control update.
    /// We may stop the compilation process and wait for the next event.
    SourceControlUpdateEnter,
    /// If source control update has not changed the base revision of the commit
    /// We may continue the `watch(...)` loop of the compiler, expecting to receive
    /// a `Result` event after `SourceControlUpdateLeave`.
    SourceControlUpdateLeave,
    /// When source control update completed and we detected changed base revision,
    /// we may need to create a new compiler state.
    SourceControlUpdate,
    None,
}

#[derive(Debug)]
pub struct WatchmanFileSourceResult {
    pub files: Vec<WatchmanFile>,
    pub resolved_root: ResolvedRoot,
    pub clock: Clock,
    pub saved_state_info: Option<Value>,
}

pub struct WatchmanFileSourceSubscription {
    resolved_root: ResolvedRoot,
    subscription: WatchmanSubscription<WatchmanFile>,
    base_revision: Option<String>,
}

impl WatchmanFileSourceSubscription {
    pub fn new(
        resolved_root: ResolvedRoot,
        subscription: WatchmanSubscription<WatchmanFile>,
    ) -> Self {
        Self {
            resolved_root,
            subscription,
            base_revision: get_base_hg_revision(None),
        }
    }

    /// Awaits changes from Watchman and provides the next set of changes
    /// if there were any changes to files
    pub async fn next_change(
        &mut self,
    ) -> Result<WatchmanFileSourceSubscriptionNextChange, watchman_client::Error> {
        match self.subscription.next().await? {
            SubscriptionData::FilesChanged(changes) => {
                if let Some(files) = changes.files {
                    debug!("number of files in this update: {}", files.len());
                    return Ok(WatchmanFileSourceSubscriptionNextChange::Result(
                        WatchmanFileSourceResult {
                            files,
                            resolved_root: self.resolved_root.clone(),
                            clock: changes.clock,
                            saved_state_info: None,
                        },
                    ));
                }
            }
            SubscriptionData::StateEnter { state_name, .. } => {
                if state_name == "hg.update" {
                    return Ok(WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateEnter);
                }
            }
            SubscriptionData::StateLeave {
                state_name,
                metadata,
            } => {
                if state_name == "hg.update" {
                    let current_commit = if let Some(Value::ByteString(value)) = metadata {
                        Some(value.to_string())
                    } else {
                        None
                    };
                    let current_base_revision = get_base_hg_revision(current_commit);
                    if current_base_revision != self.base_revision {
                        self.base_revision = current_base_revision;
                        return Ok(WatchmanFileSourceSubscriptionNextChange::SourceControlUpdate);
                    } else {
                        return Ok(
                            WatchmanFileSourceSubscriptionNextChange::SourceControlUpdateLeave,
                        );
                    }
                }
            }
            SubscriptionData::Canceled => {
                return Err(watchman_client::Error::WatchmanResponseError {
                    message: String::from("Watchman subscription canceled"),
                });
            }
        }
        Ok(WatchmanFileSourceSubscriptionNextChange::None)
    }
}

/// Base revision in this case is a common ancestor of two revisions:
/// `master` and current commit hash or `.`
///
/// TODO: Make this dynamic on the default branch name.
fn get_base_hg_revision(commit_hash: Option<String>) -> Option<String> {
    let output = Command::new("hg")
        .arg("log".to_string())
        .arg("-r".to_string())
        .arg(format!(
            "ancestor(master, {})",
            commit_hash.unwrap_or_else(|| ".".to_string())
        ))
        .arg("-T={node}")
        .output()
        .ok()?;

    if output.stdout.is_empty() {
        return None;
    }

    Some(String::from_utf8_lossy(&output.stdout).to_string())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use serde::Deserialize;
use watchman_client::prelude::*;

query_result_type! {
    pub struct WatchmanFile {
        pub name: NameField,
        pub exists: ExistsField,
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::cell::UnsafeCell;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::mem::MaybeUninit;
use std::num::NonZeroU32;
use std::ptr;
use std::ptr::NonNull;
use std::sync::atomic::AtomicPtr;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;

use parking_lot::Mutex;

const MIN_SHIFT: u32 = 7;
const U32_BITS: usize = 32;
const MIN_SIZE: u32 = 1 << MIN_SHIFT;
const NUM_SIZES: usize = U32_BITS - MIN_SHIFT as usize;
const MAX_INDEX: u32 = std::u32::MAX - MIN_SIZE;

// Memory consistency assertions provide a lot of checking of the internals,
// but have a huge runtime cost.  Be warned!  These are really for active
// debugging of atomic_arena itself.
macro_rules! memory_consistency_assert {
    ($($arg:tt)*) => (if cfg!(memory_consistency_assertions) { assert!($($arg)*); })
}

macro_rules! memory_consistency_assert_eq {
    ($($arg:tt)*) => (if cfg!(memory_consistency_assertions) { assert_eq!($($arg)*); })
}

/// `AtomicArena<'a, T>` is a nearly-lock-free arena of `T`.  It offers O(1)
/// lock-free `get` and O(1) nearly lock-free `add`, and can be accessed
/// through the `Ref<'a, T>` type which is internally stored as `NonZeroU32`.
/// This means that `Ref<...>` and `Option<Ref<...>>` both take 4 bytes.
///
/// It is unsafe to get a `Ref` that was not produced by `add` to the same
/// `AtomicArena`; the type arguments on `Ref` and `AtomicArena` are intended
/// to enforce that separation.
///
/// How it works
///
/// Internally, `AtomicArena` uses a telescoping array of buckets, arranged in
/// reverse order.  The last bucket holds the first `MIN_SIZE` objects
/// allocated, and each bucket in use holds twice as many objects as the one
/// above it.  So when `MIN_SIZE` = 128, initially things look like this:
/// ```ignore
/// next_biased_index: 128
/// buckets:
///   +----+
///   |    |
///   |  0 | null
///   +----+
///   |    |
///   |  1 | null
///   +----+
///   :  : :
///   +----+
///   |    |
///   | 23 | null
///   +----+
///   |    |     +--+--+....--------------------------+
///   | 24 | --> |  |  | array of 128 uninitialized T |
///   +----+     +--+--+....--------------------------+
/// ```
/// After allocating 537 objects, things look as follows:
/// ```ignore
/// next_biased_index: 665 = 537 + 128
/// buckets:
///   +----+
///   |  0 | null
///   +----+
///   |  1 | null
///   +----+
///   :  : :
///   +----+
///   |    |                                 Ref 664
///   | 21 | null                            |
///   +----+                                 v
///   |    |     +--+--+....----------------+--+-------------------------------+
///   | 22 | --> |  |  | objects 512 -- 663 |  | empty entries 665 -- 1023     |
///   +----+     +--+--+....----------------+--+-------------------------------+
///   |    |     +--+--+....-----------------------------------+
///   | 23 | --> |  |  | objects for Refs 256 -- 511           |
///   +----+     +--+--+....-----------------------------------+
///   |    |     +--+--+....--------------------------+
///   | 24 | --> |  |  | objects for Refs 128 -- 255  |
///   +----+     +--+--+....--------------------------+
///               ^  ^
///      Ref 128 -'  `- Ref 129
/// ```
///
/// Note that the first object allocted gets index `MIN_SIZE`, and that we cap
/// the extra space allocated for currently empty entries at
/// `2 * len() + MIN_SIZE - 1`.
///
/// Why are the buckets in reverse order like this?  It's for ease of index
/// computation in `get()`.  In particular, note that the bucket index is equal
/// to the number of leading zeros in the `Ref`, and the offset in the bucket
/// is the trailing bits of the Ref after clearing the topmost 1 bit.
/// We could waste somewhat less storage than this with some more bit
/// trickery, but this is currently good enough for our purposes.
///
/// In the common case, allocation is simply a matter of atomically
/// incrementing `next_biased_index` and initializing the empty entry.  When a
/// bucket fills (we discover the bucket pointer we fetched is null in
/// `slice_for_slot`), we fall back to locking in `slice_for_slot_slow` by
/// taking the `bucket_alloc_mutex`.  This re-checks the entry in `buckets` and
/// allocates a fresh bucket if it's still empty.
pub struct AtomicArena<'a, T> {
    phantom_life: PhantomData<&'a ()>,
    next_biased_index: AtomicU32,
    /// buckets in reverse order, starting from the back and working
    /// forwards.  Capacity for bucket i is bucket_capacity(i).
    buckets: [AtomicPtr<MaybeUninit<T>>; NUM_SIZES as usize],
    bucket_alloc_mutex: Mutex<()>,
}

/// `Ref<'a, T>` is a 32-bit nonzero reference to an object of type `T` held in
/// the `AtomicArena<'a, T>`.  It acts similar to `&'a T` in that it is `Copy`
/// and `Clone` regardless of `T` and dereferences via `arena.get(...)` to `&T`.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)] // Same repr & ABI as NonZeroU32 & u32.
pub struct Ref<'a, T> {
    phantom: PhantomData<T>,
    phantom_life: PhantomData<&'a ()>,
    biased_index: NonZeroU32,
}

/// `Ref<'a, T>` requires manual implementations of `Clone` and `Copy` since `T` is a
/// phantom parameter and not required to support `Clone` or `Copy`.
impl<'a, T> Clone for Ref<'a, T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<'a, T> Copy for Ref<'a, T> {}

impl<'a, T> Ref<'a, T> {
    /// Gets an index suitable for array indexing out of a `Ref<'a, T>`.
    /// The resulting index *is* 0-based, and < the corresponding
    /// `arena.len()`.
    pub fn index(&self) -> u32 {
        self.biased_index.get() - MIN_SIZE // Unbias to be 0-based.
    }

    /// Re-create ref from biased index.
    pub unsafe fn from_index(index: u32) -> Self {
        // OK because MIN_SIZE must be > 0 for algorithmic correctness.
        Self::from_raw(NonZeroU32::new_unchecked(index + MIN_SIZE))
    }

    /// Internal value of ref; only use this if you know what you're
    /// doing.  Actually `>= MIN_SIZE`.
    pub(crate) fn raw(&self) -> NonZeroU32 {
        self.biased_index
    }

    /// Re-create ref from raw index.
    pub unsafe fn from_raw(biased_index: NonZeroU32) -> Self {
        Ref {
            phantom: PhantomData,
            phantom_life: PhantomData,
            biased_index,
        }
    }

    /// Very unsafe rebranding of one ref type into another.
    /// Use for serialization / deserialization of refs.
    pub(crate) unsafe fn rebrand<'b, U>(&self) -> Ref<'b, U> {
        Ref {
            phantom: PhantomData,
            phantom_life: PhantomData,
            biased_index: self.biased_index,
        }
    }
}

impl<'a, T> Debug for Ref<'a, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "atomic_arena::Ref({:?})", self.biased_index)
    }
}

impl<'a, T> Display for Ref<'a, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "#{}", self.index())
    }
}

/// Capacity of bucket at bucket index `a`.
fn bucket_capacity(a: usize) -> usize {
    (1 << 31) >> (a as u32)
}

/// Transform external biased index `i` into (bucket index, index in bucket)
fn index(i: u32) -> (usize, usize) {
    memory_consistency_assert!(i >= MIN_SIZE);
    memory_consistency_assert!(i - MIN_SIZE <= MAX_INDEX);
    memory_consistency_assert!(i as u64 <= std::usize::MAX as u64);
    let a = i.leading_zeros() as usize;
    memory_consistency_assert!(a < NUM_SIZES, "{} < {}", a, NUM_SIZES);
    let b = (i & ((bucket_capacity(0) as u32 - 1) >> a)) as usize;
    memory_consistency_assert!(b < bucket_capacity(a));
    memory_consistency_assert_eq!(i, bucket_capacity(a) as u32 + b as u32);
    (a, b)
}

/// A default instance makes it easier to create sharded instances.
impl<'a, T> Default for AtomicArena<'a, T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a, T> AtomicArena<'a, T> {
    pub const fn new() -> Self {
        AtomicArena {
            phantom_life: PhantomData,
            next_biased_index: AtomicU32::new(MIN_SIZE),
            buckets: [
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
            ],
            bucket_alloc_mutex: parking_lot::const_mutex(()),
        }
    }

    /// Get pointer to bucket for slot `a`.  Normally this just fetches
    /// the pointer from `self.buckets`, but if this is the first `add`
    /// it calls `slice_for_slot_slow` to allocate it.
    #[inline]
    fn slice_for_slot(&self, a: usize) -> NonNull<MaybeUninit<T>> {
        if let Some(curr) = NonNull::new(self.buckets[a as usize].load(Ordering::Acquire)) {
            curr
        } else {
            self.slice_for_slot_slow(a)
        }
    }

    /// Try to allocate a new bucket.  Ordinarily this will be the
    /// first `add` attempt to that bucket, but there can be a race to
    /// create a fresh bucket among multiple `add` threads.
    fn slice_for_slot_slow(&self, a: usize) -> NonNull<MaybeUninit<T>> {
        // Take the allocation mutex, and double-check that the bucket still
        // needs to be allocated.  Double-checked locking is fine because the
        // buckets are `AtomicPtr` with the unlocked read and locked write
        // as an `Acquire / Release` pair.
        let lock = self.bucket_alloc_mutex.lock();
        // Relaxed load because we know we're competing with prior lock holders now.
        if let Some(curr) = NonNull::new(self.buckets[a as usize].load(Ordering::Relaxed)) {
            return curr;
        }
        let cap = bucket_capacity(a) as usize;
        // Allocate bucket as vector, then prise it apart since we
        // only care about capacity and pointer.  We use MaybeUninit
        // because we are tracking slot validity across all buckets
        // separately using self.size.
        // https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees
        // notes that "Vec::with_capacity(n), will produce a Vec with
        // exactly the requested capacity".
        let mut v: ManuallyDrop<Vec<MaybeUninit<T>>> = ManuallyDrop::new(Vec::with_capacity(cap));
        let len = v.len();
        let acap = v.capacity();
        let ptr = v.as_mut_ptr();
        // Ensure with_capacity has obeyed its guarantee.
        memory_consistency_assert!(acap == cap || std::mem::size_of::<T>() == 0);
        memory_consistency_assert_eq!(len, 0);
        if let Some(nn_ptr) = NonNull::new(ptr) {
            self.buckets[a as usize].store(ptr, Ordering::Release);
            drop(lock);
            nn_ptr
        } else {
            panic!("Vec with non-0 len and null ptr!")
        }
    }

    #[inline]
    /// Number of allocated objects in the arena as of the time of call.
    pub fn len(&self) -> usize {
        (self.next_biased_index.load(Ordering::Relaxed) - MIN_SIZE) as usize
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns both `Ref` to the added value and the reference that
    /// would be returned by `get(id)`.  Equivalent to:
    /// ```ignore
    ///    let id = arena.add(element);
    ///    let r = arena.get(id);
    ///    (id, r)
    /// ```
    pub fn add_get(&self, element: T) -> (Ref<'a, T>, &T) {
        // Atomically obtain an id, thus resolving conflicts among
        // concurrent add() operations.
        let s = self.next_biased_index.fetch_add(1, Ordering::Relaxed);
        // Linearization point for add().
        assert!(s >= MIN_SIZE); // Panic on wraparound ( == overflow).
        let biased_index = NonZeroU32::new(s).unwrap(); // Succeeds after above check.
        let (a, b) = index(s);
        let e_ptr = self.slice_for_slot(a).as_ptr();
        // This is checked in index_test with monotonicity checks
        // at boundaries.  Note that s and thus (a, b) are unique,
        // so this is the first and only write to this slot.
        //
        // This is also the point where using MaybeUninit is
        // important.  This write will otherwise attempt to Drop
        // the current (uninitialized) contents of this bucket
        // entry before writing the new contents.  This can yield
        // a hard-to-debug segfault in the internals of malloc.
        let e_ptr: *mut MaybeUninit<T> = unsafe { e_ptr.add(b as usize) };
        unsafe {
            *e_ptr = MaybeUninit::new(element);
        }
        let e: &T = unsafe { &*(&*e_ptr).as_ptr() };
        (
            Ref {
                phantom: PhantomData,
                phantom_life: PhantomData,
                biased_index,
            },
            e,
        )
    }

    /// Returns `Ref` to the the added value, which can safely be
    /// passed to `get`.  `self.get(self.add(t)) == &t`
    pub fn add(&self, element: T) -> Ref<'a, T> {
        let (biased_index, _) = self.add_get(element);
        biased_index
    }

    /// `get(i)` is safe if, like a pointer we might dereference, `i` is a
    /// `Ref` returned by `self.add(...)` obtained in a thread-safe way
    /// (unsafe example: load via Ordering::Relaxed).
    pub fn get(&self, r: Ref<'a, T>) -> &T {
        let i = r.biased_index.get();
        // In debug mode, bounds check and panic.  Note that this bounds check
        // won't catch all unsafe accesses, since add() increments size *before*
        // storing to the bucket.
        if cfg!(debug_assertions) {
            let l = self.next_biased_index.load(Ordering::Relaxed);
            debug_assert!(i < l, "{} < {}", i, l);
        }
        let (a, b) = index(i);
        let e_ptr = unsafe {
            // Get bucket address, but do *not* allocate a bucket.
            // Ordering::Relaxed is OK because we got a Ref in a
            // thread-safe way in get(..).
            self.buckets
                .get_unchecked(a as usize)
                .load(Ordering::Relaxed)
        };
        // Sanity check bucket.  Again, won't catch all unsafe accesses.
        memory_consistency_assert!(!e_ptr.is_null());
        unsafe {
            // Read the added element, and strip the MaybeUnit wrapper to yield a &T.
            let r: &MaybeUninit<T> = &*e_ptr.add(b as usize);
            &*r.as_ptr()
        }
    }
}

impl<'a, T> Debug for AtomicArena<'a, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "AtomicArena[{}]", self.len())
    }
}

impl<'a, T> Drop for AtomicArena<'a, T> {
    fn drop(&mut self) {
        // At this point all other outstanding operations on self must
        // be complete since we have a &mut reference.
        let l = self.next_biased_index.load(Ordering::Relaxed);
        if l == MIN_SIZE {
            // Nothing to do, and otherwise there's a fencepost error
            // in index(l-1) below.
            return;
        }
        let (last_a, last_b) = index(l - 1);
        for (a, bucket) in self.buckets.iter_mut().enumerate().rev() {
            // We turn each allocated bucket into a `Vec<T>` whose `capacity`
            // matches the bucket capacity and whose `len` matches the number
            // of entries in use (equal to `capacity` except for `last_a` which
            // is only using `last_b` entries).  We can then `drop` the
            // resulting `Vec`, freeing any storage held by the allocated
            // objects in addition to the storage for the bucket itself.
            if a < last_a {
                break;
            }
            let mut b_ptr = if let Some(nn) = NonNull::new(bucket.load(Ordering::Relaxed)) {
                nn
            } else {
                panic!("Null bucket pointer before length.  Shouldn't happen.")
            };
            let cap = bucket_capacity(a);
            // Every bucket after last_a is full, every bucket before it is empty.
            // last_b is the last element in last_a that has been initialized.
            let sz = if a == last_a { last_b + 1 } else { cap };
            let iv: Vec<T> = unsafe {
                // View b_ptr as a *mut T rather than a NonNull<MaybeUninit<T>>.
                let b_ptr: *mut T = (b_ptr.as_mut()).as_mut_ptr();
                // We know that the first sz elements at *b_ptr are
                // initialized, and c elements were allocated.  Build
                // a well-formed Vec<T> (stripping away MaybeUninit<>)
                // and deallocate that.  This should be at least as
                // efficient as simply calling Drop on individual
                // elements, and is vastly simpler than calling the
                // mem apis directly.
                Vec::from_raw_parts(b_ptr, sz as usize, cap as usize)
            };
            drop(iv);
            bucket.store(ptr::null_mut(), Ordering::Relaxed)
        }
    }
}

/// Sometimes we want to create a static `AtomicArena` with a distinguished
/// `ZERO` element pre-added at compile time.  (Example: string interning with
/// a distinguished empty string).  The `Zero<T>` struct allows us to do that
/// using a series of top-level constants.
///
/// IMPORTANT: A GIVEN INSTANCE OF `Zero<T>` CAN BE PASSED TO AT MOST ONE CALL
/// TO `AtomicArena::with_zero`!!!!  This cannot be statically enforced, which
/// is one reason there's a macro in `intern` that wraps this all up safely.
pub struct Zero<T> {
    last_bucket: [UnsafeCell<MaybeUninit<T>>; MIN_SIZE as usize],
}

impl<T> Zero<T> {
    #[inline(always)]
    pub const fn zero() -> Ref<'static, T> {
        Ref {
            phantom: PhantomData,
            phantom_life: PhantomData,
            biased_index: unsafe { NonZeroU32::new_unchecked(MIN_SIZE) },
        }
    }

    fn zero_value(&self) -> &T {
        let r: &MaybeUninit<T> = unsafe { &*self.last_bucket[0].get() };
        unsafe { &*r.as_ptr() }
    }

    pub const fn new(zero: T) -> Self {
        Zero {
            last_bucket: [
                UnsafeCell::new(MaybeUninit::new(zero)),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
                UnsafeCell::new(MaybeUninit::uninit()),
            ],
        }
    }
}

unsafe impl<T> Sync for Zero<T> {}

impl<T: Debug> Debug for Zero<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Zero({:?})", self.zero_value())
    }
}

impl<T> AtomicArena<'static, T> {
    pub const fn with_zero(z: &'static Zero<T>) -> Self {
        let p: *mut MaybeUninit<T> = z.last_bucket[0].get();
        AtomicArena {
            phantom_life: PhantomData,
            next_biased_index: AtomicU32::new(MIN_SIZE + 1),
            buckets: [
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(ptr::null_mut()),
                AtomicPtr::new(p),
            ],
            bucket_alloc_mutex: parking_lot::const_mutex(()),
        }
    }
}

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

    use parking_lot::Condvar;
    use parking_lot::Mutex;
    use rand::thread_rng;
    use rand::Rng;

    use super::*;

    static mut ZERO: Zero<&str> = Zero::new("zero");
    static STRING_ARENA: AtomicArena<'static, &str> = AtomicArena::with_zero(unsafe { &ZERO });

    /// For internal testing purposes we permit the unsafe synthesis of Refs.
    fn mk_ref<'a, T>(index: u32) -> Ref<'a, T> {
        Ref {
            phantom: PhantomData,
            phantom_life: PhantomData,
            biased_index: NonZeroU32::new(index + MIN_SIZE).unwrap(),
        }
    }

    #[test]
    fn empty_drop() {
        let vi: AtomicArena<'_, String> = AtomicArena::new();
        drop(vi);
    }

    #[test]
    fn sizing() {
        // A bunch of checking for off-by-one errors of various sorts
        // in the vector sizes for the telescoping array.
        for a in 0..(NUM_SIZES - 1) {
            assert_eq!(1 << (U32_BITS - 1 - a), bucket_capacity(a));
        }
        assert_eq!(MIN_SIZE as usize, bucket_capacity(NUM_SIZES - 1));
    }

    #[test]
    fn indexing() {
        let (a, b) = index(MIN_SIZE);
        assert_eq!(a, NUM_SIZES - 1);
        assert_eq!(b, 0);
        let (a, b) = index(std::u32::MAX);
        assert_eq!(a, 0);
        assert_eq!(b, (1 << 31) - 1);
        assert_eq!(b, bucket_capacity(0) - 1);
        // Check thresholds
        for s in (MIN_SHIFT + 1)..(U32_BITS as u32) {
            let i = 1 << (s as u32);
            let (a0, b0) = index(i - 1);
            let (a1, b1) = index(i);
            assert_eq!(a0, U32_BITS - s as usize);
            assert_eq!(
                a1,
                U32_BITS - s as usize - 1,
                "at {} -> ({}, {})",
                i,
                a1,
                b1
            );
            assert_eq!(b0, bucket_capacity(a0) - 1);
            assert_eq!(b1, 0);
        }
        // Check monotonicity.
        for i in MIN_SIZE..9 * MIN_SIZE {
            let (a0, b0) = index(i);
            let (a1, b1) = index(i + 1);
            assert!(
                (a0 == a1 && b0 < b1) || (a0 == (a1 + 1) && b1 == 0),
                "{} {} {} >= {} {} {}",
                i - 1,
                a0,
                b0,
                i,
                a1,
                b1
            );
        }
    }

    #[test]
    fn add_read() {
        let v: AtomicArena<'_, usize> = AtomicArena::new();
        assert_eq!(v.len(), 0);
        for i in 0..10_000 {
            let ii = i as u32;
            let r = v.add(i);
            assert_eq!(r.index(), ii);
            assert_eq!(v.len(), i + 1);
            assert_eq!(*v.get(r), i);
            for j in 0..=ii {
                assert_eq!(*v.get(mk_ref(j)), j as usize);
            }
        }
        // For the rest of the range, don't do the O(n^2) full check.
        for i in 10_000..10_000_000 {
            let r = v.add(i);
            assert_eq!(r.index(), i as u32);
            assert_eq!(v.len(), i + 1);
            assert_eq!(*v.get(r), i);
        }
    }

    #[test]
    fn add_read_static() {
        assert_eq!(Zero::<&str>::zero().index(), 0);
        assert_eq!(STRING_ARENA.len(), 1);
        assert_eq!(STRING_ARENA.get(Zero::<&str>::zero()), &"zero");
        for i in 1..1_000 {
            let r = STRING_ARENA.add(Box::leak(format!("{}", i).into()));
            assert_eq!(r.index(), i);
            assert_eq!(STRING_ARENA.len(), i as usize + 1);
            assert_eq!(STRING_ARENA.get(r), &format!("{}", i));
            assert_eq!(STRING_ARENA.get(Zero::<&str>::zero()), &"zero");
            for j in 1..=i {
                assert_eq!(STRING_ARENA.get(mk_ref(j)), &format!("{}", j));
            }
        }
        // For the rest of the range, don't do the O(n^2) full check.
        for i in 1_000..1_000_000 {
            let r = STRING_ARENA.add(Box::leak(format!("{}", i).into()));
            assert_eq!(r.index(), i);
            assert_eq!(STRING_ARENA.len(), i as usize + 1);
            assert_eq!(STRING_ARENA.get(r), &format!("{}", i));
            assert_eq!(STRING_ARENA.get(Zero::<&str>::zero()), &"zero");
        }
    }

    #[test]
    fn add_parallel_read() {
        const N: u32 = 1_000_000;
        let arena: Arc<AtomicArena<'_, String>> = Arc::new(AtomicArena::new());
        // Make sure we don't just run the producer or all the
        // consumers without interleaving them.
        let progress = Arc::new((Mutex::new(0u32), Condvar::new()));
        let mut consumers = Vec::new();
        let len = Arc::new(AtomicU32::new(0));
        for r in 0..10 {
            // Consumers
            let arena = arena.clone();
            let progress = progress.clone();
            let len = len.clone();
            consumers.push(thread::spawn(move || {
                const I: u32 = N * 3 / 2;
                let mut rng = thread_rng();
                let mut next_poke = 1500;
                let mut next_seek = 1000;
                let mut n_seen = 0;
                for ii in 0..I {
                    let n = len.load(Ordering::Acquire);
                    if n > 0 {
                        // First reader always checks latest completed add.
                        let i = if r == 0 { n - 1 } else { rng.gen_range(0..n) };
                        let s = arena.get(mk_ref(i));
                        assert_eq!(s, &format!("{}", i));
                        if r == 0 {
                            // n should trail arena.len().
                            let l = arena.len();
                            assert!(n as usize <= l);
                        }
                        n_seen += 1;
                    }
                    if ii == next_poke {
                        let (lock, cvar) = &*progress;
                        let mut l = lock.lock();
                        while *l < next_seek {
                            cvar.wait(&mut l);
                        }
                        next_poke *= 10;
                        next_seek *= 10;
                    }
                }
                assert!(n_seen > 0);
            }));
        }
        {
            // Producer in main thread.
            let mut next_poke = 1000;
            for i in 0..N {
                let r = arena.add(format!("{}", i));
                assert_eq!(r.index(), i);
                len.store(i + 1, Ordering::Release);
                if i == next_poke {
                    let (lock, cvar) = &*progress;
                    *lock.lock() = i;
                    cvar.notify_all();
                    next_poke *= 10;
                }
            }
        }
        for c in consumers {
            c.join().unwrap();
        }
    }

    #[test]
    fn parallel_add_parallel_read() {
        use std::sync::atomic::AtomicU32;
        const N: u32 = 2_000_000;
        const WRITERS: u32 = 10;
        let arena: Arc<AtomicArena<'_, usize>> = Arc::new(AtomicArena::new());
        let mut avail: Arc<Vec<AtomicU32>> = Arc::new(Vec::with_capacity(N as usize));
        Arc::get_mut(&mut avail)
            .unwrap()
            .resize_with(N as usize, || AtomicU32::new(10 * N as u32));
        // Make sure we don't just run the producer or all the
        // consumers without interleaving them.
        let progress = Arc::new((Mutex::new(0u32), Condvar::new()));
        let mut producers = Vec::new();
        let mut consumers = Vec::new();
        for k in 0..WRITERS {
            let arena = arena.clone();
            let avail = avail.clone();
            let progress = progress.clone();
            producers.push(thread::spawn(move || {
                let mut next_poke = 10;
                for i in 0..N / WRITERS {
                    let n = i * WRITERS + k;
                    let id = arena.add(n as usize);
                    assert!(id.index() < N);
                    assert_eq!(
                        avail[id.index() as usize].load(Ordering::Acquire),
                        10 * N as u32
                    );
                    avail[id.index() as usize].store(n as u32, Ordering::Release);
                    if k == 0 && i == next_poke {
                        let (lock, cvar) = &*progress;
                        *lock.lock() = i;
                        next_poke *= 10;
                        cvar.notify_all();
                    }
                }
            }));
        }
        for _ in 0..10 {
            const I: u32 = N * 3 / 2;
            let arena = arena.clone();
            let avail = avail.clone();
            let progress = progress.clone();
            consumers.push(thread::spawn(move || {
                let mut rng = thread_rng();
                let mut next_poke = 150;
                let mut next_seek = 10;
                let mut n_seen = 0;
                for ii in 0..I {
                    let i = rng.gen_range(0..N);
                    let expect = avail[i as usize].load(Ordering::Acquire);
                    if expect < N {
                        let s = arena.get(mk_ref(i));
                        assert_eq!(*s, expect as usize);
                        n_seen += 1;
                    }
                    if ii == next_poke {
                        let (lock, cvar) = &*progress;
                        let mut l = lock.lock();
                        while *l < next_seek {
                            cvar.wait(&mut l);
                        }
                        next_poke *= 10;
                        next_seek *= 10;
                        if next_seek >= N {
                            next_seek = 0;
                        }
                    }
                }
                assert!(n_seen > 0);
            }));
        }
        for p in producers {
            p.join().unwrap();
        }
        assert_eq!(arena.len(), N as usize);
        let mut fail: Vec<(u32, u32)> = Vec::new();
        for i in 0..N {
            let a = avail[i as usize].load(Ordering::Relaxed);
            if a >= N {
                fail.push((a, i as u32));
            }
        }
        assert!(fail.is_empty(), "{:?}", fail);
        for c in consumers {
            c.join().unwrap();
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::hash::BuildHasherDefault;
use std::hash::Hasher;
use std::marker::PhantomData;

pub type BuildIdHasher<T> = BuildHasherDefault<IdHasher<T>>;

/// A simple fast multiplicative hasher for Ids.
///
/// It's tempting to use `IdHasher` for Ids --- but
/// [HashBrown](https://crates.io/crates/hashbrown) and
/// [std::collections::HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html)
/// use the upper 7 bits of the hash for a tag, then compare 8-16 tags in
/// parallel. Without the multiply, typical low-valued u32 ids would all have
/// tag 0.
#[derive(Debug, Default, Clone, Copy)]
pub struct IdHasher<T>(u64, PhantomData<T>);

/// Marker interface to allow supported types. Additional primitive types
/// can be supported by adding an `IsEnabled` decl.
pub trait IsEnabled {}
impl IsEnabled for u32 {}

impl<T: IsEnabled> Hasher for IdHasher<T> {
    fn write(&mut self, _: &[u8]) {
        unimplemented!()
    }

    fn write_u32(&mut self, n: u32) {
        debug_assert_eq!(self.0, 0); // Allow one write per Hasher instance.
        self.0 = (n as u64).wrapping_mul(0x9e3779b97f4a7c15);
    }

    fn finish(&self) -> u64 {
        self.0
    }
}