// A Lisp Driver to be embedded in Java Applications

// The contents of this file are subject to the Mozilla Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is JAKLD code, released November 26, 2002.
//
// The Initial Developer of the Original Code is Taiichi Yuasa.
// Portions created by Taiichi Yuasa are Copyright (C) 2002
// Taiichi Yuasa. All Rights Reserved.
//
// Contributor(s): Taiichi Yuasa <yuasa@kuis.kyoto-u.ac.jp>

import java.io.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.*;
import java.lang.reflect.Array;

public class Picture extends JPanel {

  private static JFrame frame = null;
  private static Picture canvas = null;
  private static BufferedImage imageBuf = null;
  private static boolean imageBufModified = false;
  private static Graphics2D bufGraphics = null;
  private static int bufColor = 0;
  private static WritableRaster bufRaster = null;
  private static DataBuffer bufDataBuffer = null;

  private static int unitPixels = 512;
  private static int imageWidth = 512;
  private static int imageHeight = 512;
  private static double pixelSize = 1.0 / unitPixels;

  public void paintComponent(Graphics g) {
    super.paintComponent(g);

    g.drawImage(imageBuf, 0, 0, canvas);

    imageBufModified = false;
  }

  private static void startPicture1() {
    canvas = new Picture();
    canvas.setPreferredSize(new Dimension(imageWidth, imageHeight));

    frame = new JFrame("JAKLD Picture");
    frame.add(canvas, BorderLayout.CENTER);
    frame.pack();
    frame.setVisible(true);

    imageBuf = new BufferedImage(imageWidth, imageHeight,
                                  BufferedImage.TYPE_INT_RGB);
    bufGraphics = imageBuf.createGraphics();
    bufGraphics.setBackground(Color.white);
    bufGraphics.clearRect(0, 0, imageWidth, imageHeight);
    bufGraphics.setColor(Color.black);
    bufColor = Color.black.getRGB();

    bufRaster = imageBuf.getRaster();
    bufDataBuffer = bufRaster.getDataBuffer();
  }

  static { Subr.def("Picture", "startPicture", "start-picture", 0, 3); }
  public static Boolean startPicture(Integer up, Integer pw, Integer ph) {
    if (frame != null) throw Eval.error("picture already started");

    if (up != null) {
      unitPixels = up.intValue();
      pixelSize = 1.0 / unitPixels;
      if (pw != null) {
        imageWidth = pw.intValue();
        if (ph != null) {
          imageHeight = ph.intValue();
        } else {
          imageHeight = imageWidth;
        }
      } else {
        imageHeight = imageWidth = unitPixels;
      }
    }

    javax.swing.SwingUtilities.invokeLater(new Thread()
    {public void run() { startPicture1(); }});

    (new Thread() {public void run() {
      for (;;) {
        try {
          Thread.sleep(100);
        } catch (InterruptedException e) {}
        if (imageBufModified) {
          canvas.repaint();
        }
      }
      }}).start();

    IO.loadFromJarFile("picture.lsp");

    return true;
  }

  static { Subr.def("Picture", "setBgColor", "set-bg-color", 1); }
  public static Boolean setBgColor(Object color) {
    if (frame == null) return false;
    bufGraphics.setBackground(makeColor(color));
    bufGraphics.clearRect(0, 0, imageWidth, imageHeight);
    canvas.repaint();
    return true;
  }

  static { Subr.def("Picture", "setColor", "set-color", 1, 1); }
  public static Boolean setColor(Object color, Boolean redrawp) {
    if (frame == null) return false;
    Color c = makeColor(color);
    bufGraphics.setColor(c);
    bufColor = c.getRGB();
    return true;
  }

  private final static Symbol Sblack = Symbol.intern("black");
  private final static Symbol Sblue = Symbol.intern("blue");
  private final static Symbol Scyan = Symbol.intern("cyan");
  private final static Symbol SdarkGray = Symbol.intern("dark-gray");
  private final static Symbol Sgray = Symbol.intern("gray");
  private final static Symbol Sgreen = Symbol.intern("green");
  private final static Symbol SlightGray = Symbol.intern("light-gray");
  private final static Symbol Smagenta = Symbol.intern("magenta");
  private final static Symbol Sorange = Symbol.intern("orange");
  private final static Symbol Spink = Symbol.intern("pink");
  private final static Symbol Sred = Symbol.intern("red");
  private final static Symbol Swhite = Symbol.intern("white");
  private final static Symbol Syellow = Symbol.intern("yellow");

