// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using NuGet.Packaging.Core;
using NuGet.Packaging.PackageCreation.Resources;
using NuGet.Versioning;

namespace NuGet.Packaging
{
    public class ManifestMetadata : IPackageMetadata
    {
        private string _minClientVersionString;

        private IEnumerable<string> _authors = Enumerable.Empty<string>();
        private IEnumerable<string> _owners = Enumerable.Empty<string>();

        private string _iconUrl;
        private string _licenseUrl;
        private string _projectUrl;

        public ManifestMetadata()
        {
        }

        /// <summary>
        /// Constructs a ManifestMetadata instance from an IPackageMetadata instance
        /// </summary>
        public ManifestMetadata(IPackageMetadata copy)
        {
            Id = copy.Id?.Trim();
            Version = copy.Version;
            Title = copy.Title?.Trim();
            Authors = copy.Authors;
            Owners = copy.Owners;
            Tags = string.IsNullOrEmpty(copy.Tags) ? null : copy.Tags.Trim();
            _licenseUrl = copy.LicenseUrl?.OriginalString;
            _projectUrl = copy.ProjectUrl?.OriginalString;
            _iconUrl = copy.IconUrl?.OriginalString;
            RequireLicenseAcceptance = copy.RequireLicenseAcceptance;
            Description = copy.Description?.Trim();
            Copyright = copy.Copyright?.Trim();
            Summary = copy.Summary?.Trim();
            ReleaseNotes = copy.ReleaseNotes?.Trim();
            Language = copy.Language?.Trim();
            DependencyGroups = copy.DependencyGroups;
            FrameworkReferences = copy.FrameworkReferences;
            PackageAssemblyReferences = copy.PackageAssemblyReferences;
            MinClientVersionString = copy.MinClientVersion?.ToString();
            ContentFiles = copy.ContentFiles;
            DevelopmentDependency = copy.DevelopmentDependency;
        }

        [ManifestVersion(5)]
        public string MinClientVersionString
        {
            get { return _minClientVersionString; }
            set
            {
                Version version = null;
                if (!String.IsNullOrEmpty(value) && !System.Version.TryParse(value, out version))
                {
                    throw new InvalidDataException(NuGetResources.Manifest_InvalidMinClientVersion);
                }

                _minClientVersionString = value;
                MinClientVersion = version;
            }
        }

        public Version MinClientVersion { get; private set; }

        public string Id { get; set; }

        public NuGetVersion Version { get; set; }

        public string Title { get; set; }

        public IEnumerable<string> Authors
        {
            get { return _authors; }
            set { _authors = value ?? Enumerable.Empty<string>(); }
        }

        public IEnumerable<string> Owners
        {
            get { return (_owners == null || !_owners.Any()) ? _authors : _owners; }
            set { _owners = value ?? Enumerable.Empty<string>(); }
        }

        // The (Icon/License/Project)Url properties have backing strings as we need to be able to differentiate
        //   between the property not being set (valid) and set to an empty value (invalid). 
        public void SetIconUrl(string iconUrl)
        {
            _iconUrl = iconUrl;
        }

        public Uri IconUrl
        {
            get
            {
                if (_iconUrl == null)
                {
                    return null;
                }

                return new Uri(_iconUrl);
            }
        }

        public void SetLicenseUrl(string licenseUrl)
        {
            _licenseUrl = licenseUrl;
        }

        public Uri LicenseUrl
        {
            get
            {
                if (_licenseUrl == null)
                {
                    return null;
                }

                return new Uri(_licenseUrl);
            }
        }

        public void SetProjectUrl(string projectUrl)
        {
            _projectUrl = projectUrl;
        }

        public Uri ProjectUrl
        {
            get
            {
                if (_projectUrl == null)
                {
                    return null;
                }

                return new Uri(_projectUrl);
            }
        }

        public bool RequireLicenseAcceptance { get; set; }

        public bool DevelopmentDependency { get; set; }

        public string Description { get; set; }

        public string Summary { get; set; }

        [ManifestVersion(2)]
        public string ReleaseNotes { get; set; }

        [ManifestVersion(2)]
        public string Copyright { get; set; }

        public string Language { get; set; }

        public string Tags { get; set; }

        private IEnumerable<PackageDependencyGroup> _dependencyGroups = new List<PackageDependencyGroup>();
        public IEnumerable<PackageDependencyGroup> DependencyGroups
        {
            get
            {
                return _dependencyGroups;
            }
            set
            {
                _dependencyGroups = MergeDependencyGroups(value);
            }
        }

