using Capnp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

[assembly: InternalsVisibleTo("CapnpC.CSharp.Generator.Tests")]

namespace CapnpC.CSharp.Generator
{
    /// <summary>
    /// Provides methods for controlling both the C# code generator backend and the frontend "capnpc"
    /// </summary>
    public static class CapnpCompilation
    {
        /// <summary>
        /// Returns the basename of the capnp executable
        /// </summary>
        public static string CapnpCompilerFilename
        {
            get => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "capnp.exe" : "capnp";
        }

        /// <summary>
        /// Generates C# code from given input stream
        /// </summary>
        /// <param name="input">input stream containing the binary code generation request, which the frontend capnpc emits</param>
        /// <returns>generation result</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="input"/> is null</exception>
        public static GenerationResult GenerateFromStream(Stream input)
        {
            if (input == null)
                throw new ArgumentNullException(nameof(input));

            try
            {
                var segments = Framing.ReadSegments(input);
                var dec = DeserializerState.CreateRoot(segments);
                var reader = Schema.CodeGeneratorRequest.Reader.Create(dec);
                var model = Model.SchemaModel.Create(reader);
                var codeGen = new CodeGen.CodeGenerator(model, new CodeGen.GeneratorOptions());
                return new GenerationResult(codeGen.Generate());
            }
            catch (Exception exception)
            {
                return new GenerationResult(exception);
            }
        }

        /// <summary>
        /// Invokes "capnp.exe -o-" with given additional arguments and redirects the output to the C# generator backend.
        /// </summary>
        /// <param name="arguments">additional command line arguments</param>
        /// <param name="workingDirectory">optional working directory</param>
        /// <returns>generation result</returns>
        /// <exception cref="ArgumentNullException"><paramref name="arguments"/>is null</exception>
        public static GenerationResult InvokeCapnpAndGenerate(IEnumerable<string> arguments, string workingDirectory = null)
        {
            if (arguments == null)
                throw new ArgumentNullException(nameof(arguments));

            using (var compiler = new Process())
            {
                var argList = new List<string>();
                argList.Add("compile");
                argList.Add($"-o-");
                argList.AddRange(arguments);

                compiler.StartInfo.FileName = CapnpCompilerFilename;
                compiler.StartInfo.Arguments = string.Join(" ", argList);
                compiler.StartInfo.UseShellExecute = false;
                compiler.StartInfo.RedirectStandardOutput = true;
                compiler.StartInfo.RedirectStandardError = true;
                if (!string.IsNullOrWhiteSpace(workingDirectory))
                {
                    compiler.StartInfo.WorkingDirectory = workingDirectory;
                }

                try
                {
                    compiler.Start();
                }
                catch (Exception exception)
                {
                    return new GenerationResult(exception)
                    {
                        ErrorCategory = CapnpProcessFailure.NotFound
                    };
                }

                var result = GenerateFromStream(compiler.StandardOutput.BaseStream);

                var messageList = new List<CapnpMessage>();

                while (!compiler.StandardError.EndOfStream)
                {
                    messageList.Add(new CapnpMessage(compiler.StandardError.ReadLine()));
                }

                result.Messages = messageList;

                if (!result.IsSuccess)
                {
                    compiler.WaitForExit();
                    int exitCode = compiler.ExitCode;

                    if (exitCode == 0)
                        result.ErrorCategory = CapnpProcessFailure.BadOutput;
                    else
                        result.ErrorCategory = CapnpProcessFailure.BadInput;
                }

                return result;
            }
        }
    }
}