  private static Color makeColor(Object color) {
    if (color instanceof Integer) {
      return new Color(((Integer) color).intValue());
    } else if (color instanceof Symbol) {
      if (color == Sblack) {
        return Color.black;
      } else if (color == Sblue) {
        return Color.blue;
      } else if (color == Scyan) {
        return Color.cyan;
      } else if (color == SdarkGray) {
        return Color.darkGray;
      } else if (color == Sgray) {
        return Color.gray;
      } else if (color == Sgreen) {
        return Color.green;
      } else if (color == SlightGray) {
        return Color.lightGray;
      } else if (color == Smagenta) {
        return Color.magenta;
      } else if (color == Sorange) {
        return Color.orange;
      } else if (color == Spink) {
        return Color.pink;
      } else if (color == Sred) {
        return Color.red;
      } else if (color == Swhite) {
        return Color.white;
      } else if (color == Syellow) {
        return Color.yellow;
      } else {
        throw Eval.error("unknown color name "+color);
      }
    } else {
      throw Eval.error("invalid color "+ color);
    }
  }

  static { Subr.def("Picture", "showPicture", "show-picture", 0); }
  public static Boolean showPicture() {
    if (frame == null) return false;
    frame.setVisible(true);
    return true;
  }

  static { Subr.def("Picture", "hidePicture", "hide-picture", 0); }
  public static Boolean hidePicture() {
    if (frame == null) return false;
    frame.setVisible(false);
    return true;
  }

  static { Subr.def("Picture", "redrawPicture", "redraw-picture", 0); }
  public static Boolean redrawPicture() {
    if (canvas == null) return false;
    canvas.repaint();
    return true;
  }

  static { Subr.def("Picture", "clearPicture", "clear-picture", 0); }
  public static Boolean clearPicture() {
    if (canvas == null) return false;
    bufGraphics.clearRect(0, 0, imageWidth, imageHeight);
    canvas.repaint();
    return true;
  }

  static { Subr.def("Picture", "drawLine", "draw-line", 2); }
  public static Boolean drawLine(Pair start, Pair end) {
    if (canvas == null) return false;
    bufGraphics.drawLine
    ((int) (((Number) start.car).doubleValue()*unitPixels),
     (int) (imageHeight - ((Number) start.cdr).doubleValue()*unitPixels),
     (int) (((Number) end.car).doubleValue()*unitPixels),
     (int) (imageHeight - ((Number) end.cdr).doubleValue()*unitPixels));
    imageBufModified = true;
    return true;
  }

  static { Subr.def("Picture", "drawPolygon", "draw-polygon", 2); }
  public static Boolean drawPolygon(List verl, Boolean fillp) {
    if (canvas == null) return false;
    int len = 0;
    for (List vl = verl; vl != List.nil; vl = (List) vl.cdr) len++;
    int[] xpoints = new int[len];
    int[] ypoints = new int[len];
    for (int ix = 0; ix < len; verl = (List) verl.cdr, ix++) {
      Pair vertex = (Pair) verl.car;
      xpoints[ix] = (int) (((Number) vertex.car).doubleValue()*unitPixels);
      ypoints[ix] = (int) (imageHeight
                            - ((Number) vertex.cdr).doubleValue()*unitPixels);
    }
    Polygon poly = new Polygon(xpoints, ypoints, len);
    if (fillp.booleanValue()) {
      bufGraphics.fill(poly);
    } else {
      bufGraphics.draw(poly);
    }
    imageBufModified = true;
    return true;
  }

  static { Subr.def("Picture", "readImage", "read-image", 1); }
  public static BufferedImage readImage(String fname) {
    BufferedImage image;

    try {
      image = ImageIO.read(new File(fname));
    } catch (IOException e) {
      throw Eval.error("cannot read image file");
    }
    if (image == null)
      throw Eval.error("cannot read image file");

    // Scale the image so that the longer dimension becomes equal to
    // the unitPixels pixels.
    int width = image.getWidth();
    int height = image.getHeight();

    if (width > 0 && height > 0) {
      // Do this only when the image size is known.
      double scale = unitPixels;
      //scale /= (width >= height ? width : height);
      //AffineTransform atf = AffineTransform.getScaleInstance(scale, scale);
      AffineTransform atf
      = AffineTransform.getScaleInstance(scale/width, scale/height);
      AffineTransformOp atfop
      = new AffineTransformOp(atf,AffineTransformOp.TYPE_NEAREST_NEIGHBOR);

      image = atfop.filter(image, null);
    }
    return image;
  }

