/*
 * Copyright 2009 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.template.soy.base.internal;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.error.SoyCompilationException;
import com.google.template.soy.error.SoyError;
import com.google.template.soy.error.SoyErrorKind;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * Record for one input Soy file.
 *
 * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
 *
 * <p>TODO(lukes): This should either be a subtype of CharSource or hold a CharSource
 *
 */
public interface SoyFileSupplier {


  /**
   * An opaque identifier that can be compared for equality with other versions from the same
   * resource.
   * <p>
   * Instances are not {@code Comparable} since a version is not necessarily monotonic ; e.g. a
   * cryptographically strong hash function produces a more reliable version identifier than a
   * time-stamp but not one that can be said to be newer or older than any other version.
   */
  interface Version {

    /**
     * Compares to versions that are equivalent.  Meaningless if applied to versions from a
     * different resource.
     */
    @Override boolean equals(Object o);


    /** A version for stable resources : resources that don't change over the life of a JVM. */
    Version STABLE_VERSION = new Version() {};

  }


  /**
   * Returns a {@link Reader} for the Soy file content.
   *
   * @throws IOException If there is an error opening the input.
   */
  Reader open() throws IOException;


  /**
   * True if the underlying resource has changed since the given version.
   */
  boolean hasChangedSince(Version version);


  /**
   * Returns the kind of this input Soy file.
   */
  SoyFileKind getSoyFileKind();


  /**
   * Returns the path to the Soy file, used for as a unique map/set key and for messages.
   */
  String getFilePath();

  /**
   * Returns the version of the Soy file read.
   */
  Version getVersion();

  /**
   * Container for factory methods for {@link SoyFileSupplier}s.
   *
   * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
   */
  final class Factory {

    private static final SoyErrorKind FILE_URL_SYNTAX =
        SoyErrorKind.of("file URL has invalid syntax: ''{0}''");

    /**
     * Creates a new {@code SoyFileSupplier} given a {@code CharSource} for the file content,
     * as well as the desired file path for messages.
     *
     * @param contentSource Source for the Soy file content.
     * @param soyFileKind The kind of this input Soy file.
     * @param filePath The path to the Soy file, used for as a unique map/set key and for messages.
     */
    public static SoyFileSupplier create(
        CharSource contentSource, SoyFileKind soyFileKind, String filePath) {
      return new StableSoyFileSupplier(contentSource, soyFileKind, filePath);
    }


    /**
     * Creates a new {@code SoyFileSupplier} given a {@code File}.
     *
     * @param inputFile The Soy file.
     * @param soyFileKind The kind of this input Soy file.
     */
    public static SoyFileSupplier create(File inputFile, SoyFileKind soyFileKind) {
      return create(
          Files.asCharSource(inputFile, UTF_8), soyFileKind, inputFile.getPath());
    }


    /**
     * Creates a new {@code SoyFileSupplier} given a resource {@code URL}, as well as the desired
     * file path for messages.
     *
     * @param inputFileUrl The URL of the Soy file.
     * @param soyFileKind The kind of this input Soy file.
     * @param filePath The path to the Soy file, used for as a unique map/set key and for messages.
     */
    public static SoyFileSupplier create(
        URL inputFileUrl, SoyFileKind soyFileKind, String filePath) {
      if (inputFileUrl.getProtocol().equals("file")) {
        // If the URL corresponds to a local file (such as a resource during local development),
        // open it up as a volatile file, so we can account for changes to the file.
        URI inputFileUri;
        try {
          inputFileUri = inputFileUrl.toURI();
        } catch (URISyntaxException ex) {
          SoyError e = SoyError.DEFAULT_FACTORY.create(
              new SourceLocation(inputFileUrl.toString()),
              FILE_URL_SYNTAX, inputFileUrl);
          SoyCompilationException sce = new SoyCompilationException(
              ImmutableList.of(e));
          sce.initCause(ex);
          throw sce;
        }
        return new VolatileSoyFileSupplier(new File(inputFileUri), soyFileKind);
      } else {
        return create(
            Resources.asCharSource(inputFileUrl, UTF_8), soyFileKind, filePath);
      }
    }


    /**
     * Creates a new {@code SoyFileSupplier} given a resource {@code URL}.
     *
     * <p> Important: This function assumes that the desired file path is returned by
     * {@code inputFileUrl.toString()}. If this is not the case, please use
     * {@link #create(java.net.URL, SoyFileKind, String)} instead.
     *
     * @see #create(java.net.URL, SoyFileKind, String)
     * @param inputFileUrl The URL of the Soy file.
     * @param soyFileKind The kind of this input Soy file.
     */
    public static SoyFileSupplier create(URL inputFileUrl, SoyFileKind soyFileKind) {
      return create(inputFileUrl, soyFileKind, inputFileUrl.toString());
    }


    /**
     * Creates a new {@code SoyFileSupplier} given the file content provided as a string, as well
     * as the desired file path for messages.
     *
     * @param content The Soy file content.
     * @param soyFileKind The kind of this input Soy file.
     * @param filePath The path to the Soy file, used for as a unique map/set key and for messages.
     */
    public static SoyFileSupplier create(
        CharSequence content, SoyFileKind soyFileKind, String filePath) {
      // TODO(cgdecker): Use CharSource.wrap once the Guava version is updated
      final String contentString = content.toString();
      CharSource source = new CharSource() {
        @Override
        public Reader openStream() {
          return new StringReader(contentString);
        }
      };
      return create(source, soyFileKind, filePath);
    }


    private Factory() {
      // Not instantiable.
    }

  }

}
