/*
 * 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.File;
import java.net.URI;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.collect.Lists;
import com.google.common.io.Files;

import lombok.Getter;
import lombok.NonNull;
import nz.org.riskscape.problem.ResultOrProblems;

/**
 * Factory of {@link Resource}s.
 */
public class DefaultResourceFactory implements ResourceFactory {

  @Getter
  private final List<ResourceLoader> loaders;

  public DefaultResourceFactory() {
    this(new FileResourceLoader());
  }

  public DefaultResourceFactory(ResourceLoader... loaders) {
    this.loaders = Lists.newArrayList(loaders);
  }

  /**
   * Loads the resource pointed to by target and return the {@link Resource} along with any problems encountered.
   * @param target to load
   * @param relativeTo URI to resolve relative targets against
   * @return resource and/or problems encountered
   */
  @Override
  public Resource load(@NonNull String target, @NonNull URI relativeTo) throws ResourceLoadingException {
    ResultOrProblems<URI> resourceUri = UriHelper.uriFromLocation(target, relativeTo);
    if (resourceUri.hasProblems()) {
      throw new ResourceLoadingException(relativeTo, resourceUri.getAsSingleProblem());
    }
    return load(resourceUri.get());
  }

  /**
   * Loads the resource pointed to by target and return the {@link Resource} along with any problems encountered.
   * @param target to load
   * @param relativeTo URI to resolve relative targets against
   * @return resource and/or problems encountered
   */
  @Override
  public Resource load(@NonNull URI target, @NonNull URI relativeTo) throws ResourceLoadingException {
    return load(relativeTo.resolve(target));
  }

  /**
   * Loads the resource pointed to by target and return the {@link Resource} along with any problems encountered.
   * @param target to load
   * @return resource and/or problems encountered
   */
  @Override
  public Resource load(@NonNull URI target) throws ResourceLoadingException {
    for (ResourceLoader loader: loaders) {
      if (loader.canLoad(target)) {
        return loader.load(target);
      }
    }
    throw new ResourceLoadingException(target, String.format("Could not find a Resource Loader for '%s'",
        target.toString()));
  }

  /**
   * Add a {@link ResourceLoader} to this factory.
   * @param loader
   */
  @Override
  public void add(ResourceLoader loader) {
    if (! loaders.contains(loader)) {
      loaders.add(loader);
    }
  }

  @Override
  public CreateHandle create(CreateRequest request) {
    // NB for now we just support writing to files, we can add plugability later when it's needed without any fuss
    URI location = request.getContainer();
    if (location.getScheme().equals("file")) {
      // TODO should we append a file extension based on content type?
      File directory = new File(location.getPath());

      // canWrite will pick up any errors
      directory.mkdirs();

      if (!directory.canWrite() || !directory.isDirectory()) {
        throw new CreateException(ResourceProblems.get().containerNotWritable(location));
      }

      File file = new File(directory, request.getName());

      while (file.exists() && !request.isReplace()) {
        // the API says we can just come up with our own name - so a suffix/discriminator is a reasonable option
        // for us
        file = increment(file);
      }
      return new FileCreateHandle(request, file);
    } else {
      throw new CreateException(ResourceProblems.get().unsupportedScheme(location.getScheme()));
    }
  }


  /**
   * Adds a counter on to a file name to avoid clashes - see {@link DefaultResourceFactoryTest} for more info
   * Package scoped for testing
   */
  File increment(File file) {
    String filename = file.getName();
    String extension = Files.getFileExtension(filename);
    String append = extension.length() == 0 ? "" : "." + extension;
    String basename = Files.getNameWithoutExtension(filename);
    Pattern pattern = Pattern.compile("(.+)-([0-9]+)$");
    // look for `-$num` in the basename
    Matcher matcher = pattern.matcher(basename);
    if (matcher.matches()) {
      // existing one - increment and re-assemble
      int counter = Integer.parseInt(matcher.group(2)) + 1;
      return new File(file.getParentFile(), matcher.group(1) + "-" + counter + append);
    } else {
      // add one and re-assemble
      return new File(file.getParentFile(), basename + "-1" + append);
    }
  }

}
