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

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Vector;

import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviObject;
import jp.sourceforge.dvibrowser.dvicore.DviUnit;
import jp.sourceforge.dvibrowser.dvicore.api.DviContextSupport;
import jp.sourceforge.dvibrowser.dvicore.util.DviUtils;


public class EmbeddedPostScript
{
//  private static final Logger LOGGER = Logger.getLogger(EmbeddedPostScript.class.getName());
  
  private final Vector<Prologue> prologues = new Vector<Prologue>();
  private final Vector<Global> globals = new Vector<Global>();
  private final HashMap<Integer, Vector<Local>> page2locals
    = new HashMap<Integer, Vector<Local>>();

  private Vector<Local> locals = null;

  public void beginPage(int pageNum)
  {
    if (page2locals.containsKey(pageNum)) {
      locals = page2locals.get(pageNum);
    } else {
      locals = new Vector<Local>();
      page2locals.put(pageNum, locals);
    }
  }

  public void endPage()
  {
    locals = null;
  }

  public void add(Prologue a)
  {
    prologues.add(a);
  }

  public void add(Global a)
  {
    globals.add(a);
  }

  public void add(Local a)
  {
    locals.add(a);
  }

  private static void writePostScript(Vector<? extends Element> ce,
      PrintWriter pw, Config cfg) throws DviException
  {
    for (Element e : ce) {
      e.writePostScript(pw, cfg);
    }
  }

  public String toPostScript(int pageNum, int dpi) 
  throws DviException
  {
    if (!page2locals.containsKey(pageNum))
      return null;

    Vector<Local> ls = page2locals.get(pageNum);

    if (ls == null || ls.size() == 0)
      return null;

    Config cfg = new Config(dpi);

    try {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw);

      pw.println("%!PS-Adobe-2.0");
      pw.println("%%Pages: 1");
      pw.println("%%BoundingBox: 0 0 595 842"); // Change bounding box depending on paper sizes.
      pw.println("%%EndComments");
      pw.println("%!");

      writePostScript(prologues, pw, cfg);

      // TODO: Use the paper size to set the bounding boxes
      pw.println(
          "TeXDict begin"
        + " 39158280 55380996 "
        + " 1000 "
        + dpi + " " + dpi
        + " (a.dvi) "
        + " @start end "
      );
      pw.println("TeXDict begin 1 0 bop 0 0 a");

      writePostScript(globals, pw, cfg);

      writePostScript(ls, pw, cfg);

      pw.println("end");
      pw.println("showpage");
      pw.close();
      sw.flush();
      return sw.toString();
    } catch (Exception ex) {
      throw new DviException(ex);
    }
  }

  private static class Config
  {
    public final int dpi;
    private Config(int dpi) {
      this.dpi = dpi;
    }
  }

  private static interface Element
  {
    public abstract void writePostScript(PrintWriter pw, Config cfg) throws DviException;
  }

  public static interface Local extends Element {}
  public static interface Global extends Element {}
  public static interface Prologue extends Element {}

  public static class ProloguePostScript
  {
    public final String postScript;
    public ProloguePostScript(String postScript) {
      this.postScript = postScript;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      pw.println(postScript);
    }
  }
  
  // TODO: support encoding.
  private static String escapeFilenameForPS(String s)
  {
    return s.replaceAll("\\\\", "\\\\\\\\");
  }

  public static class PrologueFile
  extends DviObject
  implements Prologue
  {
    public final String fileName;
    public PrologueFile(DviContextSupport dcs, String fileName) {
      super(dcs);
      this.fileName = fileName;
    }

    public void writePostScript(PrintWriter pw, Config cfg) throws DviException
    {
      File file = DviUtils.toLocalFile(getDviContext().getDviResource(fileName));
      if (file == null || !file.exists())
        throw new DviException("Cannot find postscript prologue: " + fileName);

      String escapedFilename = escapeFilenameForPS(file.getAbsolutePath());
      pw.println(
          " (" + escapedFilename + ") run"
      );
    }
  }

  public static class HeaderSpecial
  implements Global
  {
    public final String fileName;
    public HeaderSpecial(String fileName) {
      this.fileName = fileName;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      String escapedFilename = escapeFilenameForPS(fileName);
      pw.println(
        " (" + escapedFilename + ") run"
      );
    }
  }

  public static class BangSpecial
  implements Global
  {
    public final String postScript;
    public BangSpecial(String postScript) {
      this.postScript = postScript;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      pw.println(
        " @defspecial " + postScript + " @fedspecial"
      );
    }
  }

  private static class WithReferencePoint
  implements Local
  {
    public final int h;
    public final int v;
    public final DviUnit dviUnit;
    private WithReferencePoint(int h, int v, DviUnit dviUnit)
    {
      this.h = h;
      this.v = v;
      this.dviUnit = dviUnit;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      double psH = dviUnit.mapToPixelDouble(h, cfg.dpi);
      double psV = dviUnit.mapToPixelDouble(v, cfg.dpi);
      pw.println(
        " " + psH + " " + psV + " moveto "
      );
    }
  }

  public static class PSFileSpecial
  extends WithReferencePoint
  {
    public final String fileName;
    public final int llx;
    public final int lly;
    public final int urx;
    public final int ury;
    public final int rwi;
    public final int rhi;
    public final int angle;
    public PSFileSpecial(
      int h, int v, DviUnit dviUnit,
      String fileName,
      int llx, int lly, int urx, int ury,
      int rwi, int rhi, int angle
    ) {
      super(h, v, dviUnit);
      this.fileName = fileName;
      this.llx = llx;
      this.lly = lly;
      this.urx = urx;
      this.ury = ury;
      this.rwi = rwi;
      this.rhi = rhi;
      this.angle = angle;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      super.writePostScript(pw, cfg);
      String escapedFilename = escapeFilenameForPS(fileName);
//      if (new File(realFile).exists()) {
        pw.println(
            " @beginspecial"
          + " " + llx + " @llx"
          + " " + lly + " @lly"
          + " " + urx + " @urx"
          + " " + ury + " @ury"
          + ((rwi != 0) ? (" " + rwi + " @rwi") : "")
          + ((rhi != 0) ? (" " + rhi + " @rhi") : "")
          + ((angle != 0) ? (" " + angle + " @angle") : "")
          + " @setspecial"
          + " (" + escapedFilename + ") run"
          + " @endspecial"
        );
//      } else {
//      }
    }
  }

  public static class PSSpecial
  extends WithReferencePoint
  {
    public final String postScript;
    public PSSpecial(int h, int v, DviUnit dviUnit, String postScript)
    {
      super(h, v, dviUnit);
      this.postScript = postScript;
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      super.writePostScript(pw, cfg);
      pw.println(
        " " + postScript
      );
    }
  }

  public static class QuoteSpecial
  extends PSSpecial
  {
    public QuoteSpecial(int h, int v, DviUnit dviUnit, String postScript)
    {
      super(h, v, dviUnit, postScript);
    }

    public void writePostScript(PrintWriter pw, Config cfg)
    {
      super.writePostScript(pw, cfg);
      pw.println(
          " @beginspecial"
        + " @setspecial"
        + " " + postScript
        + " @endspecial"
      );
    }
  }
}
