/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package jp.sourceforge.dvibrowser.dvicore.doc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviFontSpec;
import jp.sourceforge.dvibrowser.dvicore.DviFontTable;
import jp.sourceforge.dvibrowser.dvicore.DviObject;
import jp.sourceforge.dvibrowser.dvicore.DviUnit;
import jp.sourceforge.dvibrowser.dvicore.api.DviContextSupport;
import jp.sourceforge.dvibrowser.dvicore.api.DviData;
import jp.sourceforge.dvibrowser.dvicore.api.DviDocument;
import jp.sourceforge.dvibrowser.dvicore.api.DviExecutorContext;
import jp.sourceforge.dvibrowser.dvicore.api.DviInput;
import jp.sourceforge.dvibrowser.dvicore.api.DviPage;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviBop;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPostPost;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPostamble;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPreamble;
import jp.sourceforge.dvibrowser.dvicore.io.DviInputStreamReader;
import jp.sourceforge.dvibrowser.dvicore.render.EmptyDviExecutorHandler;


// TODO: support document cache
public class StreamDviDocument
extends DviObject
implements DviDocument //, java.io.Serializable
{
  public static final long MAX_BUFFER_LENGTH = 10000000; /* 10MB */

  private DviPreamble   preamble = null;
  private DviPostamble  postamble = null;
  private DviPostPost   postPost = null;

  private byte [] buf = null;

  final DviFontTable fontTable = new DviFontTable();

  private ArrayList<DviPage> pages = new ArrayList<DviPage>();

  public StreamDviDocument(DviContextSupport dcs, InputStream in)
  throws DviException
  {
    super(dcs);
    try {
      parseInputStream(in);
    } catch (IOException ex) {
      throw new DviException(ex);
    }
  }

  private void parseInputStream(InputStream is)
  throws IOException, DviException
  {
    final MyInputStream mis = new MyInputStream(is);
    final DviInputStreamReader in = new DviInputStreamReader(mis);

    getDviContext().execute(
      new DviData() {
        public DviInput getInput()      { return in; }
        public DviFontTable getFontTable() { throw new UnsupportedOperationException(); }
        public DviUnit getDviUnit()     { throw new UnsupportedOperationException(); }
        public long getDataSize() throws DviException
        {
          throw new UnsupportedOperationException();
        }
        public DviInput getInput(long start, long end) throws DviException
        {
          throw new UnsupportedOperationException();
        }
      },
      new EmptyDviExecutorHandler() {
        private int pageNum=0;
        private DviExecutorContext ctx;
        public void begin(DviExecutorContext ctx) {
          this.ctx = ctx;
        }
        public void end() {
          this.ctx = null;
        }
        public void doPre(DviPreamble pre) {
          preamble = pre;
        }
        public void doPost(DviPostamble post) {
          postamble = post;
        }
        public void doPostPost(DviPostPost pp) {
          postPost = pp;
          ctx.setTerminate(true);
        }
        private long bop;
        public void doBop(DviBop bop) {
          this.bop = ctx.getCommandRange().begin();
        }
        public void doEop() {
          long eop = ctx.getCommandRange().begin();
          DviDocument doc = StreamDviDocument.this;
          if (doc.getDviContext() instanceof URLDviDocument) {
            doc = (URLDviDocument) doc.getDviContext();
          }
          DviPage page = createPage(pageNum, bop, eop);
          pages.add(page);
          pageNum++;
        }
        public void doDefineFont(int fn, DviFontSpec fs) {
          fontTable.put(fn, fs);
        }
      }
    );
    if (preamble == null)
      throw new DviException
        ("no preamble found in the stream.");

    if (postamble == null)
      throw new DviException
        ("no postamble found in the stream.");

    if (postPost == null)
      throw new DviException
        ("no postPost found in the stream.");

    buf = mis.baos.toByteArray();
  }

  protected DviPage createPage(int pageNum, long bop, long eop)
  {
    return new DefaultDviPage(this, pageNum, bop, eop);
  }

  private static class MyInputStream
  extends FilterInputStream
  {
    public MyInputStream(InputStream is) { super(is); }
    private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    public int read()
    throws IOException
    {
      int c = super.read();
      baos.write(c);
      return c;
    }

    public int read(byte [] b, int off, int len)
    throws IOException
    {
      int r = super.read(b, off, len);
      if (r > 0) {
        baos.write(b, off, r);
      }
      return r;
    }
    public void reset()
    throws IOException
    {
      throw new UnsupportedOperationException();
    }
    public long skip(long n)
    throws IOException
    {
      throw new UnsupportedOperationException();
    }
    public boolean markSupported()
    {
      return false;
    }
  }

  public int getTotalPages() throws DviException {
    return postamble.totalPages();
  }

  public DviUnit getDviUnit() {
    return postamble.dviUnit();
  }
  public DviPreamble getPreamble() {
    return preamble;
  }
  public DviPostamble getPostamble() {
    return postamble;
  }
  public DviPostPost getPostPost() {
    return postPost;
  }
  public DviFontTable getFontTable() {
    return fontTable;
  }

  public DviInput getInput()
  throws DviException
  {
    ByteArrayInputStream bais = new ByteArrayInputStream(buf);
    return new DviInputStreamReader(bais);
  }
  
  public long getDataSize()
  {
    return buf.length;
  }

  public DviInput getInput(long start, long end)
  throws DviException
  {
    DviInputStreamReader in = new DviInputStreamReader(
      new ByteArrayInputStream(
        this.buf, (int) start, (int)(end - start) + 1
      )
    );
    in.setOffset(start);
    return in;
  }

  public DviPage getPage(int p)
  throws DviException
  {
    if (p < 0 || getTotalPages() <= p)
      throw new IllegalArgumentException
        ("page number out of bounds.");

    return pages.get(p);
  }

  public DviPage [] getPages()
  {
    return pages.toArray(new DviPage [pages.size()]);
  }
}
