using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;

public class Packages
{
    public List<NuGetPackSettings> NuspecNuGetSettings { get; private set; }
    public FilePath[] NugetPackages { get; private set; }
    public FilePath[] BinFiles { get; private set; }
    public string NugetPackagesDir {get; private set;}
    public string SkiaSharpVersion {get; private set; }
    public string SkiaSharpLinuxVersion {get; private set; }
    public Dictionary<string, IList<Tuple<string,string>>> PackageVersions{get; private set;}

    class DependencyBuilder : List<NuSpecDependency>
    {
        Packages _parent;

        public DependencyBuilder(Packages parent)
        {
            _parent = parent;
        }
        
        string GetVersion(string name)
        {
            return _parent.PackageVersions[name].First().Item1;
        }

        public DependencyBuilder Dep(string name, params string[] fws)
        {
            if(fws.Length == 0)
                Add(new NuSpecDependency() { Id = name, Version = GetVersion(name) });
            foreach(var fw in fws)
                Add(new NuSpecDependency() { Id = name, TargetFramework = fw, Version = GetVersion(name) });
            return this;
        }
        public DependencyBuilder Deps(string[] fws, params string[] deps)
        {
            foreach(var fw in fws)
                foreach(var name in deps)
                    Add(new NuSpecDependency() { Id = name, TargetFramework = fw, Version = GetVersion(name) });
            return this;
        }
    }
        
    public Packages(ICakeContext context, Parameters parameters)
    {
        // NUGET NUSPECS
        context.Information("Getting git modules:");

        var ignoredSubModulesPaths = System.IO.File.ReadAllLines(".git/config").Where(m=>m.StartsWith("[submodule ")).Select(m => 
        {
            var path = m.Split(' ')[1].Trim("\"[] \t".ToArray());
            context.Information(path);
            return ((DirectoryPath)context.Directory(path)).FullPath;
        }).ToList();

        var normalizePath = new Func<string, string>(
            path => path.Replace(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar).ToUpperInvariant());

        // Key: Package Id
        // Value is Tuple where Item1: Package Version, Item2: The *.csproj/*.props file path.
        var packageVersions = new Dictionary<string, IList<Tuple<string,string>>>();
        PackageVersions = packageVersions;
        System.IO.Directory.EnumerateFiles(((DirectoryPath)context.Directory("./build")).FullPath,
            "*.props", SearchOption.AllDirectories).ToList().ForEach(fileName =>
        {
            if (!ignoredSubModulesPaths.Any(i => normalizePath(fileName).Contains(normalizePath(i))))
            {
                var xdoc = XDocument.Load(fileName);
                foreach (var reference in xdoc.Descendants().Where(x => x.Name.LocalName == "PackageReference"))
                {
                    var name = reference.Attribute("Include").Value;
                    var versionAttribute = reference.Attribute("Version");
                    var version = versionAttribute != null 
                        ? versionAttribute.Value 
                        : reference.Elements().First(x=>x.Name.LocalName == "Version").Value;
                    IList<Tuple<string, string>> versions;
                    packageVersions.TryGetValue(name, out versions);
                    if (versions == null)
                    {
                        versions = new List<Tuple<string, string>>();
                        packageVersions[name] = versions;
                    }
                    versions.Add(Tuple.Create(version, fileName));
                }
            }
        });

        context.Information("Checking installed NuGet package dependencies versions:");

        packageVersions.ToList().ForEach(package =>
        {
            var packageVersion = package.Value.First().Item1;
            bool isValidVersion = package.Value.All(x => x.Item1 == packageVersion);
            if (!isValidVersion)
            {
                context.Information("Error: package {0} has multiple versions installed:", package.Key);
                foreach (var v in package.Value)
                {
                    context.Information("{0}, file: {1}", v.Item1, v.Item2);
                }
                throw new Exception("Detected multiple NuGet package version installed for different projects.");
            }
        });

        context.Information("Setting NuGet package dependencies versions:");

        var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
        var SerilogSinksDebugVersion = packageVersions["Serilog.Sinks.Debug"].FirstOrDefault().Item1;
        var SerilogSinksTraceVersion = packageVersions["Serilog.Sinks.Trace"].FirstOrDefault().Item1;
        var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
        var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
        var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
        SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
		SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
        var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
        var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
        var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
        var SharpDXDirect3D9Version = packageVersions["SharpDX.Direct3D9"].FirstOrDefault().Item1;
        var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
        var SystemMemoryVersion = packageVersions["System.Memory"].FirstOrDefault().Item1;

        context.Information("Package: Serilog, version: {0}", SerilogVersion);
        context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
        context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
        context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
        context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
        context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
        context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
        context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
        context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
        context.Information("Package: SharpDX.Direct3D9, version: {0}", SharpDXDirect3D9Version);
        context.Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion);
        context.Information("Package: System.Memory, version: {0}", SystemMemoryVersion);

        var nugetPackagesDir = System.Environment.GetEnvironmentVariable("NUGET_HOME")
            ?? System.IO.Path.Combine(System.Environment.GetEnvironmentVariable("USERPROFILE") ?? System.Environment.GetEnvironmentVariable("HOME"), ".nuget");
        
