// Copyright 2016-2019, Pulumi Corporation

using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumirpc;

namespace Pulumi
{
    public partial class Deployment
    {
        private async Task<(string urn, string id, Struct data)> RegisterResourceAsync(
            Resource resource, ResourceArgs args, ResourceOptions options)
        {
            var name = resource.GetResourceName();
            var type = resource.GetResourceType();
            var custom = resource is CustomResource;

            var label = $"resource:{name}[{type}]";
            Log.Debug($"Registering resource start: t={type}, name={name}, custom={custom}");

            var request = CreateRegisterResourceRequest(type, name, custom, options);

            Log.Debug($"Preparing resource: t={type}, name={name}, custom={custom}");
            var prepareResult = await PrepareResourceAsync(label, resource, custom, args, options).ConfigureAwait(false);
            Log.Debug($"Prepared resource: t={type}, name={name}, custom={custom}");

            PopulateRequest(request, prepareResult);

            Log.Debug($"Registering resource monitor start: t={type}, name={name}, custom={custom}");
            var result = await this.Monitor.RegisterResourceAsync(request);
            Log.Debug($"Registering resource monitor end: t={type}, name={name}, custom={custom}");
            return (result.Urn, result.Id, result.Object);
        }

        private static void PopulateRequest(RegisterResourceRequest request, PrepareResult prepareResult)
        {
            request.Object = prepareResult.SerializedProps;
            request.Parent = prepareResult.ParentUrn;
            request.Provider = prepareResult.ProviderRef;
            request.Aliases.AddRange(prepareResult.Aliases);
            request.Dependencies.AddRange(prepareResult.AllDirectDependencyURNs);

            foreach (var (key, resourceURNs) in prepareResult.PropertyToDirectDependencyURNs)
            {
                var deps = new RegisterResourceRequest.Types.PropertyDependencies();
                deps.Urns.AddRange(resourceURNs);
                request.PropertyDependencies.Add(key, deps);
            }
        }

        private static RegisterResourceRequest CreateRegisterResourceRequest(string type, string name, bool custom, ResourceOptions options)
        {
            var customOpts = options as CustomResourceOptions;
            var deleteBeforeReplace = customOpts?.DeleteBeforeReplace;

            var request = new RegisterResourceRequest()
            {
                Type = type,
                Name = name,
                Custom = custom,
                Protect = options.Protect ?? false,
                Version = options.Version ?? "",
                ImportId = customOpts?.ImportId ?? "",
                AcceptSecrets = true,
                DeleteBeforeReplace = deleteBeforeReplace ?? false,
                DeleteBeforeReplaceDefined = deleteBeforeReplace != null,
                CustomTimeouts = new RegisterResourceRequest.Types.CustomTimeouts
                {
                    Create = TimeoutString(options.CustomTimeouts?.Create),
                    Delete = TimeoutString(options.CustomTimeouts?.Delete),
                    Update = TimeoutString(options.CustomTimeouts?.Update),
                },
            };

            if (customOpts != null)
                request.AdditionalSecretOutputs.AddRange(customOpts.AdditionalSecretOutputs);

            request.IgnoreChanges.AddRange(options.IgnoreChanges);

            return request;
        }

        private static string TimeoutString(TimeSpan? timeSpan)
        {
            if (timeSpan == null)
                return "";

            // This will eventually be parsed by go's ParseDuration function here:
            // https://github.com/pulumi/pulumi/blob/06d4dde8898b2a0de2c3c7ff8e45f97495b89d82/pkg/resource/deploy/source_eval.go#L967
            //
            // So we generate a legal duration as allowed by
            // https://golang.org/pkg/time/#ParseDuration.
            //
            // Simply put, we simply convert our ticks to the integral number of nanoseconds
            // corresponding to it.  Since each tick is 100ns, this can trivialy be done just by
            // appending "00" to it.
            return timeSpan.Value.Ticks.ToString() + "00ns";
        }
    }
}
