// Copyright (C) 2015 Advanced Micro Devices, Inc.  All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.amd.gerrit.plugins.manifestsubscription;

import com.amd.gerrit.plugins.manifestsubscription.manifest.Include;
import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest;
import com.amd.gerrit.plugins.manifestsubscription.manifest.Project;
import com.amd.gerrit.plugins.manifestsubscription.manifest.RemoveProject;
import com.google.common.collect.Sets;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class CanonicalManifest {
  private Map<String, Manifest> manifests;

  public CanonicalManifest(VersionedManifests manifests) {
    this.manifests = manifests.getManifests();
  }

  public CanonicalManifest(Map<String, Manifest> manifests) {
    this.manifests = manifests;
  }

  Manifest getCanonicalManifest(String path) throws ManifestReadException {
    if (manifests.containsKey(path)) {
      Manifest manifest = (Manifest) manifests.get(path).clone();

      Manifest includedManifest;
      Path includedPath;
      Iterator<Include> i = manifest.getInclude().listIterator();
      String include;
      while (i.hasNext()) {
        include = i.next().getName();
        includedPath = Paths.get(include);
        i.remove();

        includedManifest = getCanonicalManifest(includedPath.normalize().toString());

        try {
          mergeManifestInto(includedManifest, manifest);
        } catch (Exception e) {
          throw new ManifestReadException(path);
        }

      }
      // Clear remove project after all include manifest is processed
      manifest.getRemoveProject().clear();

      return manifest;
    }

    throw new ManifestReadException(path);
  }

  private Manifest mergeManifestInto(Manifest inner, Manifest outer)
      throws Exception {
    if (outer.getDefault() != null && inner.getDefault() != null) {
      throw new Exception();
    }
    if (outer.getNotice() != null && inner.getNotice() != null) {
      throw new Exception();
    }
    if (outer.getManifestServer() != null && inner.getManifestServer() != null) {
      throw new Exception();
    }
    if (outer.getRepoHooks() != null && inner.getRepoHooks() != null) {
      throw new Exception();
    }

    //TODO add more check
    resolveRemoveProject(inner, outer);

    if (outer.getDefault() == null) {
      outer.setDefault(inner.getDefault());
    }
    if (outer.getNotice() == null) {
      outer.setNotice(inner.getNotice());
    }
    if (outer.getManifestServer() == null) {
      outer.setManifestServer(inner.getManifestServer());
    }
    if (outer.getRepoHooks() == null) {
      outer.setRepoHooks(inner.getRepoHooks());
    }


    //TODO name remote name duplication check
    outer.getRemote().addAll(inner.getRemote());

    outer.getProject().addAll(inner.getProject());
    outer.getExtendProject().addAll(inner.getExtendProject());

    return outer;
  }

  private void resolveRemoveProject(Manifest manifest, Set<String> toBeRemoved) {
    Project p;
    Iterator<Project> i = manifest.getProject().listIterator();
    while (i.hasNext()) {
      p = i.next();

      if (toBeRemoved.contains(p.getName())) {
        i.remove();
      }
    }
  }

  private void resolveRemoveProject(Manifest manifest, Manifest toRemove) {
    Set<String> removeProjects = Sets.newHashSet();

    RemoveProject rp;
    Iterator<RemoveProject> i = toRemove.getRemoveProject().listIterator();
    while (i.hasNext()) {
      rp = i.next();
      removeProjects.add(rp.getName());
    }

    if (removeProjects.size() > 0) {
      resolveRemoveProject(manifest, removeProjects);
    }
  }
}

@@ -2238,24 +2238,66 @@
 ).clear();%0A%0A
+      removeNotDefaultProject(manifest);%0A%0A
       return
@@ -4279,11 +4279,311 @@
    %7D%0A  %7D
+%0A%0A  private void removeNotDefaultProject(Manifest manifest) %7B%0A    Project p;%0A    Iterator%3CProject%3E i = manifest.getProject().listIterator();%0A    while (i.hasNext()) %7B%0A      p = i.next();%0A%0A      if (p.getGroups() != null && p.getGroups().contains(%22notdefault%22)) %7B%0A        i.remove();%0A      %7D%0A    %7D%0A  %7D
 %0A%7D%0A

Remove project in notdefault group from canonical manifest