/*
 * $Id: OrthoRotateOpBitmapImpl.java 4483 2008-11-16 18:15:32Z uckelman $
 *
 * Copyright (c) 2007-2008 by Joel Uckelman
 *
 * 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.tools.imageop;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.List;

import VASSAL.tools.HashCode;
import VASSAL.tools.image.ImageUtils;

public class OrthoRotateOpBitmapImpl extends AbstractTiledOpImpl
                                     implements RotateOp {
  private final ImageOp sop;
  private final int angle;
  private final int hash;

  public OrthoRotateOpBitmapImpl(ImageOp sop, int angle) {
    if (sop == null) throw new IllegalArgumentException();

    angle = (360 + (angle % 360)) % 360;  // put angle in [0,360)
    if (angle % 90 != 0) throw new IllegalArgumentException();
 
    // angle is now in { 0, 90, 180, 270 }.

    this.sop = sop;
    this.angle = angle / 90;

    hash = HashCode.hash(sop) ^ HashCode.hash(angle);
  }

  public List<VASSAL.tools.opcache.Op<?>> getSources() {
    return Collections.<VASSAL.tools.opcache.Op<?>>singletonList(sop);
  }

  public BufferedImage eval() throws Exception {
    final BufferedImage src = sop.getImage(null);
    if (size == null) fixSize();

    // remain opaque if our parent image is
    final BufferedImage dst = ImageUtils.createCompatibleImage(
        size.width, size.height, src.getTransparency() != BufferedImage.OPAQUE
    );

    final Graphics2D g = dst.createGraphics();
    g.rotate(Math.PI/2.0*angle, src.getWidth()/2.0, src.getHeight()/2.0);
    g.drawImage(src, 0, 0, null);
    g.dispose();

    return dst;
  }

  protected void fixSize() {
    if ((size = getSizeFromCache()) == null) {
      size = sop.getSize();

      // transpose dimensions for 90- and 270-degree rotations
      if (angle == 1 || angle == 3) 
        size.setSize(size.height, size.width);
    }
  }

  public double getAngle() {
    return angle * 90;
  }

  public RenderingHints getHints() {
//    return ImageUtils.getDefaultHints();
    return null;
  }

  protected ImageOp createTileOp(int tileX, int tileY) {
    return new TileOp(this, tileX, tileY);
  }

  private static class TileOp extends AbstractTileOpImpl {
    private final ImageOp sop;
    private final int angle;
    private final int hash;
 
    public TileOp(OrthoRotateOpBitmapImpl rop, int tileX, int tileY) {
      if (rop == null) throw new IllegalArgumentException();

      if (tileX < 0 || tileX >= rop.getNumXTiles() ||
          tileY < 0 || tileY >= rop.getNumYTiles())
        throw new IndexOutOfBoundsException(); 

      this.angle = rop.angle;

      final int sx0, sy0, sx1, sy1;

      switch (angle) {
      case 0:
        sx0 = tileX*rop.tileSize.width;
        sy0 = tileY*rop.tileSize.height;
        sx1 = Math.min((tileX+1)*rop.tileSize.width, rop.size.width);
        sy1 = Math.min((tileY+1)*rop.tileSize.height, rop.size.height);
        break;
      case 1:
        // FIXME: test this
        sx0 = tileY*rop.tileSize.height;
        sy0 = tileX*rop.tileSize.width;
        sx1 = Math.min((tileY+1)*rop.tileSize.height, rop.size.height);
        sy1 = Math.min((tileX+1)*rop.tileSize.width, rop.size.width);
        break;
      case 2:
        sx1 = rop.size.width - tileX*rop.tileSize.width - 1;
        sy1 = rop.size.height - tileY*rop.tileSize.height - 1;
        sx0 = rop.size.width -
                Math.min((tileX+1)*rop.tileSize.width, rop.size.width) -1;
        sy0 = rop.size.height -
                Math.min((tileY+1)*rop.tileSize.height, rop.size.height) - 1;
        break;
      case 3:
      default:
        // FIXME: test this
        sx1 = rop.size.height - tileY*rop.tileSize.height - 1;
        sy1 = rop.size.width - tileX*rop.tileSize.width - 1;
        sx0 = rop.size.height -
                Math.min((tileY+1)*rop.tileSize.height, rop.size.height) - 1;
        sy0 = rop.size.width -
                Math.min((tileX+1)*rop.tileSize.width, rop.size.width) -1;
        break;
      }

      size = new Dimension(sx1-sx0, sy1-sy0);

      sop = new CropOpBitmapImpl(rop.sop, sx0, sy0, sx1, sy1);
                            
      hash = HashCode.hash(sop) ^ HashCode.hash(angle);
    }

    public List<VASSAL.tools.opcache.Op<?>> getSources() {
      return Collections.<VASSAL.tools.opcache.Op<?>>singletonList(sop);
    }

    public BufferedImage eval() throws Exception {
      final BufferedImage src = sop.getImage(null);

      // remain opaque if our parent image is
      final BufferedImage dst = ImageUtils.createCompatibleImage(
        size.width, size.height, src.getTransparency() != BufferedImage.OPAQUE
      );

      final Graphics2D g = dst.createGraphics();
      g.rotate(Math.PI/2.0*angle, src.getWidth()/2.0, src.getHeight()/2.0);
      g.drawImage(src, 0, 0, null);
      g.dispose();

      return dst; 
    }

    protected void fixSize() { }
  
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || !(o instanceof TileOp)) return false;

      final TileOp op = (TileOp) o;
      return angle == op.angle &&
             sop.equals(op.sop);
    }

    @Override
    public int hashCode() {
      return hash;
    }
  } 

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || !(o instanceof OrthoRotateOpBitmapImpl)) return false;
    
    final OrthoRotateOpBitmapImpl op = (OrthoRotateOpBitmapImpl) o;
    return angle == op.getAngle() && sop.equals(op.sop);
  }

  @Override
  public int hashCode() {
    return hash;
  }
}
