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

import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

import nz.org.riskscape.config.ConfigSection;
import nz.org.riskscape.engine.bind.Parameter;
import nz.org.riskscape.engine.ini.IdentifiedObjectBuilder;
import nz.org.riskscape.engine.problem.GeneralProblems;
import nz.org.riskscape.engine.resource.UriHelper;
import nz.org.riskscape.problem.Problem;
import nz.org.riskscape.problem.Problems;
import nz.org.riskscape.problem.ResultOrProblems;

/**
 * Factory for creating {@link Bookmark}s from files in ini format.
 */
public class BookmarkFactory extends IdentifiedObjectBuilder.Base<Bookmark> {

  public static final String BOOKMARK_KEY = "bookmark";

  public static final String MAP_ATTRIBUTE_PREFIX = "map-attribute.";

  private static boolean isLocalFile(String location, URI relativeTo) {
    return !UriHelper.hasUriScheme(location) && UriHelper.isFile(relativeTo);
  }

  public BookmarkFactory() {
    super(Bookmark.class, BOOKMARK_KEY);
  }

  public static ResultOrProblems<URI> uriFromLocation(String location, URI relativeTo) {
    String[] splitLocation = new String[0];

    if (isLocalFile(location, relativeTo)) {
      // here we do a bit of a kludge to support query parameters in filepaths - we
      // only want to treat anything up the the first '?' as a file URI. Note that
      // this means that ? in filepaths will not work for bookmarks constructed via
      // this factory, unless you use the full URL format
      splitLocation = location.split(Pattern.quote("?"), 2);
      location = splitLocation[0];
    }

    ResultOrProblems<URI> uriResult = UriHelper.uriFromLocation(location, relativeTo);

    // if we split up the string, tack everything after the '?' back onto the end
    if (splitLocation.length == 2 && !uriResult.hasProblems()) {
      URI rejoinedUri = URI.create(uriResult.get().toString() + "?" + splitLocation[1]);
      uriResult = ResultOrProblems.of(rejoinedUri);
    }
    return uriResult;
  }

  /**
   * Builds a bookmark from an ini file section.
   * @param id of the bookmark to build
   * @param section config section to build bookmark from
   * @return bookmark or encountered problems
   */
  @Override
  public ResultOrProblems<Bookmark> build(String id, ConfigSection section) {
    List<Problem> problems = Lists.newArrayList();
    Consumer<List<Problem>> addProblems  = ps -> problems.addAll(ps);

    String description = section.getOne("description").map(or ->
      or.ifElseReturn(
        value -> value,
        addProblems,
        ""
      )
    ).orElse("");

    String format = section.getOne("format").map(or -> or.ifElseReturn(v -> v, addProblems, null)).orElse(null);
    String location = section.getOneRequired("location").ifElseReturn(l -> l, addProblems, null);

    if (Strings.isNullOrEmpty(id)) {
      problems.add(GeneralProblems.required("id", Parameter.class));
    }

    URI uri = null;
    if (location != null) {
      ResultOrProblems<URI> uriResult = uriFromLocation(location, section.getLocation());
      if (uriResult.hasProblems()) {
        problems.add(Problems.foundWith(Parameter.class, "location", uriResult.getProblems()));
      } else {
        uri = uriResult.get();
      }
    }

    /* don't create the bookmark if we'll never be able to resolve its URI */
    if (uri == null || Problem.hasErrors(problems)) {
      return ResultOrProblems.failed(problems);
    }

    //We always add the ResultOrProblems with the Bookmark and problems. Even when problems exist the bookmark
    //is useful as it will hopefully contain the bookmarkId which will help the user to find the problem
    return ResultOrProblems.of(new Bookmark(id, description, format, uri, section.toMultiMap()), problems);
  }
}
