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

import java.util.Stack;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import dvi.DviException;
import dvi.DviObject;
import dvi.api.DviContextSupport;
import dvi.api.DviDocument;

public class SourceSpecialParser
extends DviObject
{
  private static final Logger LOGGER = Logger.getLogger(SourceSpecialParser.class.getName());
  private final AnchorSet anchorSet = new AnchorSet();
  private final PrivateExecuter exe;

  public SourceSpecialParser(DviContextSupport dcs)
  {
    super(dcs);
    exe = new PrivateExecuter(dcs, anchorSet);
  }

  private static final Pattern srcPat
    = Pattern.compile(
      "\\s*src:\\s*([-0-9]*)\\s\\s*(.*)\\s*",
      Pattern.CASE_INSENSITIVE
    );
  
  public void execute(DviDocument doc)
  throws DviException
  {
    for (int p=0; p<doc.getTotalPages(); p++) {
      exe.execute(doc.getPage(p));
    }
  }

  public AnchorSet getAnchorSet()
  {
    return anchorSet;
  }

  private static class PrivateExecuter
  extends AbstractDviSpecialExecutor
  {
    private final AnchorSet anchorSet;
    public PrivateExecuter(DviContextSupport dcs, AnchorSet anchorSet) {
      super(dcs);
      this.anchorSet = anchorSet;
    }

    private static class StackItem
    {
      private final long start;
      private final String tag;
      private final String data;
      private final int lineNumber;
      public StackItem(long start, String tag, String data, int lineNumber) {
        this.start = start;
        this.tag = tag;
        this.data = data;
        this.lineNumber = lineNumber;
      }
    }

    private final Stack<StackItem> stack
      = new Stack<StackItem>();
    
    @Override
    public void end() throws DviException {
      flushStack();
      super.end();
    }
    
    private void flushStack()
    {
      if (!stack.empty()) {
        StackItem it = stack.pop();
        DviExecutorContextImpl ctx = getExecutorContext();
        Anchor a = null;
        if ("src".equals(it.tag)) {
          a = new Anchor.Source(
            it.start,
            ctx.getCommandRange().end(),
            it.data,
            it.lineNumber
          );
        }
        anchorSet.add(a);
      }
    }

    @Override
    public void doSpecial(byte [] _xxx)
    throws DviException
    {
      String xxx = new String(_xxx);
      Matcher mat;
      DviExecutorContextImpl ctx = getExecutorContext();
      if ((mat = srcPat.matcher(xxx)).matches()) {
        flushStack();
        try {
          stack.push(
            new StackItem(
              ctx.getCommandRange().begin(),
              "src",
              mat.group(2), // filename
              Integer.parseInt(mat.group(1)) // linenumber
            )
          );
        } catch (NumberFormatException e) {
          LOGGER.warning(e.getMessage());
        }
      }
    }
  }
}
