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


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

import lombok.Getter;
import nz.org.riskscape.engine.RiskscapeException;
import nz.org.riskscape.engine.function.ArgumentList;
import nz.org.riskscape.engine.function.BinaryFunction;
import nz.org.riskscape.engine.function.CompositeAggregationFunction;
import nz.org.riskscape.engine.function.FlattenFunction;
import nz.org.riskscape.engine.function.FunctionArgument;
import nz.org.riskscape.engine.function.IdentifiedFunction;
import nz.org.riskscape.engine.function.IdentifiedFunction.Category;
import nz.org.riskscape.engine.function.agg.AggregationFunctionAdapter;
import nz.org.riskscape.engine.function.lang.AppendList;
import nz.org.riskscape.engine.function.lang.AssertNotNull;
import nz.org.riskscape.engine.function.lang.Cast;
import nz.org.riskscape.engine.function.lang.ConcatList;
import nz.org.riskscape.engine.function.lang.FilterList;
import nz.org.riskscape.engine.function.lang.ListLength;
import nz.org.riskscape.engine.function.lang.ListSort;
import nz.org.riskscape.engine.function.lang.MapList;
import nz.org.riskscape.engine.function.lang.NullOf;
import nz.org.riskscape.engine.function.lang.NullableFunction;
import nz.org.riskscape.engine.function.lang.SelectFunction;
import nz.org.riskscape.engine.function.lang.ZipLists;
import nz.org.riskscape.engine.problem.ProblemFactory;
import nz.org.riskscape.engine.problem.SeverityLevel;
import nz.org.riskscape.engine.types.Nullable;
import nz.org.riskscape.engine.types.RSList;
import nz.org.riskscape.engine.types.Type;
import nz.org.riskscape.engine.types.Types;
import nz.org.riskscape.problem.Problem;
import nz.org.riskscape.problem.Problems;

public class LanguageFunctions {

  public interface LocalProblems extends ProblemFactory {

    /**
     * When a user generated warning has been raised.
     *
     * Problem text will show that the message content if from the user, not RiskScape.
     */
    @SeverityLevel(Problem.Severity.WARNING)
    Problem userWarning(String message);

    /**
     * When a user generated assert has been raised.
     *
     * Problem text will show that the message content if from the user, not RiskScape.
     */
    Problem userAssert(String message);
  }

  public static final LocalProblems PROBLEMS = Problems.get(LocalProblems.class);

  public static final IdentifiedFunction APPEND = new AppendList().builtin("append", Category.LANGUAGE);
  public static final IdentifiedFunction CONCAT = new ConcatList().builtin("concat", Category.LANGUAGE);
  public static final IdentifiedFunction MAP = new MapList().builtin("map", Category.LANGUAGE);
  public static final IdentifiedFunction MAP_STRUCT = new MapStruct().builtin("map_struct", Category.LANGUAGE);
  public static final IdentifiedFunction FILTER = new FilterList().builtin("filter", Category.LANGUAGE);
  public static final IdentifiedFunction FLATTEN_STRUCT =
      new FlattenFunction().builtin("flatten_struct", Category.LANGUAGE);
  public static final IdentifiedFunction REDUCE = new Reduce().builtin("reduce", Category.LANGUAGE);
  public static final IdentifiedFunction SELECT = new SelectFunction().builtin("select_attr", Category.LANGUAGE);
  public static final IdentifiedFunction ZIP = new ZipLists().builtin("zip", Category.LANGUAGE);
  public static final IdentifiedFunction LENGTH = new ListLength().builtin("length", Category.LANGUAGE);
  public static final IdentifiedFunction LIST_SORT = new ListSort().builtin("sort", Category.LANGUAGE);
  public static final IdentifiedFunction NULL_OF = new NullOf().builtin("null_of", Category.LANGUAGE);
  public static final IdentifiedFunction NULLABLE = new NullableFunction().builtin("nullable", Category.LANGUAGE);
  public static final IdentifiedFunction CAST = new Cast().builtin("cast", Category.LANGUAGE);
  public static final IdentifiedFunction GET_ATTR = new GetAttribute().builtin("get_attr", Category.LANGUAGE);
  public static final IdentifiedFunction ASSERT_NOT_NULL =
      new AssertNotNull().builtin("assert_not_null", Category.LANGUAGE);

  public static final IdentifiedFunction ASSERT = RealizableFunction.contextOnly(
      ArgumentList.create("condition", Types.BOOLEAN, "message", Types.TEXT),
      Types.NOTHING,
      context -> {
        return args -> {
          boolean condition = (boolean) args.get(0);
          if (! condition) {
            String message = (String) args.get(1);
            throw new RiskscapeException(PROBLEMS.userAssert(message));
          }
          return null;
        };
      }
  ).builtin("assert", Category.LANGUAGE);

  public static final IdentifiedFunction WARNING = RealizableFunction.contextOnly(
      ArgumentList.create("when", Types.BOOLEAN, "message", Types.TEXT),
      Nullable.TEXT,
      context -> {
        return args -> {
          boolean when = (boolean) args.get(0);
          if (when) {
            String message = (String) args.get(1);
            context.getProblemSink().accept(PROBLEMS.userWarning(message));
            return message;
          }
          return null;
        };
      }
  ).builtin("warning", Category.LANGUAGE);

  public static final List<IdentifiedFunction> FUNCTIONS = Arrays.asList(
      APPEND,
      CONCAT,
      MAP,
//      MAP_STRUCT, // beta function for now (remove from beta before uncommenting this line)
      FILTER,

      // Add me back in when not beta
//      REDUCE,

      // Add me back in when not beta
//      FLATTEN,
      // Add me back in when not beta
//      SELECT,
      ZIP,
      LENGTH,
      LIST_SORT,

      new BinaryFunction<Long, Long>(
          new FunctionArgument("start", Types.INTEGER),
          new FunctionArgument("stop", Types.INTEGER),
          RSList.create(Types.INTEGER),
          (start, stop) -> LongStream.range(start, stop).mapToObj(Long::valueOf).collect(Collectors
              .toList())
      ).builtin(
          "range",
          Category.LANGUAGE),

      new AggregationFunctionAdapter(new CompositeAggregationFunction()) {
        @Getter
        private final ArgumentList arguments = ArgumentList.create(
            "items",       RSList.LIST_ANYTHING,
            "aggregation", Types.ANYTHING
        );

        @Override
        public List<Type> getArgumentTypes() {
          return getArguments().getArgumentTypes();
        }

      }.builtin("aggregate_struct", IdentifiedFunction.Category.LANGUAGE),
      NULLABLE,
      NULL_OF,
      CAST,
//      GET_ATTR, // remove from beta plugin before uncommenting this line
      ASSERT_NOT_NULL,
      ASSERT,
      WARNING
  );

}
