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

import java.io.EOFException;
import java.io.IOException;

import jp.sourceforge.dvibrowser.dvicore.DviByteRange;
import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviFontSpec;
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.DviExecutor;
import jp.sourceforge.dvibrowser.dvicore.api.DviExecutorContext;
import jp.sourceforge.dvibrowser.dvicore.api.DviExecutorHandler;
import jp.sourceforge.dvibrowser.dvicore.api.DviInput;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviBop;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviCommand;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPostPost;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPostamble;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviPreamble;


// TODO: support logging

public class BasicExecutor
extends DviObject
implements DviExecutor
{
  protected static class DviExecutorContextImpl
  extends DviObject
  implements DviExecutorContext
  {
    private DviData data;
    private DviInput in;
    private DviExecutorHandler handler;

    private int command;
    private long commandBegin;
    private long commandEnd;
    private boolean terminate = false;
    
    public DviExecutorContextImpl(DviContextSupport dcs)
    {
      super(dcs);
    }

    public DviData getData() { return data; }
    public int getCommand() { return command; }
    public DviByteRange getCommandRange() {
      return new DviByteRange(commandBegin, commandEnd);
    }
    public void setTerminate(boolean f) { terminate = f; }

    private void commandDetermined() {
      commandEnd = in.getOffset() - 1;
    }
  }

  public BasicExecutor(DviContextSupport dcs)
  {
    super(dcs);
  }

  private static final DviExecutorHandler defaultHandler
    = new EmptyDviExecutorHandler();

  public void execute(DviData data)
  throws DviException
  {
    execute(data, null);
  }

  private DviExecutorContextImpl ctx;
  protected DviExecutorContextImpl getExecutorContext()
  {
    return ctx;
  }

  public void execute(DviData data, DviExecutorHandler handler)
  throws DviException
  {
    ctx = new DviExecutorContextImpl(this);
    ctx.data = data;
    ctx.handler = (handler != null) ? handler : defaultHandler;
    ctx.in = data.getInput();
    ctx.terminate = false;

    if (ctx.handler == null)
      throw new NullPointerException
        ("handler is null");
    if (ctx.data == null)
      throw new NullPointerException
        ("data is null");
    if (ctx.in == null)
      throw new NullPointerException
        ("input is null");

    try {
      begin(ctx);
      try {
        while (!ctx.terminate) {
          executeOneCommand(ctx);
        }
      } catch (EOFException ex) {
        // ignored.
      } finally {
        end();
      }
    } catch(DviException ex) {
      throw ex;
    } catch(Throwable ex) {
      ex.printStackTrace();
      throw new DviException(ex);
    } finally {
      try {
        ctx.in.close();
      } catch (IOException ex) {
        // ignored.
      }
    }
  }

  protected void executeOneCommand(DviExecutorContextImpl ctx)
  throws IOException, DviException
  {
    final int t;
    final DviInput in = ctx.in;
    
    ctx.commandBegin = ctx.commandEnd = in.getOffset();
    t = ctx.command = in.readU1();

    if (t <= 127) {
      if (wantSet()) {
        ctx.commandDetermined();
        doSet(t);
      }
    } else if (171 <= t && t <= 234) {
      if (wantSelectFont()) {
        ctx.commandDetermined();
        doSelectFont(t - 171);
      }
    } else {
      switch(t) {
        case DviCommand.DVI_SET4:
        case DviCommand.DVI_SET3:
        case DviCommand.DVI_SET2:
        case DviCommand.DVI_SET1: {
          final int alen = t - DviCommand.DVI_SET1 + 1;
          if (wantSet()) {
            final int code;

            code = in.readU(alen);
            ctx.commandDetermined();
            doSet(code);
          } else {
            in.skip(alen);
          }
          break;
        }

        case DviCommand.DVI_PUT4:
        case DviCommand.DVI_PUT3:
        case DviCommand.DVI_PUT2:
        case DviCommand.DVI_PUT1: {
          final int alen = t - DviCommand.DVI_PUT1 + 1;
          if (wantPut()) {
            final int code;
  
            code = in.readU(alen);
            ctx.commandDetermined();
            doPut(code);
          } else {
            in.skip(alen);
          }
          break;
        }

        case DviCommand.DVI_FONT4:
        case DviCommand.DVI_FONT3:
        case DviCommand.DVI_FONT2:
        case DviCommand.DVI_FONT1: {
          final int alen = t - DviCommand.DVI_FONT1 + 1;
          if (wantSelectFont()) {
            final int fn;

            fn = in.readU(alen);
            ctx.commandDetermined();
            doSelectFont(fn);
          } else {
            in.skip(alen);
          }
          break;
        }

        case DviCommand.DVI_XXX4:
        case DviCommand.DVI_XXX3:
        case DviCommand.DVI_XXX2:
        case DviCommand.DVI_XXX1: {
          final int alen = t - DviCommand.DVI_XXX1 + 1;
          final int k;
          k = in.readU(alen);
          if (wantSpecial()) {
            final byte [] xxx = new byte[k];
            in.readFully(xxx);

            ctx.commandDetermined();
            doSpecial(xxx);
          } else {
            in.skip(alen + k);
          }
          break;
        }

        case DviCommand.DVI_SET_RULE: {
          if (wantSetRule()) {
            final int h, w;

            h = in.readS4();
            w = in.readS4();

            ctx.commandDetermined();
            doSetRule(w, h);
          } else {
            in.skip(4 + 4);
          }
          break;
        }

        case DviCommand.DVI_PUT_RULE: {
          if (wantPutRule()) {
            final int h, w;

            h = in.readS4();
            w = in.readS4();

            ctx.commandDetermined();
            doPutRule(w, h);
          } else {
            in.skip(4 + 4);
          }
          break;
        }

        case DviCommand.DVI_FNT_DEF4:
        case DviCommand.DVI_FNT_DEF3:
        case DviCommand.DVI_FNT_DEF2:
        case DviCommand.DVI_FNT_DEF1: {
          final int alen = t - DviCommand.DVI_FNT_DEF1 + 1;
          if (wantDefineFont()) {
            final int fn, cs, ss, ds, al, nl;
            final byte [] fontName;

            fn = in.readU(alen);
            cs = in.readS4();
            ss = in.readS4();
            ds = in.readS4();
            al = in.readU1();
            nl = in.readU1();

            fontName = new byte[al+nl];
            in.readFully(fontName);

            ctx.commandDetermined();
            doDefineFont(
              fn,
              DviFontSpec.getInstance(cs, ss, ds, al, nl, fontName)
            );
          } else {
            final int al, nl;
            in.skip(alen + 4 + 4 + 4);
            al = in.readU1();
            nl = in.readU1();
            in.skip(al + nl);
          }
          break;
        }

        case DviCommand.DVI_RIGHT4:
        case DviCommand.DVI_RIGHT3:
        case DviCommand.DVI_RIGHT2:
        case DviCommand.DVI_RIGHT1: {
          final int alen = t - DviCommand.DVI_RIGHT1 + 1;
          if (wantRight()) {
            final int dh;

            dh = in.readS(alen);

            ctx.commandDetermined();
            doRight(dh);
          } else {
            in.skip(alen);
          }
          break;
        }

        case DviCommand.DVI_W4:
        case DviCommand.DVI_W3:
        case DviCommand.DVI_W2:
        case DviCommand.DVI_W1: {
          final int alen = t - DviCommand.DVI_W1 + 1;
          if (wantW()) {
            final int dh;

            dh = in.readS(alen);

            ctx.commandDetermined();
            doW(dh);
          } else {
            in.skip(alen);
          }
          break;
        }
        case DviCommand.DVI_W0: {
          if (wantW0()) {
            ctx.commandDetermined();
            doW0();
          }
          break;
        }

        case DviCommand.DVI_X4:
        case DviCommand.DVI_X3:
        case DviCommand.DVI_X2:
        case DviCommand.DVI_X1: {
          final int alen = t - DviCommand.DVI_X1 + 1;
          if (wantX()) {
            final int dh;

            dh = in.readS(alen);

            ctx.commandDetermined();
            doX(dh);
          } else {
            in.skip(alen);
          }
          break;
        }
        case DviCommand.DVI_X0: {
          if (wantX0()) {
            ctx.commandDetermined();
            doX0();
          }
          break;
        }


        case DviCommand.DVI_DOWN4:
        case DviCommand.DVI_DOWN3:
        case DviCommand.DVI_DOWN2:
        case DviCommand.DVI_DOWN1: {
          final int alen = t - DviCommand.DVI_DOWN1 + 1;
          if (wantDown()) {
            final int dv;

            dv = in.readS(alen);

            ctx.commandDetermined();
            doDown(dv);
          } else {
            in.skip(alen);
          }
          break;
        }

        case DviCommand.DVI_Y4:
        case DviCommand.DVI_Y3:
        case DviCommand.DVI_Y2:
        case DviCommand.DVI_Y1: {
          final int alen = t - DviCommand.DVI_Y1 + 1;
          if (wantY()) {
            final int dv;

            dv = in.readS(alen);

            ctx.commandDetermined();
            doY(dv);
          } else {
            in.skip(alen);
          }
          break;
        }
        case DviCommand.DVI_Y0: {
          if (wantY0()) {
            ctx.commandDetermined();
            doY0();
          }
          break;
        }

        case DviCommand.DVI_Z4:
        case DviCommand.DVI_Z3:
        case DviCommand.DVI_Z2:
        case DviCommand.DVI_Z1: {
          final int alen = t - DviCommand.DVI_Z1 + 1;
          if (wantZ()) {
            final int dv;

            dv = in.readS(alen);

            ctx.commandDetermined();
            doZ(dv);
          } else {
            in.skip(alen);
          }
          break;
        }
        case DviCommand.DVI_Z0: {
          if (wantZ0()) {
            ctx.commandDetermined();
            doZ0();
          }
          break;
        }

        case DviCommand.DVI_PUSH: {
          if (wantPush()) {
            ctx.commandDetermined();
            doPush();
          }
          break;
        }
        case DviCommand.DVI_POP: {
          if (wantPop()) {
            ctx.commandDetermined();
            doPop();
          }
          break;
        }

        case DviCommand.DVI_NOP: {
          if (wantNop()) {
            ctx.commandDetermined();
            doNop();
          }
          break;
        }

        case DviCommand.DVI_BOP: {
          if (wantBop()) {
            final int [] count = new int [10];
            final int backPointer;

            for (int i=0; i<10; i++) 
              count[i] = in.readS4();
            backPointer = in.readS4();

            ctx.commandDetermined();
            doBop(
              new DviBop(count, backPointer)
            );
          } else {
            in.skip(10 * 4 + 4);
          }
          break;
        }
        case DviCommand.DVI_EOP: {
          if (wantEop()) {
            ctx.commandDetermined();
            doEop();
          }
          break;
        }
        case DviCommand.DVI_PRE: {
          if (wantPre()) {
            final int idByte, num, den, mag, commentSize;

            idByte      = in.readU1();
            num         = in.readS4();
            den         = in.readS4();
            mag         = in.readS4();
            commentSize = in.readU1();

            final byte [] comment = new byte [commentSize];
            in.readFully(comment);

            ctx.commandDetermined();
            doPre(
              new DviPreamble(
                idByte,
                DviUnit.getInstance(num, den, mag),
                comment
              )
            );
          } else {
            final int commentSize;
            in.skip(1 + 4 + 4 + 4);
            commentSize = in.readU1();
            in.skip(commentSize);
          }
          break;
        }
        case DviCommand.DVI_POST: {
          if (wantPost()) {
            final int firstBackPointer;
            final int num, den, mag, maxV, maxH, maxStackDepth, totalPages;

            firstBackPointer = in.readS4();
            num              = in.readS4();
            den              = in.readS4();
            mag              = in.readS4();
            maxV             = in.readS4();
            maxH             = in.readS4();
            maxStackDepth    = in.readU2();
            totalPages       = in.readU2();

            ctx.commandDetermined();
            doPost(
              new DviPostamble(
                firstBackPointer,
                DviUnit.getInstance(num, den, mag),
                maxV, maxH,
                maxStackDepth,
                totalPages
              )
            );
          } else {
            in.skip(6 * 4 + 2 * 2);
          }
          break;
        }
        case DviCommand.DVI_POST_POST: {
          if (wantPostPost()) {
            final int postamblePointer;
            final int idByte;

            postamblePointer = in.readS4();
            idByte           = in.readU1();

            ctx.commandDetermined();
            doPostPost(
              new DviPostPost(
                postamblePointer,
                idByte
              )
            );
          } else {
            in.skip(4 + 1);
          }
          ctx.setTerminate(true);
          break;
        }
        case DviCommand.DVI_UNDEF1:
        case DviCommand.DVI_UNDEF2:
        case DviCommand.DVI_UNDEF3:
        case DviCommand.DVI_UNDEF4:
        case DviCommand.DVI_UNDEF5: {
          ctx.commandDetermined();
          /* TODO: handle these commands. */
          break;
        }
          
        case DviCommand.DVI_UNDEF6: {
          ctx.commandDetermined();
          /* FIXME: handle TDIR */
          break;
        }

        default:
          /* not reached. */
          throw new IllegalStateException
            ("Illegal executer state.");
      }
    }
  }

  public boolean wantSet()        { return true; }
  public boolean wantPut()        { return true; }
  public boolean wantSelectFont() { return true; }
  public boolean wantSpecial()    { return true; }
  public boolean wantSetRule()    { return true; }
  public boolean wantPutRule()    { return true; }
  public boolean wantDefineFont() { return true; }
  public boolean wantRight()      { return true; }
  public boolean wantW()          { return true; }
  public boolean wantW0()         { return true; }
  public boolean wantX()          { return true; }
  public boolean wantX0()         { return true; }
  public boolean wantDown()       { return true; }
  public boolean wantY()          { return true; }
  public boolean wantY0()         { return true; }
  public boolean wantZ()          { return true; }
  public boolean wantZ0()         { return true; }
  public boolean wantPush()       { return true; }
  public boolean wantPop()        { return true; }
  public boolean wantNop()        { return true; }
  public boolean wantBop()        { return true; }
  public boolean wantEop()        { return true; }
  public boolean wantPre()        { return true; }
  public boolean wantPost()       { return true; }
  public boolean wantPostPost()   { return true; }

  public void begin(DviExecutorContext ctx) throws DviException {
    this.ctx.handler.begin(ctx);
  }
  public void end() throws DviException {
    ctx.handler.end();
  }

  public void doSet(int code) throws DviException {
    ctx.handler.doSet(code);
  }
  public void doSetRule(int w, int h) throws DviException {
    ctx.handler.doSetRule(w, h);
  }
  public void doPut(int code) throws DviException {
    ctx.handler.doPut(code);
  }
  public void doPutRule(int w, int h) throws DviException {
    ctx.handler.doPutRule(w, h);
  }
  public void doNop() throws DviException {
    ctx.handler.doNop();
  }

  public void doSelectFont(int fn) throws DviException {
    ctx.handler.doSelectFont(fn);
  }
  public void doDefineFont(int fn, DviFontSpec fs) throws DviException {
    ctx.handler.doDefineFont(fn, fs);
  }

  public void doPush() throws DviException {
    ctx.handler.doPush();
  }
  public void doPop() throws DviException {
    ctx.handler.doPop();
  }

  public void doPre(DviPreamble preamble) throws DviException {
    ctx.handler.doPre(preamble);
  }
  public void doBop(DviBop bop) throws DviException {
    ctx.handler.doBop(bop);
  }
  public void doEop() throws DviException {
    ctx.handler.doEop();
  }
  public void doPost(DviPostamble postamble) throws DviException {
    ctx.handler.doPost(postamble);
  }
  public void doPostPost(DviPostPost postPost) throws DviException {
    ctx.handler.doPostPost(postPost);
  }

  public void doRight(int by) throws DviException {
    ctx.handler.doRight(by);
  }
  public void doW(int by) throws DviException {
    ctx.handler.doW(by);
  }
  public void doW0() throws DviException {
    ctx.handler.doW0();
  }
  public void doX(int by) throws DviException {
    ctx.handler.doX(by);
  }
  public void doX0() throws DviException {
    ctx.handler.doX0();
  }

  public void doDown(int by) throws DviException {
    ctx.handler.doDown(by);
  }
  public void doY(int by) throws DviException {
    ctx.handler.doY(by);
  }
  public void doY0() throws DviException {
    ctx.handler.doY0();
  }
  public void doZ(int by) throws DviException {
    ctx.handler.doZ(by);
  }
  public void doZ0() throws DviException {
    ctx.handler.doZ0();
  }

  public void doSpecial(byte [] xxx) throws DviException {
    ctx.handler.doSpecial(xxx);
  }
}
