package nxm.sys.lib;

import java.util.zip.Deflater;
import java.util.zip.Inflater;

import nxm.sys.inc.ProvisionalUseOnly;

/** Zip based Compress/Decompress that wraps around another IOResource with a starting offset.
    @author  Jeff Schoen
 */
@ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
class MatLabZipResource extends IOResource {

  private static int CBUFSZ=4096, UBUFSZ=CBUFSZ+64;
  private static int INMEM=0x1, NOZIP=0x2;
  private IOResource io;
  private long cstart,coffset,uoffset;
  private int clength,ulength;
  private byte[] cbuf = new byte[CBUFSZ];
  private byte[] ubuf = new byte[UBUFSZ];
  private boolean tandem,inMem,noZip;
  private Inflater inflater;
  private Deflater deflater;


  /** Creates a new instance. */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  MatLabZipResource (IOResource io, long cstart, int flags) {
    this.io=io;
    this.cstart=cstart;
    this.coffset=cstart;
    this.clength=0;
    this.uoffset=cstart;
    this.ulength=0;
    this.M=io.M;
    this.dir=io.dir;
    this.type=io.type;
    this.urlstr=io.urlstr;
    this.qualTable=io.qualTable;
    this.isOpen=io.isOpen;
    this.tandem=io.isOpen;
    if (this.dir<0) {
      inflater = new Inflater();
    } else {
      deflater = new Deflater();
      ulength = CBUFSZ;
    }
  }

  /** Set the current seek offset
      @param offset Current seek offset in bytes (0..N-1).
      @return true on success, false on failure.
   */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public boolean seek (long offset) {
    this.offset = offset;
    return (offset==this.offset);
  }

  private boolean rseek (long offset) {
    boolean back = (offset<uoffset);
    if (back) {
      inflater.reset();
      uoffset=coffset=cstart;
      ulength=clength=0;
    }
    if (offset>=uoffset+ulength) {
      if (inflater.getRemaining()<=0) {
        coffset += clength;
        clength = io.read(cbuf,0, CBUFSZ,coffset);
        inflater.setInput(cbuf,0,clength);
      }
      try { uoffset += ulength; ulength = inflater.inflate(ubuf,0,UBUFSZ); }
      catch (Exception e) { System.out.println("Problem with inflater: "+e); }
    }
    return true;
  }

  private boolean wseek (long offset, boolean finish) {
    if (offset>=uoffset+ulength || finish) {
      int ulen = (int)(offset-uoffset);
      if (ulen>0 && deflater.needsInput()) { deflater.setInput(ubuf,0,ulen); uoffset += ulen; }
      if (finish) deflater.finish();
      for (clength=-1; clength!=0; ) {
        try { clength=deflater.deflate(cbuf,0,CBUFSZ); }
        catch (Exception e) { System.out.println("Problem with deflater: "+e); }
        if (clength>0) { io.write(cbuf,0, clength,coffset); coffset+=clength; }
      }
    }
    return true;
  }

  /** open the resource */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public boolean open () {
    if (tandem) return false;
    return io.open();
  }

  /** close the resource */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public boolean close () {
    if  (dir>0) wseek(offset,true);
    if (tandem) return false;
    return io.close();
  }

  /** get length of resource in bytes */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public long getLength () { return length; }

  /** set length of resource in bytes */
  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public void setLength (long length) { this.length = length; }

  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public int read (byte[] buf, int boff, int bytes) {
    int status=0, off=boff,ndo;
    while (bytes>0) {
      int uoff = (int)(offset-uoffset);
      if (offset<cstart) {
        ndo = (int)(cstart-offset);
        if (ndo>bytes) ndo=bytes;
        io.read(buf,boff,ndo,offset);
      } else {
        ndo = (uoff<0)? 0 : ulength-uoff;
        if (ndo<=0) { rseek(offset); continue; }
        if (ndo>bytes) ndo=bytes;
        System.arraycopy(ubuf,uoff, buf,boff, ndo);
      }
      bytes-=ndo; boff+=ndo; offset+=ndo;
    }
    return boff;
  }

  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  @Override
  public int write (byte[] buf, int boff, int bytes) {
    int status=0, off=boff,ndo;
    while (bytes>0) {
      if (offset<cstart) {
        ndo = (int)(cstart-offset);
        if (ndo>bytes) ndo=bytes;
        io.write(buf,boff,ndo,offset);
      } else {
        int uoff = (int)(offset-uoffset);
        ndo = (uoff<0)? 0 : ulength-uoff;
        if (ndo>bytes) ndo=bytes;
        if (ndo<=0) { wseek(offset,false); continue; }
        System.arraycopy(buf,boff, ubuf,uoff, ndo);
      }
      bytes-=ndo; boff+=ndo; offset+=ndo;
    }
    return boff;
  }

  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  void setBuffer (byte[] buf, long offset, int length) {
    ubuf = buf;
    uoffset = offset;
    ulength = length;
  }

  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  byte[] getBuffer() {
    return ubuf;
  }

  @ProvisionalUseOnly("Added in NeXtMidas 3.7.0")
  long getCompressedOffset() {
    return coffset;
  }

}
