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

import java.util.NoSuchElementException;

import lombok.Getter;
import nz.org.riskscape.engine.Tuple;
import nz.org.riskscape.engine.relation.TupleIterator;

/**
 * The {@link Scheduler} works with Pages, which is simply a block of Tuples. The goal is to
 * reduce thread-contention - each thread can work independently with its own Page of Tuples, and we
 * only have to worry about locking when adding/getting a new Page.
 */
public class Page {

  private final Tuple[] data;

  /**
   * The maximum number of Tuples that can fit in this page
   */
  @Getter
  private final int maxSize;

  private int writeIndex = 0;

  public Page(int pageSize) {
    this.data = new Tuple[pageSize];
    this.maxSize = pageSize;
  }

  /**
   * @return the number of Tuples currently in this page
   */
  public int getTupleCount() {
    return writeIndex;
  }

  /**
   * @return true if no more Tuples can be added to this page
   */
  public boolean isFull() {
    return writeIndex >= maxSize;
  }

  /**
   * Adds (i.e. writes) a new tuple to the page.
   */
  public void add(Tuple tuple) {
    if (isFull()) {
      throw new IllegalStateException("Cannot add tuple - page is full");
    }
    data[writeIndex++] = tuple;
  }

  @Override
  public String toString() {
    return String.format("Page[written %d/%d tuples]", getTupleCount(), getMaxSize());
  }

  /**
   * Returns a copy of the Page that can only be read from, not updated. Note that multiple different
   * processes can read from the same Page at the same time.
   */
  public ReadOnlyPage getReadOnlyCopy() {
    return new ReadOnlyPage();
  }

  public class ReadOnlyPage implements TupleIterator {

    private int readIndex = 0;

    /**
     * @return the next Tuple in the page to read.
     */
    @Override
    public Tuple next() {
      if (!hasNext()) {
        throw new NoSuchElementException("No more tuples left in page to read. " + toString());
      }
      return data[readIndex++];
    }

    /**
     * @return the Tuple in the page that will be read next.
     */
    public Tuple peek() {
      if (!hasNext()) {
        throw new NoSuchElementException("No more tuples left in page to peek. " + toString());
      }
      return data[readIndex];
    }

    @Override
    public boolean hasNext() {
      return readIndex < getTupleCount();
    }

    @Override
    public String toString() {
      return String.format("Page[read %d/%d tuples]", readIndex + 1, getTupleCount());
    }

    /**
     * @return the total number of Tuples to be read
     */
    public int getTupleCount() {
      return writeIndex;
    }
  }
}
