/*
 * 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.bind;

import java.io.File;
import java.net.URI;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;

import nz.org.riskscape.engine.FailedObjectException;
import nz.org.riskscape.engine.Identified;
import nz.org.riskscape.engine.IdentifiedCollection;
import nz.org.riskscape.engine.NoSuchObjectException;
import nz.org.riskscape.problem.ResultOrProblems;

/**
 * Interface for a class that can convert a string in to an object of a particular type
 *
 * A note on deprecation and where this code is going:
 *
 *  - binding is to become detached from the parameter - the parameter is just context that we'll probably wrap around
 *  any binding problems via the binding context bind method
 *
 *  - new binders should not implement the deprecated methods and instead use the NewBaseBinder class, which will take
 *  care of implementing those deprecated methods in a consistent way
 */
public interface ParameterBinder extends Identified {

  /**
   * Useful pre-emptive validation for values before they go in to geotools to check whether they look openable.
   */
  static boolean validateExistingFileOrUri(Parameter param, String value) {
    try {
      File file = Paths.get(value).toFile();
      if (file.exists() && !file.isDirectory()) {
        return true;
      }
    } catch (InvalidPathException ex) {
    }

    try {
      if (value.contains("://")) {
        URI.create(value);
        return true;
      }
    } catch (IllegalArgumentException urlEx) {
    }

    return false;
  }

  /**
   * Helper method for wrapping a lookup to wrap a {@link FailedObjectException}
   * in a {@link ParameterBindingException}
   */
  static <T extends Identified> T wrapFailedObject(Parameter parameter, String id, IdentifiedCollection<T> collection) {
    try {
      return collection.get(id);
    } catch (FailedObjectException ex) {
      throw new ParameterBindingException(parameter, ex.getProblem());
    }
  }

  /**
   * Helper method for wrapping a lookup to wrap it in a
   * {@link ParameterBindingException}
   */
  static <T extends Identified> T wrapLookup(Parameter parameter, String id, BindingContext context,
      Class<T> identifedClass) {
    IdentifiedCollection<T> collection = context.getProject().getCollectionByClass(identifedClass);
    try {
      return collection.get(id, context.getProject().getProblemSink());
    } catch (NoSuchObjectException | FailedObjectException ex) {
      throw new ParameterBindingException(parameter, ex.getProblem());
    }
  }

  /**
   * @return true if this binder can convert an object of sourceType to destinationType
   */
  boolean canBind(Class<?> sourceType, Class<?> destinationType);

  /**
   * Convert a string in to an object of a desired type.
   *
   * @param context dependencies etc that can be used to aid conversion
   * @param modelParameter describes the parameter including the desired java type. The implementation must return an
   * object for which <code>modelParameter.getType().isInstanceOf(object)</code> returns true
   * @param value the string to convert
   * @throws ParameterBindingException if there was an error binding which the user might be able to fix
   * @return a converted object
   * @deprecated use {@link #bindValue(BindingContext, Object, Class)} instead.  Older code can use the CompatBinder to
   * as a simple way of avoiding the deprecation warning and conforming to the new API
   */
    @Deprecated
    Object bind(BindingContext context, Parameter modelParameter, String value) throws ParameterBindingException;


  /**
   * Convert a value in to an object of a desired type.  See {@link BindingContext#bind(Object, Class) for more detail
   * on this new API.  Note that the `destinationType` arg now follows the `value`, which matches the canBind method and
   * follows a from -> to order
   * @param context dependencies etc that can be used to aid conversion
   * @param value an object to convert, must be of a type that `canBind` says it supports
   * @param destinationType a type to convert the value to, must be of a type that `canBind` says it supports
   * @return a converted object, or problems detailing why the value could not be converted
   */
  <T> ResultOrProblems<T> bindValue(BindingContext context, Object value, Class<T> destinationType);


}