        NugetPackagesDir = System.IO.Path.Combine(nugetPackagesDir, "packages");
        
        var SetNuGetNuspecCommonProperties = new Action<NuGetPackSettings> ((nuspec) => {
            nuspec.Version = parameters.Version;
            nuspec.Authors = new [] { "Avalonia Team" };
            nuspec.Owners = new [] { "stevenk" };
            nuspec.LicenseUrl = new Uri("http://opensource.org/licenses/MIT");
            nuspec.ProjectUrl = new Uri("https://github.com/AvaloniaUI/Avalonia/");
            nuspec.RequireLicenseAcceptance = false;
            nuspec.Symbols = false;
            nuspec.NoPackageAnalysis = true;
            nuspec.Description = "The Avalonia UI framework";
            nuspec.Copyright = "Copyright 2015";
            nuspec.Tags = new [] { "Avalonia" };
        });

        var coreLibraries = new string[][]
        {
            new [] { "./src/", "Avalonia.Animation"},
            new [] { "./src/", "Avalonia.Base"},
            new [] { "./src/", "Avalonia.Controls"},
            new [] { "./src/", "Avalonia.DesignerSupport"},
            new [] { "./src/", "Avalonia.Diagnostics"},
            new [] { "./src/", "Avalonia.Input"},
            new [] { "./src/", "Avalonia.Interactivity"},
            new [] { "./src/", "Avalonia.Layout"},
            new [] { "./src/", "Avalonia.Logging.Serilog"},
            new [] { "./src/", "Avalonia.Visuals"},
            new [] { "./src/", "Avalonia.Styling"},
            new [] { "./src/", "Avalonia.OpenGL"},
            new [] { "./src/", "Avalonia.Themes.Default"},
            new [] { "./src/Markup/", "Avalonia.Markup"},
            new [] { "./src/Markup/", "Avalonia.Markup.Xaml"},
        };

        var extensionsToPack = new [] {".dll", ".xml", ".pdb"};

