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

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;


import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import nz.org.riskscape.engine.Assert;
import nz.org.riskscape.engine.Tuple;
import nz.org.riskscape.engine.types.Struct;

@RunWith(Parameterized.class)
public class PeekingTupleIteratorExceptionHandlingTest {


  public PeekingTupleIteratorExceptionHandlingTest(List<Tuple> tuples) {
    this.example = tuples;
  }

  private static final Tuple FAIL = Tuple.ofValues(Struct.of());
  private static final Tuple EMPTY= Tuple.ofValues(Struct.of());

  @Parameters
  public static List<List<Tuple>> implementations() {
    return Arrays.asList(
        ImmutableList.of(empty()),
        ImmutableList.of(empty(), empty()),
        ImmutableList.of(empty(), empty(), empty()),
        ImmutableList.of(FAIL),
        ImmutableList.of(FAIL, FAIL),
        ImmutableList.of(FAIL, FAIL, FAIL),
        ImmutableList.of(empty(), FAIL),
        ImmutableList.of(empty(), empty(), FAIL),
        ImmutableList.of(empty(), empty(), empty(), FAIL),
        ImmutableList.of(FAIL, empty()),
        ImmutableList.of(FAIL, FAIL, empty()),
        ImmutableList.of(FAIL, FAIL, FAIL, empty()),
        ImmutableList.of(empty(), FAIL, empty(), FAIL),
        ImmutableList.of(empty(), FAIL, empty(), FAIL, empty(), FAIL),
        ImmutableList.of(empty(), FAIL, empty()),
        ImmutableList.of(empty(), FAIL, FAIL, FAIL, empty()),
        ImmutableList.of(FAIL, empty(), empty(), empty(), FAIL),

        ImmutableList.of()
    );
  }

  private static Tuple empty() {
   return EMPTY.clone();
  }

  final List<Tuple> example;

  @Test
  public void aPeekingIteratorWillThrowAnInvalidTupleExceptionOnNextNoHasNextChecks() throws Exception {
    TupleIterator iterator = from(example);

    for (Tuple tuple : example) {
      if (tuple == FAIL) {
        Assert.assertThrows(InvalidTupleException.class, () -> iterator.next());
      } else {
        assertEquals(tuple, iterator.next());
      }

    }

    Assert.assertThrows(NoSuchElementException.class, () -> iterator.next());
    Assert.assertThrows(NoSuchElementException.class, () -> iterator.next());
  }

  @Test
  public void aPeekingIteratorWillThrowAnInvalidTupleExceptionOnNextPerfectIterationBehaviour() throws Exception {
    TupleIterator iterator = from(example);

    for (Tuple tuple : example) {
      assertTrue(iterator.hasNext());
      if (tuple == FAIL) {
        Assert.assertThrows(InvalidTupleException.class, () -> iterator.next());
      } else {
        assertEquals(tuple, iterator.next());
      }
    }

    assertFalse(iterator.hasNext());
    Assert.assertThrows(NoSuchElementException.class, () -> iterator.next());
  }


  @Test
  public void aPeekingIteratorWillThrowAnInvalidTupleExceptionWeirdHasNext() throws Exception {
    TupleIterator iterator = from(example);

    for (Tuple tuple : example) {
      assertTrue(iterator.hasNext());
      assertTrue(iterator.hasNext());

      if (tuple == FAIL) {
        Assert.assertThrows(InvalidTupleException.class, () -> iterator.next());
      } else {
        assertEquals(tuple, iterator.next());
      }
    }

    assertFalse(iterator.hasNext());
    Assert.assertThrows(NoSuchElementException.class, () -> iterator.next());
    assertFalse(iterator.hasNext());
    Assert.assertThrows(NoSuchElementException.class, () -> iterator.next());
  }


  private TupleIterator from(List<Tuple> fails) {
    List<Tuple> toIterate = Lists.newArrayList(fails);
    return TupleIterators.peeker(() -> {
      if (toIterate.isEmpty()) {
        return null;
      }

      Tuple removed = toIterate.remove(0);
      if (removed == FAIL) {
        throw new InvalidTupleException(removed, "we got a baddun");
      } else {
        return removed;
      }
    }, Optional.empty());
  }

}
