<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- ============================================================ -->
  <!-- Build targets                                                -->
  <!-- ============================================================ -->
  <!-- 
  Is run once after build, checks if the project's nuget package's .nuspec depends on Baseclass.Contrib.Nuget.Output and copies everything which is in the
  output folder to the builds OutDir.
  -->
  <Target Name="CopyToOutput" AfterTargets="Compile" DependsOnTargets="CollectNugetOutputFiles">
    <Copy SourceFiles="@(NugetOutputFiles)" DestinationFiles="@(NugetOutputFiles->'$(OutDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
  </Target>

  <!-- 
  Is run once after clean, delete all files that match files collected by the CollectNugetOutputFiles task.
  -->
  <Target Name="CleanOutput" AfterTargets="Clean" DependsOnTargets="CollectNugetOutputFiles">
    <Delete Files="@(NugetOutputFiles->'$(OutDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
  </Target>

  <!--
  Is run once per file which has been deleted by the CleanOutput target, if the containing folder is empty it gets deleted.
  -->
  <Target Name="CleanEmptyFolder" Inputs="@(NugetOutputFiles)" Outputs="%(Identity).Dummy" AfterTargets="CleanOutput">
    <ItemGroup>
      <EmptyCheck Include="$(OutDir)%(NugetOutputFiles.RecursiveDir)**\*.*" />
    </ItemGroup>

    <RemoveDir Condition="'@(EmptyCheck)' == '' And '%(NugetOutputFiles.RecursiveDir)' != ''" Directories="$(OutDir)%(NugetOutputFiles.RecursiveDir)" />
  </Target>

  <!-- ============================================================ -->
  <!-- Web publication targets                                      -->
  <!-- ============================================================ -->
  
  <PropertyGroup>
    <PipelineCollectFilesPhaseDependsOn>
    $(PipelineCollectFilesPhaseDependsOn);
    CollectNugetPackageFiles;
    </PipelineCollectFilesPhaseDependsOn>
  </PropertyGroup>

  <!--
  Is run once during the PipelineCollectFilesPhase to fill FilesForPackagingFromProject with the files from NugetOutputFiles
  -->
  <Target Name="CollectNugetPackageFiles" DependsOnTargets="CollectNugetOutputFiles">
    <ItemGroup>
      <FilesForPackagingFromProject Include="@(NugetOutputFiles)" >
        <DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
      </FilesForPackagingFromProject>
    </ItemGroup>
  </Target>

  <!-- ============================================================ -->
  <!-- Common targets                                               -->
  <!-- ============================================================ -->
  
  <ItemGroup>
    <NupkgFiles Include="$(MSBuildThisFileDirectory)\..\..\..\*\*.nupkg"/>
  </ItemGroup>

  <ItemGroup>
    <PackageFiles Include="$(MSBuildProjectDirectory)\packages.config"/>
  </ItemGroup>
  
  <!--
  Is run once to compute the NugetOutputFiles item from the NuGet dependencies of the project
  -->
  <Target Name="CollectNugetOutputFiles">
    <Message Text="Collecting NuGet output files for $(MSBuildProjectName):" />
    
    <PackageFilter PackageConfigs="@(PackageFiles)" NugetPackages="@(NupkgFiles)">
      <Output ItemName="FilteredNugetPackages" TaskParameter="Result" />
    </PackageFilter>

    <Message Text="@(FilteredNugetPackages->'%(RecursiveDir)%(Filename)%(Extension)', '%0D%0A')" />
    <Message Text="Total filtered packages : @(FilteredNugetPackages->Count())" /> 
    
    <ItemGroup>
      <NugetOutputFiles Include="%(FilteredNugetPackages.RootDir)%(FilteredNugetPackages.Directory)\output\**\*.*" />
    </ItemGroup>
    
    <Message Text="@(NugetOutputFiles->'%(RecursiveDir)%(Filename)%(Extension)', '%0D%0A')" />
    <Message Text="Total files collected : @(NugetOutputFiles->Count())" /> 
  </Target>

  <!--
  Filter the NugetPackages list to only include nuget packages referenced in the PackageConfigs list which depend on Baseclass.Contrib.Nuget.Output
  -->
  <UsingTask TaskName="PackageFilter" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <PackageConfigs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
      <NugetPackages ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
      <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
    </ParameterGroup>
    <Task>        
      <Reference Include="System.Xml"/>
      <Reference Include="System.Xml.Linq"/>
      <Reference Include="WindowsBase" />
      <Using Namespace="System" />
      <Using Namespace="System.Collections.Generic" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.IO.Packaging" />
      <Using Namespace="System.Linq" />
      <Using Namespace="System.Threading" />
      <Using Namespace="System.Xml" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[                
            var usedPackages = new HashSet<string>(); // packaged used by the project
            try
            {                
                foreach (var packageConfig in PackageConfigs)
                {
                    var path = packageConfig.GetMetadata("FullPath");
                    var xml = new XmlDocument();
                    xml.LoadXml(File.ReadAllText(path));
                    var deps = xml.GetElementsByTagName("package");
                    foreach (XmlNode dep in deps)
                    {
                        if (dep.Attributes == null)
                        {
                            continue;
                        }
                        var id = dep.Attributes.GetNamedItem("id").Value;
                        if ("Baseclass.Contrib.Nuget.Output".Equals(id))
                        {
                            continue;
                        }
                        var version = dep.Attributes.GetNamedItem("version").Value;
                        var s = string.Format("{0}.{1}", id, version);
                        usedPackages.Add(s);
                    }
                }
            }
            catch (Exception e)
            {
                Log.LogError("Failed to load package files: {0}", e.Message);
                return false;
            }

            var usedNugetPackages = new List<ITaskItem>(); // list of nuget packages used by the project
            try
            {
                foreach (var nugetPackage in NugetPackages)
                {                    
                    var path = nugetPackage.GetMetadata("FullPath");
                    var parts = path.Split(Path.DirectorySeparatorChar);
                    usedNugetPackages.AddRange(from part in parts where usedPackages.Contains(part) select nugetPackage);
                }
            }
            catch (Exception e)
            {
                Log.LogError("Failed to filter nuget specs: {0}", e.Message);
                return false;
            }

            var result = new List<ITaskItem>(); // list of nuget packages used by the project that depends on Baseclass.Contrib.Nuget.Output
            foreach (var nugetPackage in usedNugetPackages)
            {
                var path = nugetPackage.GetMetadata("FullPath");
                var nupkgpath = Path.GetDirectoryName(path);

                using (var archive = Package.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    var nuspec = archive.GetParts().Single(part => part.Uri.ToString().EndsWith(".nuspec"));
                    var nugetSpec = Path.Combine(nupkgpath, Path.GetFileName(nuspec.Uri.ToString()));

                    // use a mutex to ensure that only one process unzip the nuspec
                    // and that one process do not start reading it due to its existence while another one is still writing it.
                    Mutex mut = new Mutex(false, "UnzipNuSpec");
                    try
                    {
                      mut.WaitOne();
                      
                      if (!File.Exists(nugetSpec))
                      {
                          // unpack the nuget spec file from the package
                          try
                          {
                              using (var outputstream = new FileStream(nugetSpec, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
                              {
                                  using (var nspecstream = nuspec.GetStream())
                                  {
                                      nspecstream.CopyTo(outputstream);
                                  }
                              }
                          }
                          catch (IOException)
                          {
                              if (!File.Exists(nugetSpec))
                              {
                                  throw;
                              }
                          }
                      }
                    }
                    finally
                    {
                      mut.ReleaseMutex();
                    }

                    var xml = new XmlDocument();
                    xml.LoadXml(File.ReadAllText(nugetSpec));
                    var deps = xml.GetElementsByTagName("dependency");
                    foreach (XmlNode dep in deps)
                    {
                        if (dep.Attributes == null)
                        {
                            continue;
                        }
                        var id = dep.Attributes.GetNamedItem("id").Value;
                        if ("Baseclass.Contrib.Nuget.Output".Equals(id))
                        {
                            result.Add(new TaskItem(nugetPackage));
                            break;
                        }
                    }
                }
            }
            Result = result.ToArray();
            return true;
  ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>