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

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;

import nz.org.riskscape.engine.Project;
import nz.org.riskscape.engine.Reference;
import nz.org.riskscape.engine.cli.Table.Property;
import nz.org.riskscape.engine.function.IdentifiedFunction;
import nz.org.riskscape.engine.function.IdentifiedFunction.Category;
import nz.org.riskscape.engine.i18n.Messages;
import nz.org.riskscape.picocli.CommandLine;
import nz.org.riskscape.picocli.CommandLine.Command;

@Command(name = "function", subcommands = {
    FunctionCommand.FunctionListCommand.class,
    FunctionCommand.FunctionInfoCommand.class
  })
  public class FunctionCommand extends StubCommand {

  /**
   * Function list sub command.
   */
  @CommandLine.Command(name = "list")
  public static class FunctionListCommand extends ApplicationCommand {

    @CommandLine.Option(names={"-c", "--category"}, paramLabel = "<category>")
    List<Category> categories = Lists.newArrayList(Category.RISK_MODELLING, Category.UNASSIGNED);

    @CommandLine.Option(names= {"--all"})
    public boolean all;

    @Override()
    public Object doCommand(Project project) {
      Messages messages = getMessages();
      List<Reference<IdentifiedFunction>> functions = filterFunctions(project.getFunctionSet().getReferences());

      return Table.fromList(
          functions,
          IdentifiedFunction.class,
          messages.getLabels(),
          Arrays.asList(
              Property.of("id", Reference::getId),
              asPropertyWithProblems("description", idf -> getDescription(idf)),
              Property.of("arguments", ref -> ref.getResult().map(idf -> idf.getArguments().toString()).orElse("")),
              Property.of("return-type", ref -> ref.getResult().map(idf -> idf.getReturnType().toString()).orElse("")),
              Property.of("category", ref -> ref.getResult().map(idf -> idf.getCategory().toString()).orElse(""))
          )
      );
    }

    private String getDescription(IdentifiedFunction function) {
      // try to use the i18n translatable string if one exists
      String description = getMessages().getHelpMessage(function, "description").orElse(null);

      if (description == null) {
        // nope, try the function itself. This covers the user-defined (INI file) description,
        // as well as hard-coded text for built-in functions we haven't converted to i18n yet
        description = function.getDescription();
      }
      return description == null ? "" : description;
    }

    List<Reference<IdentifiedFunction>> filterFunctions(List<Reference<IdentifiedFunction>> allFunctions) {
      if (all || categories == null || categories.isEmpty()) {
        return allFunctions;
      } else {
        return allFunctions.stream()
            // this includes options in the given category *and* failed functions
            .filter(ref -> ref.getResult().map(idf -> categories.contains(idf.getCategory())).orElse(true))
            .collect(Collectors.toList());
      }
    }
  }


  /**
   * Function info sub command.
   */
  @CommandLine.Command(name = "info")
  public static class FunctionInfoCommand extends ApplicationCommand {

    @CommandLine.Parameters()
    public String functionId;

    @Override()
    public Object doCommand(Project project) {
      IdentifiedFunction f = ApplicationCommand.getObject(project.getFunctionSet(), functionId, getTerminal());

      Table table = new Table(
        Table.rowToText(Lists.newArrayList("name", f.getId())),
        Table.rowsToText(Lists.newArrayList(
          Lists.newArrayList("description", getDetailedHelp(f, "description").orElse(f.getDescription())),
          Lists.newArrayList("source", f.getSource().toString()),
          Lists.newArrayList("argument-types", f.getArguments().toString()),
          Lists.newArrayList("return-type", f.getReturnType().toString())
        ))
      );
      return table;
    }
  }

}