  static { Subr.def("Picture", "drawImage", "draw-image", 2); }
  public static Boolean drawImage(BufferedImage image, List frm) {
    if (canvas == null) return false;

    Pair origin = (Pair) frm.car;
    double c1 = ((Number) origin.car).doubleValue();
    double c2 = ((Number) origin.cdr).doubleValue();
    frm = (List) frm.cdr;
    Pair edge1 =  (Pair) frm.car;
    double a1 = ((Number) edge1.car).doubleValue();
    double a2 = ((Number) edge1.cdr).doubleValue();
    frm = (List) frm.cdr;
    Pair edge2 =  (Pair) frm.car;
    double b1 = ((Number) edge2.car).doubleValue();
    double b2 = ((Number) edge2.cdr).doubleValue();

    // Frame ((c1 . c2) (a1 . a2) (b1 . b2)) defines tranform from
    //   (x0, y0)
    // to
    //   (a1*x0 + b1*y0 + c1, a2*x0 + b2*y0 + c2)
    // in the Cartesian coordinate system used in mathematics.
    // These points are denoted as
    //   (x1, y1) = (u*x0, h-u*y0)
    // and
    //   (u*(a1*x0 + b1*y0 + c1), h - u*(a2*x0 + b2*y0 + c2))
    // in the coordinate system used in Java graphics, 
    // where u is the unitPixels and h is the imageHeight.
    // By replacing u*x0 and u*y0 with x1 and h-y1, respectively,
    // the last formula becomes
    //   (a1*x1 + (-b1)*y1 + (b1*h + c1*u),
    //      (-a2)*x1 + b2*y1 + (h - b2*h - c2*u))
    // which defines the Affine transform.

    AffineTransform atf
    = new AffineTransform(a1, -a2, -b1, b2, 
                           b1*imageHeight + c1*unitPixels,
                           imageHeight*(1-b2) - c2*unitPixels);

    bufGraphics.drawImage(image, atf, null);

    imageBufModified = true;
    return true;
  }

  private static String[] formatNames = ImageIO.getWriterFormatNames();

  static { Subr.def("Picture", "savePicture", "save-picture", 1); }
  public static Boolean savePicture(String fname) {

    int ix = fname.lastIndexOf('.');
    if (ix <= 0 || ix == Array.getLength(formatNames) - 1)
      throw Eval.error("no file type " + fname);
    String ftype = fname.substring(ix + 1);
    for(int i=0; i<Array.getLength(formatNames); i++)
      if (ftype.equals(formatNames[i])) {
        try {
          ImageIO.write(imageBuf, ftype, new File(fname));
        } catch (IOException e) {
          throw Eval.error("cannot save the picture");
        }
        return true;
      }
    throw Eval.error("unknown image type " + ftype);
  }

  static { Subr.def("Picture", "readPicture", "read-picture", 0); }
  public static BufferedImage readPicture() {

    BufferedImage out = new BufferedImage(imageWidth, imageHeight,
                                             BufferedImage.TYPE_INT_RGB);
    WritableRaster outRaster = out.getRaster();
    DataBuffer outDataBuffer = outRaster.getDataBuffer();
    for (int x=0; x<imageWidth; x++)
      for (int y=0; y<imageHeight; y++)
        outDataBuffer.setElem(x+y*imageWidth, 
                               bufDataBuffer.getElem(x+y*imageWidth));
    return out;
  }

  static { Subr.def("Picture", "drawPoint", "draw-point", 1); }
  public static Boolean drawPoint(Pair point) {
    int x = (int)(((Number) point.car).doubleValue()*unitPixels);
    int y = (int)(imageHeight -((Number) point.cdr).doubleValue()*unitPixels);
    if (x >= 0 && x < imageWidth && y >=0 && y < imageHeight) {
      bufDataBuffer.setElem(x+y*imageWidth, bufColor);
      imageBufModified = true;
      return true;
    } else {
      return false;
    }
  }

  static { Subr.def("Picture", "readProcedure", "read-procedure", 2); }
  public static BufferedImage readProcedure(Function f, boolean verbose) {

    BufferedImage out = new BufferedImage(imageWidth, imageHeight,
                                             BufferedImage.TYPE_INT_RGB);
    WritableRaster outRaster = out.getRaster();
    DataBuffer outDataBuffer = outRaster.getDataBuffer();
    for (int x=0; x<imageWidth; x++) {
      if (verbose) IO.print("*");
      for (int y=0; y<imageHeight; y++) {
        Integer val = (Integer) f.invoke1(new Pair(x*pixelSize, y*pixelSize));
        outDataBuffer.setElem(x+(imageHeight-y-1)*imageWidth, val.intValue());
      }
    }
    return out;
  }

  static void init() {
  }
}
