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

import java.net.URI;
import java.util.Optional;

import org.locationtech.jts.geom.Point;

import com.codahale.metrics.MetricRegistry;

import lombok.RequiredArgsConstructor;
import nz.org.riskscape.engine.DefaultEngine;
import nz.org.riskscape.engine.ProjectTest;
import nz.org.riskscape.engine.TaskHelper;
import nz.org.riskscape.engine.Tuple;
import nz.org.riskscape.engine.gt.GeometryHelper;
import nz.org.riskscape.engine.pipeline.RealizedPipeline;
import nz.org.riskscape.engine.pipeline.sink.SaveSink;
import nz.org.riskscape.engine.sched.SchedulerBasedExecutor;
import nz.org.riskscape.engine.steps.FilterStep;
import nz.org.riskscape.engine.steps.RelationInputStep;
import nz.org.riskscape.engine.types.Referenced;
import nz.org.riskscape.engine.types.Struct;
import nz.org.riskscape.engine.types.Types;
import nz.org.riskscape.problem.ResultOrProblems;
import nz.org.riskscape.rl.ExpressionParser;
import nz.org.riskscape.rl.ast.PipelineDeclaration;
import nz.org.riskscape.rl.ast.StructDeclaration;

@RequiredArgsConstructor
public abstract class BasePipelineOutputStoreTest extends ProjectTest implements TaskHelper {

  protected final PipelineOutputStore subject;

  protected PipelineOutputContainer created;

  protected GeometryHelper latLongHelper = GeometryHelper.latLong(project.getSridSet());
  protected GeometryHelper crs84Helper = GeometryHelper.longLat(project.getSridSet());

  protected SchedulerBasedExecutor pipelineExecutor = new SchedulerBasedExecutor(engine);

  // A trivial pipeline.
  protected PipelineDeclaration pipelineDeclaration = ExpressionParser.INSTANCE.parsePipeline(
      String.format("input(value: 10)%n-> filter(value > 8)")
  );
  protected RealizedPipeline pipeline = executionContext.realize(pipelineDeclaration);

  protected Struct spatialType = Struct.of(
      "geom", Referenced.of(Types.POINT, latLongHelper.getCrs()),
      "foo", Types.TEXT);
  protected Struct spatialTypeCrs84 = Struct.of(
      "geom", Referenced.of(Types.POINT, crs84Helper.getCrs()),
      "foo", Types.TEXT);
  protected Struct nonSpatialType = Struct.of(
      "foo", Types.TEXT,
      "bar", Types.TEXT);

  protected Point point1 = latLongHelper.point(20, 10);
  protected Point point1Crs84 = crs84Helper.point(10, 20);
  protected Tuple spatialTuple = Tuple.ofValues(spatialType, point1, "foo");
  protected Tuple spatialTupleCrs84 = Tuple.ofValues(spatialTypeCrs84, point1Crs84, "foo");
  protected Tuple nonSpatialTuple = Tuple.ofValues(nonSpatialType, "foo", 10L);

  protected MetricRegistry pipelineStats = new MetricRegistry();

  @Override
  protected void populateEngineWithDefaults(DefaultEngine newEngine) {
    super.populateEngineWithDefaults(newEngine);
    // We need the steps that out test pipeline will use
    newEngine.getPipelineSteps().add(new RelationInputStep(engine));
    newEngine.getPipelineSteps().add(new FilterStep(engine));
  }

  protected PipelineOutputOptions defaultOptions() {
    PipelineOutputOptions options = subject.newOutputOptions("test");
    options.setPipelineDeclaration(Optional.of(pipelineDeclaration));
    return options;
  }

  protected ResultOrProblems<PipelineOutputContainer> getOutputContainer(URI outputLocation) {
    return getOutputContainer(outputLocation, defaultOptions());
  }

  protected ResultOrProblems<PipelineOutputContainer> getOutputContainer(URI outputLocation,
      PipelineOutputOptions options) {
    return subject.create(outputLocation, pipeline, options).map(poc -> this.created = poc);
  }

  protected SaveSink writeSpatialData(PipelineOutputContainer container) {
    return writeSpatialData(container, "spatial", null);
  }

  protected SaveSink writeSpatialData(PipelineOutputContainer container, String name, Format format) {
    return writeData(container, name, format, spatialTuple);
  }

  protected SaveSink writeNonSpatialData(PipelineOutputContainer container) {
    return writeNonSpatialData(container, "non-spatial", null);
  }

  protected SaveSink writeNonSpatialData(PipelineOutputContainer container, String name, Format format) {
    return writeData(container, name, format, nonSpatialTuple);
  }

  protected SaveSink writeData(PipelineOutputContainer container, String name, Format format, Tuple... data) {
    SaveSink sink = (SaveSink)container.createSinkForStep(
        new SinkParameters(name, data[0].getStruct(), Optional.ofNullable(format), StructDeclaration.EMPTY)
    )
        // drain warning so tests can check they exist. These used to be drained by
        // FileSystemPipelineOutputStore.createSink but now the warning are kept with the ResultOrProblems
        // returned from createSink to they can benefit from higher level context being added to them
        // by the SinkTask
        .drainWarnings(project.getProblemSink())
        .get();
    for (Tuple d: data) {
      sink.accept(d);
    }
    sink.finish();
    return sink;
  }
}
