/*
 * Copyright 2016 Google Inc.
 *
 * 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.google.cloud.tools.eclipse.appengine.deploy;

import com.google.cloud.tools.eclipse.util.status.StatusUtil;
import com.google.common.base.Preconditions;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jst.server.core.IJ2EEModule;
import org.eclipse.jst.server.core.IUtilityModule;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.ServerUtil;
import org.eclipse.wst.server.core.model.IModuleResource;
import org.eclipse.wst.server.core.model.ModuleDelegate;
import org.eclipse.wst.server.core.util.ModuleFile;
import org.eclipse.wst.server.core.util.PublishUtil;

/**
 * Writes a WAR file of a project, or the exploded contents of it to a destination directory.
 */
public class WarPublisher {

  public static final Logger logger = Logger.getLogger(WarPublisher.class.getName());

  public static IStatus[] publishExploded(IProject project, IPath destination,
      IPath safeWorkDirectory, IProgressMonitor monitor) throws CoreException {
    Preconditions.checkNotNull(project, "project is null"); //$NON-NLS-1$
    Preconditions.checkNotNull(destination, "destination is null"); //$NON-NLS-1$
    Preconditions.checkArgument(!destination.isEmpty(), "destination is empty path"); //$NON-NLS-1$
    Preconditions.checkNotNull(safeWorkDirectory, "safeWorkDirectory is null"); //$NON-NLS-1$
    if (monitor.isCanceled()) {
      throw new OperationCanceledException();
    }

    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    subMonitor.setTaskName(Messages.getString("task.name.publish.war")); //$NON-NLS-1$

    IModuleResource[] resources =
        flattenResources(project, safeWorkDirectory, subMonitor.newChild(10));
    if (resources.length == 0) {
      IStatus error = StatusUtil.error(WarPublisher.class, project.getName()
          + " has no resources to publish"); //$NON-NLS-1$
      return new IStatus[] {error};
    }
    return PublishUtil.publishFull(resources, destination, subMonitor.newChild(90));
  }

  public static IStatus[] publishWar(IProject project, IPath destination, IPath safeWorkDirectory,
      IProgressMonitor monitor) throws CoreException {
    Preconditions.checkNotNull(project, "project is null"); //$NON-NLS-1$
    Preconditions.checkNotNull(destination, "destination is null"); //$NON-NLS-1$
    Preconditions.checkArgument(!destination.isEmpty(), "destination is empty path"); //$NON-NLS-1$
    Preconditions.checkNotNull(safeWorkDirectory, "safeWorkDirectory is null"); //$NON-NLS-1$
    if (monitor.isCanceled()) {
      throw new OperationCanceledException();
    }

    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    subMonitor.setTaskName(Messages.getString("task.name.publish.war")); //$NON-NLS-1$

    IModuleResource[] resources =
        flattenResources(project, safeWorkDirectory, subMonitor.newChild(10));
    if (resources.length == 0) {
      IStatus error = StatusUtil.error(WarPublisher.class, project.getName()
          + " has no resources to publish"); //$NON-NLS-1$
      return new IStatus[] {error};
    }
    return PublishUtil.publishZip(resources, destination, subMonitor.newChild(90));
  }

  private static IModuleResource[] flattenResources(IProject project, IPath safeWorkDirectory,
      IProgressMonitor monitor) throws CoreException {
    List<IModuleResource> resources = new ArrayList<>();

    IModule[] modules = ServerUtil.getModules(project);
    for (IModule module : modules) {
      ModuleDelegate delegate = (ModuleDelegate) module.loadAdapter(ModuleDelegate.class, monitor);
      if (delegate == null) {
        continue;
      }

      // module references can either be as members or child modules (http://eclip.se/467759)
      Collections.addAll(resources, delegate.members());

      // now handle web fragment child modules, if they exist
      for (IModule child : delegate.getChildModules()) {
        ModuleDelegate childDelegate = (ModuleDelegate)
            child.loadAdapter(ModuleDelegate.class, monitor);
        IJ2EEModule j2eeModule = (IJ2EEModule) child.loadAdapter(IJ2EEModule.class, monitor);
        IUtilityModule utilityModule =
            (IUtilityModule) child.loadAdapter(IUtilityModule.class, monitor);
        if (childDelegate == null || (j2eeModule == null && utilityModule == null)) {
          logger.log(Level.WARNING, "child modules other than J2EE module or utility" //$NON-NLS-1$
              + " module are not supported: module=" + child //$NON-NLS-1$
              + ", moduleType=" + child.getModuleType()); //$NON-NLS-1$
          continue;
        }

        // destination (relative to root), e.g., "WEB-INF/lib/spring-web-4.3.6.RELEASE.jar"
        IPath destination = new Path(delegate.getPath(child));
        String zipName = destination.lastSegment();  // to be created
        IPath zipParent = destination.removeLastSegments(1);

        boolean alreadyZip = j2eeModule != null && j2eeModule.isBinary()
            || utilityModule != null && utilityModule.isBinary();
        if (alreadyZip) {
          // per "isBinary()" Javadoc, "members()" should have a single resource.
          IModuleResource zipResource = childDelegate.members()[0];

          File javaIoFile = zipResource.getAdapter(File.class);
          IFile iFile = zipResource.getAdapter(IFile.class);

          if (javaIoFile != null) {
            resources.add(new ModuleFile(javaIoFile, zipName, zipParent));
          } else if (iFile != null) {
            resources.add(new ModuleFile(iFile, zipName, zipParent));
          }
        } else {
          IPath tempZip = safeWorkDirectory.append(zipName);
          PublishUtil.publishZip(childDelegate.members(), tempZip, monitor);
          resources.add(new ModuleFile(tempZip.toFile(), destination.lastSegment(), zipParent));
        }
      }
    }
    return resources.toArray(new IModuleResource[0]);
  }
}