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

import java.math.BigInteger;
import java.nio.ByteBuffer;

import hdf.hdf5lib.H5;
import hdf.hdf5lib.HDF5Constants;
import hdf.hdf5lib.HDFNativeData;
import lombok.Getter;
import lombok.Setter;
import nz.org.riskscape.engine.types.Type;
import nz.org.riskscape.engine.types.Types;

public class H5IntegerType extends H5Type {

  private final boolean isUnsigned;

  /**
   * As a performance optimisation, we can read a 64 bit unsigned long as a signed long, to avoid having to use
   * BigIntegers.  As long as the data never exceeds {@link Long#MAX_VALUE} or not used as a number then this is
   * safe to do an can give a massive speed up.
   *
   * Because this gives a potential loss of functionality, it is off by default.
   *
   * Setting this mutates the type instance, so make sure you 'own' the data type before setting it.
   */
  @Setter @Getter
  private boolean opaqueUnsignedLongs;

  protected H5IntegerType(long id, boolean opaqueUnsignedLongs) {
    super(id);
    this.isUnsigned = H5.H5Tget_sign(ptr) == HDF5Constants.H5T_SGN_NONE;
    this.opaqueUnsignedLongs = opaqueUnsignedLongs;
  }

  protected H5IntegerType(long id) {
   this(id, false);
  }

  public boolean isUnsigned() {
    return isUnsigned;
  }


  public byte[] intToBytes(long value) {
    switch((int) getDataSize()) {
    case Long.BYTES:
      return HDFNativeData.longToByte(value);
    case Integer.BYTES:
      return HDFNativeData.intToByte((int) value);
    case Short.BYTES:
      return HDFNativeData.shortToByte((short) value);
    case Byte.BYTES:
      return HDFNativeData.byteToByte((byte) value);

    default:
      throw new RuntimeException("unknown size -" + getDataSize());
    }
  }

  @Override
  public Object read(ByteBuffer outBuf, int offset) {
    if (isUnsigned()) {
      switch((int) getDataSize()) {
      case Long.BYTES:
        if (opaqueUnsignedLongs) {
          outBuf.getLong(offset);
        } else {
          return new BigInteger(Long.toUnsignedString(outBuf.getLong(offset)));
        }
      case Integer.BYTES:
        return Integer.toUnsignedLong(outBuf.getInt(offset));
      case Short.BYTES:
        return Short.toUnsignedLong(outBuf.getShort(offset));
      case Byte.BYTES:
        return (long)outBuf.get(offset);
      default:
        throw new RuntimeException("unknown size -" + getDataSize());
      }

    } else {
      switch((int) getDataSize()) {
      case Long.BYTES:
        return outBuf.getLong(offset);
      case Integer.BYTES:
        return (long)outBuf.getInt(offset);
      case Short.BYTES:
        return (long)outBuf.getShort(offset);
      case Byte.BYTES:
        return (long)outBuf.get(offset);
      default:
        throw new RuntimeException("unknown size - " + getDataSize());
      }
    }
  }

  @Override
  public String toString() {
    String unsigned = isUnsigned() ? " U" : " ";
    return getClassName() + unsigned + getDataSize();
  }

  @Override
  public Type toType() {
    if (isUnsigned() && getDataSize() == Long.BYTES) {
      return Types.DECIMAL;
    } else {
      return Types.INTEGER;
    }
  }

  @Override
  public boolean isVariableLength() {
    return false;
  }

}