        public IEnumerable<FrameworkAssemblyReference> FrameworkReferences { get; set; } = new List<FrameworkAssemblyReference>();

        private IEnumerable<PackageReferenceSet> _packageAssemblyReferences = new List<PackageReferenceSet>();

        [ManifestVersion(2)]
        public IEnumerable<PackageReferenceSet> PackageAssemblyReferences
        {
            get
            {
                return _packageAssemblyReferences;
            }

            set
            {
                _packageAssemblyReferences = MergePackageAssemblyReferences(value);
            }
        }

        private static IEnumerable<PackageReferenceSet> MergePackageAssemblyReferences(IEnumerable<PackageReferenceSet> referenceSets)
        {
            if (referenceSets == null)
            {
                Enumerable.Empty<PackageReferenceSet>();
            }

            var referenceSetGroups = referenceSets.GroupBy(set => set.TargetFramework);
            var groupedReferenceSets = referenceSetGroups.Select(group => new PackageReferenceSet(group.Key, group.SelectMany(g => g.References)))
                                                            .ToList();

            int nullTargetFrameworkIndex = groupedReferenceSets.FindIndex(set => set.TargetFramework == null);
            if (nullTargetFrameworkIndex > -1)
            {
                var nullFxReferenceSet = groupedReferenceSets[nullTargetFrameworkIndex];
                groupedReferenceSets.RemoveAt(nullTargetFrameworkIndex);
                groupedReferenceSets.Insert(0, nullFxReferenceSet);
            }

            return groupedReferenceSets;
        }

        public ICollection<ManifestContentFiles> ContentFiles { get; set; } = new List<ManifestContentFiles>();

        private static IEnumerable<PackageDependencyGroup> MergeDependencyGroups(IEnumerable<PackageDependencyGroup> actualDependencyGroups)
        {
            if (actualDependencyGroups == null)
            {
                return Enumerable.Empty<PackageDependencyGroup>();
            }

            var dependencyGroups = actualDependencyGroups.Select(CreatePackageDependencyGroup);

            // group the dependency sets with the same target framework together.
            var dependencySetGroups = dependencyGroups.GroupBy(set => set.TargetFramework);
            var groupedDependencySets = dependencySetGroups.Select(group => new PackageDependencyGroup(group.Key, group.SelectMany(g => g.Packages)))
                                                            .ToList();
            // move the group with the any target framework (if any) to the front just for nicer display in UI
            int anyTargetFrameworkIndex = groupedDependencySets.FindIndex(set => set.TargetFramework.IsAny);
            if (anyTargetFrameworkIndex > -1)
            {
                var anyFxDependencySet = groupedDependencySets[anyTargetFrameworkIndex];
                groupedDependencySets.RemoveAt(anyTargetFrameworkIndex);
                groupedDependencySets.Insert(0, anyFxDependencySet);
            }

            return groupedDependencySets;
        }

        private static PackageDependencyGroup CreatePackageDependencyGroup(PackageDependencyGroup dependencyGroup)
        {
            IEnumerable<PackageDependency> dependencies;

            if (dependencyGroup.Packages == null)
            {
                dependencies = Enumerable.Empty<PackageDependency>();
            }
            else
            {
                dependencies = dependencyGroup.Packages.Select(dependency =>
                    new PackageDependency(
                        dependency.Id.SafeTrim(),
                        dependency.VersionRange,
                        dependency.Include,
                        dependency.Exclude)).ToList();
            }

            return new PackageDependencyGroup(dependencyGroup.TargetFramework, dependencies);
        }

        public IEnumerable<string> Validate()
        {
            if (String.IsNullOrEmpty(Id))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Id");
            }
            else
            {
                if (Id.Length > PackageIdValidator.MaxPackageIdLength)
                {
                    yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_IdMaxLengthExceeded);
                }
                else if (!PackageIdValidator.IsValidPackageId(Id))
                {
                    yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.InvalidPackageId, Id);
                }
            }

            if (Version == null)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Version");
            }

            if (Authors == null || !Authors.Any(author => !String.IsNullOrEmpty(author)))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Authors");
            }

            if (String.IsNullOrEmpty(Description))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Description");
            }

            if (_licenseUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "LicenseUrl");
            }

            if (_iconUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "IconUrl");
            }

            if (_projectUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "ProjectUrl");
            }

            if (RequireLicenseAcceptance && String.IsNullOrWhiteSpace(_licenseUrl))
            {
                yield return NuGetResources.Manifest_RequireLicenseAcceptanceRequiresLicenseUrl;
            }
        }
    }
}