/*
 * Copyright 2008 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.gwt.core.ext.linker;

import com.google.gwt.core.ext.Linker;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.util.Util;
import com.google.gwt.util.tools.Utility;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * An artifact that will be emitted into the output. All EmittedArtifacts
 * contained in the {@link ArtifactSet} at the end of the Linking process will
 * be emitted by the compiler into the module's output directory. This type may
 * be extended by Linker providers to provide alternative implementations of
 * {@link #getContents(TreeLogger)}.
 *
 * TODO(bobv): provide a timestamp so we can make the time on output files match
 * that of input files?
 */
public abstract class EmittedArtifact extends Artifact<EmittedArtifact> {

  /**
   * Describes the visibility of an artifact.
   */
  public enum Visibility {

    /**
     * A public artifact is something that may be served to clients.
     */
    Public,

    /**
     * A private artifact is something that is only used during the build
     * process.
     */
    Private {
      @Override
      public boolean matches(Visibility visibility) {
        switch (visibility) {
          case LegacyDeploy:
          case Private:
            return true;
          default:
            return false;
        }
      }
    },

    /**
     * A deploy artifact is deployed to the server but is never served to the
     * client.
     */
    Deploy {
      @Override
      public boolean matches(Visibility visibility) {
        switch (visibility) {
          case Deploy:
          case LegacyDeploy:
            return true;
          default:
            return false;
        }
      }
    },

    /**
     * A Source artifact is a file intended for source-level debugging
     * in a browser. It should be served to clients while developing but
     * not in production unless the app is open source.
     */
    Source,

    /**
     * For legacy use only - used for artifacts that were previously marked as
     * private because they should not be delivered to the client, but actually
     * should be visible to the server.  These artifacts will now be treated as
     * both Private and Deploy, so that existing build tools that expect to find
     * them in the output directory for Private artifacts will find them.
     *
     * New code should use Deploy instead.
     */
    LegacyDeploy {
      @Override
      public boolean matches(Visibility visibility) {
        switch (visibility) {
          case Deploy:
          case LegacyDeploy:
          case Private:
            return true;
          default:
            return false;
        }
      }
    };

    /**
     * Returns true if this visibility matches the requested visibility level,
     * dealing with the fact that {@link #LegacyDeploy} matches both
     * {@link #Private} and {@link #Deploy}.
     *
     * @param visibility
     * @return true if this visibility matches the requested level
     */
    public boolean matches(Visibility visibility) {
      return this == visibility;
    }
  }

  private final String partialPath;

  /**
   * This is mutable because it has no effect on identity.
   */
  private Visibility visibility;

  protected EmittedArtifact(Class<? extends Linker> linker, String partialPath) {
    super(linker);
    assert partialPath != null;
    this.partialPath = partialPath;
    visibility = Visibility.Public;
  }

  /**
   * Provides access to the contents of the EmittedResource.
   */
  public abstract InputStream getContents(TreeLogger logger)
      throws UnableToCompleteException;

  /**
   * Returns the time, measured in milliseconds from the epoch, at which the
   * Artifact was last modified. This will be used to set the last-modified
   * timestamp on the files written to disk.
   * <p>
   * The default implementation always returns the current time. Subclasses
   * should override this method to provide a type-appropriate value.
   *
   * @return the time at which the Artifact was last modified
   */
  public long getLastModified() {
    return System.currentTimeMillis();
  }

  /**
   * Returns the partial path within the output directory of the
   * EmittedArtifact.
   */
  public final String getPartialPath() {
    return partialPath;
  }

  /**
   * @return the visibility
   */
  public Visibility getVisibility() {
    return visibility;
  }

  @Override
  public final int hashCode() {
    return getPartialPath().hashCode();
  }

  /**
   * @param visibility the visibility to set
   */
  public void setVisibility(Visibility visibility) {
    this.visibility = visibility;
  }

  @Override
  public String toString() {
    return getPartialPath();
  }

  /**
   * Provides access to the contents of the EmittedResource.
   */
  public void writeTo(TreeLogger logger, OutputStream out)
      throws UnableToCompleteException {
    InputStream in = null;
    try {
      in = new BufferedInputStream(getContents(logger));
      Util.copyNoClose(in, out);
    } catch (IOException e) {
      logger.log(TreeLogger.ERROR, "Unable to copy artifact: " + getPartialPath(), e);
      throw new UnableToCompleteException();
    } finally {
      Utility.close(in);
    }
  }

  @Override
  protected final int compareToComparableArtifact(EmittedArtifact o) {
    return getPartialPath().compareTo(o.getPartialPath());
  }

  @Override
  protected final Class<EmittedArtifact> getComparableArtifactType() {
    return EmittedArtifact.class;
  }
}