        var coreLibrariesFiles = coreLibraries
            .SelectMany(lib => extensionsToPack.Select(ext => new {lib, ext}))
            .Select((lib) => {
                return (FilePath)context.File(lib.lib[0] + lib.lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard2.0/" + lib.lib[1] + lib.ext);
        }).ToList();

        var coreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
            return new NuSpecContent { 
                Source = file.FullPath, Target = "lib/netstandard2.0" 
            };
        });

        var netFrameworkCoreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
            return new NuSpecContent { 
                Source = file.FullPath, Target = "lib/net461" 
            };
        });

        var netcoreappCoreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
            return new NuSpecContent { 
                Source = file.FullPath, Target = "lib/netcoreapp2.0" 
            };
        });

        var netFrameworkRuntimePlatform = extensionsToPack.Select(libSuffix => {
            return new NuSpecContent {
                Source = ((FilePath)context.File("./src/Avalonia.DotNetFrameworkRuntime/bin/" + parameters.DirSuffix + "/net461/Avalonia.DotNetFrameworkRuntime" + libSuffix)).FullPath, 
                Target = "lib/net461" 
            };
        });

        var netCoreRuntimePlatform = extensionsToPack.Select(libSuffix => {
            return new NuSpecContent {
                Source = ((FilePath)context.File("./src/Avalonia.DotNetCoreRuntime/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.DotNetCoreRuntime" + libSuffix)).FullPath, 
                Target = "lib/netcoreapp2.0" 
            };
        });

        var toolHostApp = new NuSpecContent{
            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, 
            Target = "tools/netcoreapp2.0/previewer"
        };

        var toolHostAppNetFx = new NuSpecContent{
            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/net461/Avalonia.Designer.HostApp.exe")).FullPath, 
            Target = "tools/net461/previewer"
        };

        var toolsContent = new[] { toolHostApp, toolHostAppNetFx };
        var coreFiles = coreLibrariesNuSpecContent
            .Concat(netFrameworkCoreLibrariesNuSpecContent).Concat(netFrameworkRuntimePlatform)
            .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
            .Concat(toolsContent)
            .ToList();

        var nuspecNuGetSettingsCore = new []
        {
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia",
                Dependencies = new DependencyBuilder(this)
                {
                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="netstandard2.0" },
                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="netcoreapp2.0" },
                    new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version, TargetFramework="net461" },
                    new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion, TargetFramework="net461" },
                    new NuSpecDependency() { Id = "System.ComponentModel.TypeConverter", Version = "4.3.0", TargetFramework="net461" },
                    new NuSpecDependency() { Id = "NETStandard.Library", Version = "2.0.0", TargetFramework="net461"}
                }
                .Deps(new string[]{"netstandard2.0", "netcoreapp2.0", "net461"},
                    "Serilog", "Serilog.Sinks.Debug", "Serilog.Sinks.Trace",
                    "System.Memory", "System.Reactive", "System.ComponentModel.Annotations")
                .ToArray(),
                Files = coreFiles,
                BasePath = context.Directory("./"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.ReactiveUI
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.ReactiveUI",
                Dependencies = new DependencyBuilder(this)
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                }.Deps(new string[] {null}, "reactiveui"),
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.ReactiveUI.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Avalonia.ReactiveUI/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Remote.Protocol
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Remote.Protocol",
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Remote.Protocol.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Avalonia.Remote.Protocol/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                OutputDirectory = parameters.NugetRoot
            },
        };

        var nuspecNuGetSettingsMobile = new []
        {
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Android
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Android",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Android.dll", Target = "lib/MonoAndroid10" }
                },
                BasePath = context.Directory("./src/Android/Avalonia.Android/bin/" + parameters.DirSuffix + "/monoandroid44/MonoAndroid44/"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.iOS
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.iOS",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.iOS.dll", Target = "lib/Xamarin.iOS10" }
                },
                BasePath = context.Directory("./src/iOS/Avalonia.iOS/bin/" + parameters.DirSuffix + "/xamarin.ios10/"),
                OutputDirectory = parameters.NugetRoot
            }
        };

        var nuspecNuGetSettingsDesktop = new []
        {
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Win32
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Win32",
                Dependencies = new DependencyBuilder(this)
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
                }.Deps(new string[]{null}, "System.Drawing.Common"),
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Windows"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Direct2D1
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Direct2D1",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "SharpDX", Version = SharpDXVersion },
                    new NuSpecDependency() { Id = "SharpDX.Direct2D1", Version = SharpDXDirect2D1Version },
                    new NuSpecDependency() { Id = "SharpDX.Direct3D11", Version = SharpDXDirect3D11Version },
                    new NuSpecDependency() { Id = "SharpDX.DXGI", Version = SharpDXDXGIVersion }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Gtk3
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Gtk3",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Gtk3.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Gtk/Avalonia.Gtk3/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Skia
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Skia",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version, TargetFramework="netcoreapp2.0" },
                    new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion, TargetFramework="netcoreapp2.0" },
                    new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion, TargetFramework="netcoreapp2.0" },
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version, TargetFramework="net461" },
                    new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion, TargetFramework="net461" },
                    new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion, TargetFramework="net461" }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.Skia.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
                OutputDirectory = parameters.NugetRoot
            },
            new NuGetPackSettings()
            {
                Id = "Avalonia.MonoMac",
                Dependencies = new DependencyBuilder(this)
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
                }.Dep("MonoMac.NetStandard").ToArray(),
                Files = new []
                {
                    new NuSpecContent { Source = "netstandard2.0/Avalonia.MonoMac.dll", Target = "lib/netstandard2.0" },
                },
                BasePath = context.Directory("./src/OSX/Avalonia.MonoMac/bin/" + parameters.DirSuffix),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.Desktop
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.Desktop",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Gtk3", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.MonoMac", Version = parameters.Version }
                },
                Files = new NuSpecContent[]
                {
                    new NuSpecContent { Source = "licence.md", Target = "" }
                },
                BasePath = context.Directory("./"),
                OutputDirectory = parameters.NugetRoot
            },
            ///////////////////////////////////////////////////////////////////////////////
            // Avalonia.LinuxFramebuffer
            ///////////////////////////////////////////////////////////////////////////////
            new NuGetPackSettings()
            {
                Id = "Avalonia.LinuxFramebuffer",
                Dependencies = new []
                {
                    new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
                    new NuSpecDependency() { Id = "Avalonia.Skia", Version = parameters.Version }
                },
                Files = new []
                {
                    new NuSpecContent { Source = "Avalonia.LinuxFramebuffer/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.LinuxFramebuffer.dll", Target = "lib/netstandard2.0" }
                },
                BasePath = context.Directory("./src/Linux/"),
                OutputDirectory = parameters.NugetRoot
            }
        };

        var nuspecNuGetSettingInterop = new NuGetPackSettings()
        {
            Id = "Avalonia.Win32.Interoperability",
            Dependencies = new []
            {
                new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
                new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
                new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
            },
            Files = new []
            {
                new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/net461/Avalonia.Win32.Interop.dll", Target = "lib/net461" }
            },
            BasePath = context.Directory("./src/Windows"),
            OutputDirectory = parameters.NugetRoot
        };

        NuspecNuGetSettings = new List<NuGetPackSettings>();

        NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore);
        NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop);

        if (parameters.IsRunningOnWindows) {
            NuspecNuGetSettings.Add(nuspecNuGetSettingInterop);
            NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
        }

        NuspecNuGetSettings.ForEach((nuspec) => SetNuGetNuspecCommonProperties(nuspec));

        NugetPackages = NuspecNuGetSettings.Select(nuspec => {
            return nuspec.OutputDirectory.CombineWithFilePath(string.Concat(nuspec.Id, ".", nuspec.Version, ".nupkg"));
        }).ToArray();

        BinFiles = NuspecNuGetSettings.SelectMany(nuspec => {
            return nuspec.Files.Select(file => {
                return ((DirectoryPath)nuspec.BasePath).CombineWithFilePath(file.Source);
            });
        }).GroupBy(f => f.FullPath).Select(g => g.First()).ToArray();
    }
}