using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Zip;
using ILRepacking;
using Nuke.Common;
using Nuke.Common.Git;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.DotNet;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.DotNet.DotNetTasks;

class Build : NukeBuild
{
    public static int Main() => Execute<Build>(x => x.Results);

    [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
    readonly string Configuration = IsLocalBuild ? "Debug" : "Release";

    [Solution("LibHac.sln")] readonly Solution Solution;
    [GitRepository] readonly GitRepository GitRepository;

    AbsolutePath SourceDirectory => RootDirectory / "src";
    AbsolutePath TestsDirectory => RootDirectory / "tests";
    AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
    AbsolutePath TempDirectory => RootDirectory / ".tmp";
    AbsolutePath CliCoreDir => TempDirectory / "hactoolnet_netcoreapp2.1";
    AbsolutePath CliFrameworkDir => TempDirectory / "hactoolnet_net46";
    AbsolutePath CliFrameworkZip => ArtifactsDirectory / "hactoolnet.zip";
    AbsolutePath CliCoreZip => ArtifactsDirectory / "hactoolnet_netcore.zip";

    AbsolutePath CliMergedExe => ArtifactsDirectory / "hactoolnet.exe";

    Project LibHacProject => Solution.GetProject("LibHac").NotNull();
    Project LibHacTestProject => Solution.GetProject("LibHac.Tests").NotNull();
    Project HactoolnetProject => Solution.GetProject("hactoolnet").NotNull();

    Target Clean => _ => _
        .Executes(() =>
        {
            DeleteDirectories(GlobDirectories(SourceDirectory, "**/bin", "**/obj"));
            DeleteDirectories(GlobDirectories(TestsDirectory, "**/bin", "**/obj"));
            EnsureCleanDirectory(ArtifactsDirectory);
            EnsureCleanDirectory(CliCoreDir);
            EnsureCleanDirectory(CliFrameworkDir);
        });

    Target Restore => _ => _
        .DependsOn(Clean)
        .Executes(() =>
        {
            DotNetRestoreSettings settings = new DotNetRestoreSettings()
                .SetProjectFile(Solution);

            if (EnvironmentInfo.IsUnix) settings = settings.RemoveRuntimes("net46");

            DotNetRestore(s => settings);
        });

    Target Compile => _ => _
        .DependsOn(Restore)
        .Executes(() =>
        {
            DotNetBuildSettings buildSettings = new DotNetBuildSettings()
                .SetProjectFile(Solution)
                .EnableNoRestore()
                .SetConfiguration(Configuration);

            if (EnvironmentInfo.IsUnix) buildSettings = buildSettings.SetFramework("netcoreapp2.1");

            DotNetBuild(s => buildSettings);

            DotNetPublishSettings publishSettings = new DotNetPublishSettings()
                .EnableNoRestore()
                .SetConfiguration(Configuration);

            DotNetPublish(s => publishSettings
                .SetProject(HactoolnetProject)
                .SetFramework("netcoreapp2.1")
                .SetOutput(CliCoreDir));

            if (EnvironmentInfo.IsWin)
            {
                DotNetPublish(s => publishSettings
                    .SetProject(HactoolnetProject)
                    .SetFramework("net46")
                    .SetOutput(CliFrameworkDir));
            }

            // Hack around OS newline differences
            if (EnvironmentInfo.IsUnix)
            {
                foreach (string filename in Directory.EnumerateFiles(CliCoreDir, "*.json"))
                {
                    ReplaceLineEndings(filename);
                }
            }
        });

    Target Pack => _ => _
        .DependsOn(Compile)
        .Executes(() =>
        {
            DotNetPackSettings settings = new DotNetPackSettings()
                .SetProject(LibHacProject)
                .EnableNoBuild()
                .SetConfiguration(Configuration)
                .EnableIncludeSymbols()
                .SetOutputDirectory(ArtifactsDirectory);

            if (EnvironmentInfo.IsUnix)
                settings = settings.SetProperties(
                    new Dictionary<string, object> { ["TargetFrameworks"] = "netcoreapp2.1" });

            DotNetPack(s => settings);

            if (Host != HostType.AppVeyor) return;

            foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory, "*.nupkg"))
            {
                PushArtifact(filename);
            }
        });

    Target Merge => _ => _
        .DependsOn(Compile)
        .OnlyWhen(() => EnvironmentInfo.IsWin)
        .Executes(() =>
        {
            string[] libraries = Directory.GetFiles(CliFrameworkDir, "*.dll");
            var cliList = new List<string> { CliFrameworkDir / "hactoolnet.exe" };
            cliList.AddRange(libraries);

            var cliOptions = new RepackOptions
            {
                OutputFile = CliMergedExe,
                InputAssemblies = cliList.ToArray(),
                SearchDirectories = new[] { "." }
            };

            new ILRepack(cliOptions).Repack();

            if (Host == HostType.AppVeyor)
            {
                PushArtifact(CliMergedExe);
            }
        });

    Target Test => _ => _
        .DependsOn(Compile)
        .Executes(() =>
        {
            DotNetTestSettings settings = new DotNetTestSettings()
                .SetProjectFile(LibHacTestProject)
                .EnableNoBuild()
                .SetConfiguration(Configuration);

            if (EnvironmentInfo.IsUnix) settings = settings.SetFramework("netcoreapp2.1");

            DotNetTest(s => settings);
        });

    Target Zip => _ => _
        .DependsOn(Pack)
        .Executes(() =>
        {
            string[] namesFx = Directory.EnumerateFiles(CliFrameworkDir, "*.exe")
                .Concat(Directory.EnumerateFiles(CliFrameworkDir, "*.dll"))
                .ToArray();

            string[] namesCore = Directory.EnumerateFiles(CliCoreDir, "*.json")
                .Concat(Directory.EnumerateFiles(CliCoreDir, "*.dll"))
                .ToArray();

            if (EnvironmentInfo.IsWin)
            {
                ZipFiles(CliFrameworkZip, namesFx);
                Console.WriteLine($"Created {CliFrameworkZip}");
            }

            ZipFiles(CliCoreZip, namesCore);
            Console.WriteLine($"Created {CliCoreZip}");

            if (Host == HostType.AppVeyor)
            {
                PushArtifact(CliFrameworkZip);
                PushArtifact(CliCoreZip);
                PushArtifact(CliMergedExe);
            }
        });

    Target Results => _ => _
        .DependsOn(Test, Zip, Merge)
        .Executes(() =>
        {
            Console.WriteLine("SHA-1:");
            using (SHA1 sha = SHA1.Create())
            {
                foreach (string filename in Directory.EnumerateFiles(ArtifactsDirectory))
                {
                    using (var stream = new FileStream(filename, FileMode.Open))
                    {
                        string hash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "");
                        Console.WriteLine($"{hash} - {Path.GetFileName(filename)}");
                    }
                }
            }
        });

    public static void ZipFiles(string outFile, string[] files)
    {
        using (var s = new ZipOutputStream(File.Create(outFile)))
        {
            s.SetLevel(9);

            foreach (string file in files)
            {
                var entry = new ZipEntry(Path.GetFileName(file));
                entry.DateTime = DateTime.UnixEpoch;

                using (FileStream fs = File.OpenRead(file))
                {
                    entry.Size = fs.Length;
                    s.PutNextEntry(entry);
                    fs.CopyTo(s);
                }
            }
        }
    }

    public static void PushArtifact(string path)
    {
        if (!File.Exists(path))
        {
            Console.WriteLine($"Unable to add artifact {path}");
        }

        var psi = new ProcessStartInfo
        {
            FileName = "appveyor",
            Arguments = $"PushArtifact \"{path}\"",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true
        };

        var proc = new Process
        {
            StartInfo = psi
        };

        proc.Start();

        proc.WaitForExit();

        Console.WriteLine($"Added AppVeyor artifact {path}");
    }

    public static void ReplaceLineEndings(string filename)
    {
        string text = File.ReadAllText(filename);
        File.WriteAllText(filename, text.Replace("\n", "\r\n"));
    }
}