// Copyright 2016-2019, Pulumi Corporation

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;

namespace Pulumi
{
    public partial class Deployment
    {
        internal static bool _excessiveDebugOutput = false;

        /// <summary>
        /// <see cref="SerializeResourcePropertiesAsync"/> walks the props object passed in,
        /// awaiting all interior promises besides those for <see cref="Resource.Urn"/> and <see
        /// cref="CustomResource.Id"/>, creating a reasonable POCO object that can be remoted over
        /// to registerResource.
        /// </summary>
        private static Task<SerializationResult> SerializeResourcePropertiesAsync(
            string label, IDictionary<string, object?> args, bool keepResources, bool keepOutputValues)
        {
            return SerializeFilteredPropertiesAsync(
                label, args,
                key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName,
                keepResources, keepOutputValues: keepOutputValues);
        }

        private static async Task<Struct> SerializeAllPropertiesAsync(
            string label, IDictionary<string, object?> args, bool keepResources, bool keepOutputValues = false)
        {
            var result = await SerializeFilteredPropertiesAsync(
                label, args, _ => true,
                keepResources, keepOutputValues).ConfigureAwait(false);
            return result.Serialized;
        }

        /// <summary>
        /// <see cref="SerializeFilteredPropertiesAsync"/> walks the props object passed in,
        /// awaiting all interior promises for properties with keys that match the provided filter,
        /// creating a reasonable POCO object that can be remoted over to registerResource.
        /// </summary>
        ///
        /// <param name="label">label</param>
        /// <param name="args">args</param>
        /// <param name="acceptKey">acceptKey</param>
        /// <param name="keepResources">keepResources</param>
        /// <param name="keepOutputValues">
        /// Specifies if we should marshal output values. It is the callers
        /// responsibility to ensure that the monitor supports the OutputValues
        /// feature.
        /// </param>
        private static async Task<SerializationResult> SerializeFilteredPropertiesAsync(
            string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources, bool keepOutputValues)
        {
            var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources, keepOutputValues);
            return result.ToSerializationResult();
        }

        /// <summary>
        /// Acts as `SerializeFilteredPropertiesAsync` without the
        /// last step of encoding the value into a Protobuf form.
        /// </summary>
        private static async Task<RawSerializationResult> SerializeFilteredPropertiesRawAsync(
            string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources, bool keepOutputValues)
        {
            var propertyToDependentResources = ImmutableDictionary.CreateBuilder<string, HashSet<Resource>>();
            var result = ImmutableDictionary.CreateBuilder<string, object>();

            foreach (var (key, val) in args)
            {
                if (acceptKey(key))
                {
                    // We treat properties with null values as if they do not exist.
                    var serializer = new Serializer(_excessiveDebugOutput);
                    var v = await serializer.SerializeAsync($"{label}.{key}", val, keepResources, keepOutputValues).ConfigureAwait(false);
                    if (v != null)
                    {
                        result[key] = v;
                        propertyToDependentResources[key] = serializer.DependentResources;
                    }
                }
            }

            return new RawSerializationResult(
                result.ToImmutable(),
                propertyToDependentResources.ToImmutable());
        }

        private readonly struct SerializationResult
        {
            public readonly Struct Serialized;
            public readonly ImmutableDictionary<string, HashSet<Resource>> PropertyToDependentResources;

            public SerializationResult(
                Struct result,
                ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
            {
                Serialized = result;
                PropertyToDependentResources = propertyToDependentResources;
            }

            public void Deconstruct(
                out Struct serialized,
                out ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
            {
                serialized = Serialized;
                propertyToDependentResources = PropertyToDependentResources;
            }
        }

        private readonly struct RawSerializationResult
        {
            public readonly ImmutableDictionary<string, object> PropertyValues;
            public readonly ImmutableDictionary<string, HashSet<Resource>> PropertyToDependentResources;

            public RawSerializationResult(
                ImmutableDictionary<string, object> propertyValues,
                ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
            {
                PropertyValues = propertyValues;
                PropertyToDependentResources = propertyToDependentResources;
            }

            public SerializationResult ToSerializationResult()
                => new SerializationResult(
                    Serializer.CreateStruct(PropertyValues),
                    PropertyToDependentResources);
        }
    }
}
