/*
 * $Id: NonRectangular.java 8078 2012-02-12 22:25:15Z uckelman $
 *
 * Copyright (c) 2003 by Rodney Kinney
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.counters;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Window;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.StringTokenizer;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;

import VASSAL.build.module.documentation.HelpFile;
import VASSAL.command.Command;
import VASSAL.tools.image.ImageUtils;
import VASSAL.tools.imageop.Op;

/**
 * A trait for assigning an arbitrary shape to a {@link GamePiece}
 * 
 * @see GamePiece#getShape
 */
public class NonRectangular extends Decorator implements EditablePiece {
  public static final String ID = "nonRect;";
  private static HashMap<String,Shape> shapeCache = new HashMap<String,Shape>();

  private String type;
  private Shape shape;

  public NonRectangular() {
    this(ID, null);
  }

  public NonRectangular(String type, GamePiece inner) {
    mySetType(type);
    setInner(inner);
  }

  public void mySetState(String newState) {
  }

  public String myGetState() {
    return "";
  }

  public String myGetType() {
    return type;
  }

  protected KeyCommand[] myGetKeyCommands() {
    return new KeyCommand[0];
  }

  public Command myKeyEvent(KeyStroke stroke) {
    return null;
  }

  public void draw(Graphics g, int x, int y, Component obs, double zoom) {
    piece.draw(g, x, y, obs, zoom);
  }

  public Rectangle boundingBox() {
    return piece.boundingBox();
  }

  public Shape getShape() {
    return shape != null ? shape : piece.getShape();
  }

  public String getName() {
    return piece.getName();
  }

  public String getDescription() {
    return "Non-Rectangular";
  }

  public void mySetType(String type) {
    this.type = type;
    final String shapeSpec = type.substring(ID.length());
    shape = buildPath(shapeSpec);
  }

  private Shape buildPath(String spec) {
    Shape sh = shapeCache.get(spec);

    if (sh == null) {
      final GeneralPath path = new GeneralPath();
      final StringTokenizer st = new StringTokenizer(spec, ",");
      if (st.hasMoreTokens()) {        
        while (st.hasMoreTokens()) {
          final String token = st.nextToken();
          switch (token.charAt(0)) {
          case 'c':
            path.closePath();
            break;
          case 'm':
            path.moveTo(Integer.parseInt(st.nextToken()),
                        Integer.parseInt(st.nextToken()));
            break;
          case 'l':
            path.lineTo(Integer.parseInt(st.nextToken()),
                        Integer.parseInt(st.nextToken()));
            break;
          }
        }
        sh = new Area(path);       
        shapeCache.put(spec, sh);
      }
    }

    return sh;
  }

  public HelpFile getHelpFile() {
    return null;
  }

  public PieceEditor getEditor() {
    return new Ed(this);
  }

  private class Ed implements PieceEditor {
    private Shape shape;
    private JPanel controls;

    private Ed(NonRectangular p) {
      shape = p.shape;
      controls = new JPanel();
      controls.setLayout(new BoxLayout(controls, BoxLayout.X_AXIS));

      final JPanel shapePanel = new JPanel() {
        private static final long serialVersionUID = 1L;

        public void paint(Graphics g) {
          final Graphics2D g2d = (Graphics2D) g;
          g2d.setColor(Color.white);
          g2d.fillRect(0, 0, getWidth(), getHeight());
          if (shape != null) {
            g2d.translate(getWidth() / 2, getHeight() / 2);
            g2d.setColor(Color.black);
            g2d.fill(shape);
          }
        }

        public Dimension getPreferredSize() {
          final Dimension d = shape == null
            ? new Dimension(60, 60) : shape.getBounds().getSize();
          d.width = Math.max(d.width, 60);
          d.height = Math.max(d.height, 60);
          return d;
        }
      };
      controls.add(shapePanel);

      final ImagePicker picker = new ImagePicker() {
        private static final long serialVersionUID = 1L;

        public void setImageName(String name) {
          super.setImageName(name);

          final Image img = Op.load(name).getImage();
          if (img != null) setShapeFromImage(img);
        }
      };
      picker.setBorder(new TitledBorder("Use image shape"));
      controls.add(picker);
    }

    public void setShapeFromImage(Image im) {
      controls.getTopLevelAncestor()
              .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

      final BufferedImage bi = ImageUtils.toBufferedImage(im);
      final int w = bi.getWidth();
      final int h = bi.getHeight();
      final int[] pixels = bi.getRGB(0, 0, w, h, new int[w*h], 0, w);

      // build the outline in strips
      final Area outline = new Area();
      for (int y = 0; y < h; ++y) {
        int left = -1;
        for (int x = 0; x < w; ++x) {
          if (((pixels[x + y*w] >>> 24) & 0xff) > 0) {
            if (left < 0) {
              left = x;
            }
          }
          else if (left > -1) {
            outline.add(new Area(new Rectangle(left, y, x-left, 1)));
            left = -1;
          }
        }

        if (left > -1) {
          outline.add(new Area(new Rectangle(left, y, w-left, 1)));
        }
      }

// FIXME: should be 2.0 to avoid integer arithemtic?
      shape = AffineTransform.getTranslateInstance(-w / 2, -h / 2)
                             .createTransformedShape(outline);

      final  Window wd = SwingUtilities.getWindowAncestor(controls);
      if (wd != null) wd.pack();

      controls.getTopLevelAncestor().setCursor(null);
    }

    public Component getControls() {
      return controls;
    }

    public String getType() {
      final StringBuilder buffer = new StringBuilder();
      if (shape != null) {
        final PathIterator it = shape.getPathIterator(new AffineTransform());
        final float[] pts = new float[6];
        while (!it.isDone()) {
          switch (it.currentSegment(pts)) {
          case PathIterator.SEG_MOVETO:
            buffer.append('m')
                  .append(',')
                  .append(Math.round(pts[0]))
                  .append(',')
                  .append(Math.round(pts[1]));
            break;
          case PathIterator.SEG_LINETO:
          case PathIterator.SEG_CUBICTO:
          case PathIterator.SEG_QUADTO:
            buffer.append('l')
                  .append(',')
                  .append(Math.round(pts[0]))
                  .append(',')
                  .append(Math.round(pts[1]));
            break;
          case PathIterator.SEG_CLOSE:
            buffer.append('c');
            break;
          }
          it.next();
          if (!it.isDone()) {
            buffer.append(',');
          }
        }
      }
      return ID + buffer.toString();
    }

    public String getState() {
      return "";
    }
  }
}
