/*
 * RiskScape™ Copyright New Zealand Institute for Earth Science Limited
 * (Earth Sciences New Zealand) is distributed for research purposes only
 * under the terms of AGPLv3.
 *
 * RiskScape™ Copyright 2026 New Zealand Institute for Earth Science
 * Limited (Earth Sciences New Zealand). All rights reserved. Source code
 * available under the AGPLv3.
 * 
 * This program is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU Affero General Public License as published by the Free
 *  Software Foundation, either version 3 of the License, or (at your option) any
 *  later version.
 * 
 * This program is distributed for RESEARCH PURPOSES ONLY, in the hope that it will
 * be useful for research and education initiatives.
 * 
 * If you are not a researcher, or you are a researcher who wishes to use this
 * program on terms other than AGPLv3 (including those who wish to restrict the
 * distribution of any source code created using this program), please contact:
 * https://riskscape.org.nz
 * 
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.  You should have received a copy
 * of the GNU Affero General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * By way of summary only, under the AGPLv3:
 *     • Permissions of this strongest copyleft license are conditioned
 *       on making available complete source code of licensed works and
 *       modifications, which include larger works using a licensed work,
 *       under the same license.
 *     • Copyright and license notices must be preserved.
 *     • Contributors provide an express grant of patent rights.
 *     • When a modified version is used to provide a service over a
 *       network, the complete source code of the modified version must be made
 *       available.
 */

package nz.org.riskscape.engine.resource;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.nio.file.Path;
import java.util.Optional;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import nz.org.riskscape.engine.RiskscapeIOException;
import nz.org.riskscape.problem.ResultOrProblems;

/**
 * Represents a file type resource in a way that allows where the resource is from to be decoupled from how it
 * is accessed.
 *
 * For example implementations may fetch remote resources and make the local copy available.
 *
 * # TODO
 *   - add some metadata methods, like size, checksum, cache metadata, anything else?
 *   - make it clearer whether the resource is open at this point or not, and whether streams need to be closed
 *
 */
public interface Resource {

  /**
   * Options that will only allow a remote file to be fetched if it can be done over a secure connection.
   */
  Options SECURE_OPTIONS = new Options();

  @Data
  class Options {
    // GL#1288 to clean up the RS_ALLOW_INSECURE quick fix
    public boolean allowInsecure = "true".equals(System.getenv("RS_ALLOW_INSECURE"));

    /**
     * A directory to use for storing any temporary files  that are created.
     *
     * Ideally this will be context specific. If not specified then the engine's temporary directory
     * will be used.
     */
    public Optional<Path> tempDirectory = Optional.empty();
  }

  /**
   * A resource that can not be opened but is still useful for identifying some content. Useful to use
   * as a resource for plugin added resources.
   */
  @RequiredArgsConstructor @EqualsAndHashCode
  class PseudoResource implements Resource {
    @Getter
    private final URI location;

    @Override
    public InputStream getContentStream() throws RiskscapeIOException {
      return NO_BYTES;
    }
  }

  /**
   * A URI for use with UNKNOWN
   */
  URI UNKNOWN_URI = URI.create("null:null");

  /**
   * An empty input stream for use with UNKNOWN
   */
  InputStream NO_BYTES = new InputStream() {

    @Override
    public int read() throws IOException {
      return -1;
    }
  };

  /**
   * Null alternative for a Resource - use this in place of null where there is no obvious resource to provide
   * as metadata.  Obviously this is not useful if the Resource is going to be open, it's meant as metadata
   * for assisting with messaging etc and clarity in places where no Resource exists.
   */
  Resource UNKNOWN = new PseudoResource(UNKNOWN_URI);

  /**
   * @return a {@link URI} identifying where this resource is located
   */
  URI getLocation();

  /**
   * Return an {@link InputStream} to the underlying resource.
   *
   * Each call should return a freshly created input stream.
   *
   * @return the input stream for the underlying resource.
   * @throws RiskscapeIOException
   *
   */
  InputStream getContentStream() throws RiskscapeIOException;

  /**
   * Return a {@link Reader} for the underlying resource.
   *
   * A convenience that wraps the result of {@link #getResourceStream() } in a {@link InputStreamReader}.
   *
   * @return reader for the underlying resource
   * @throws RiskscapeIOException
   */
  default Reader getContentReader() throws RiskscapeIOException {
    return new InputStreamReader(getContentStream());
  }

  /**
   * @return a String that is the resource's data read completely in to a string - don't use this on potentially large
   * resources, or you're going to run out of heap
   */
  default String getContentAsString() throws RiskscapeIOException {
    Reader reader = getContentReader();
    char[] strBuf = new char[1024];

    StringBuffer buffer = new StringBuffer();
    int read = 0;
    try {
      while ((read = reader.read(strBuf)) != -1) {
        buffer.append(strBuf, 0, read);
      }
    } catch (IOException e) {
      throw new RiskscapeIOException("Failed to read string from reader", e);
    }

    return buffer.toString();
  };

  default ResultOrProblems<Path> ensureLocal(Options secureOptions, String fileExtension) {
    return ResultOrProblems.failed(ResourceProblems.get().ensureLocalNotSupported(getLocation()));
  }

  /**
   * Returns a {@link Path} to a local representation of the resource if the resource loader supports
   * this and is allowable given the options. Otherwise problems preventing this operation from succeeding.
   * @throws RiskscapeIOException if IO errors occur whilst fetching resource
   */
  default ResultOrProblems<Path> ensureLocal(Options options) {
    return ensureLocal(options, null);
  };

  /**
   * Alternative to {@link #ensureLocal(nz.org.riskscape.engine.resource.Resource.Options, java.lang.String) }
   * that will only return the local path if the resource already exists there.
   *
   * @return the path to the resource on the local path or empty if the resource does not exist locally already
   */
  default Optional<Path> getLocal() {
    return Optional.empty();
  }

  /**
   * @return a media type that represents this resource
   */
  default Optional<String> getMediaType() {
    return Optional.empty();
  }

}
