package nxm.sys.libg;

import java.awt.Color;
import java.awt.Graphics;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Position;
import nxm.sys.lib.Shell;

/** PROTOTYPE: Supports Resizing of Features
 *  NOT HANDLING BOXES THAT WRAP BOUNDS YET
 *
 *  @author Jeanette McCall
 *  @since NeXtMidas 4.2.0
 */
@InternalUseOnly("PROTOTYPE only - not for use")
public class ResizeBox {
  /** Handle radius */
  static int hRadius = 2;
  static int sRadius = hRadius + 1;
  static Color selColor = Color.WHITE;
  private static boolean debugResizing = false;

  /** Handles are at corners, midway along each side, and in the middle */
  @InternalUseOnly("Public for testing purposes only")
  public static enum Handle {
    /** NONE - no Handle selected */
    NONE("NONE"),
    /** North West Handle */
    NW("NW"),
    /** North Handle */
    N("N"),
    /** North East Hanlde */
    NE("NE"),
    /** West Handle */
    W("W"),
    /** East Handle */
    E("E"),
    /** South West Handle */
    SW("SW"),
    /** South Handle */
    S("S"),
    /** South East Handle */
    SE("SE"),
    /** MOVE Handle */
    MOVE("MOVE");

    private final String name;
    private Handle (String name) {
      this.name = name;
    }
    @Override
    public String toString () {
      return this.name;
    }
  }

  /** Actual X,Y direction of expansion */
  @InternalUseOnly("Public for testing purposes only")
  public static enum Direction {
    /** None, no expansion */
    NONE("NONE"),
    /** Negative X and negative Y Direction expansion */
    NEGXY("NEGXY"),
    /** Negative Y Direction expansion */
    NEGY("NEGY"),
    /** Positive X and negative Y Direction expansion */
    POSXNEGY("POSXNEGY"),
    /** Negative X Direction expansion */
    NEGX("NEGX"),
    /** Positive X Direction expansion */
    POSX("POSX"),
    /** Negative X and positive Y Direction expansion */
    NEGXPOSY("NEGXPOSY"),
    /** Positive Y Direction expansion */
    POSY("POSY"),
    /** Positive X and positive Y Direction expansion */
    POSXY("POSXY"),
    /** Move, no expansion */
    MOVE("MOVE");

    private final String name;
    private Direction (String name) {
      this.name = name;
    }
    @Override
    public String toString () {
      return this.name;
    }
  }

  /** Do not allow instantiation */
  private ResizeBox () {}

  /** Draw a selected box at the Feature's bounding box
   *  This has small circle handles at the resize locations
   *
   *  @param feature Feature that is stretchable
   */
  static void drawSelectedBox (Feature feature) {
    int ix1 = feature.ix1Last;
    int ix2 = feature.ix2Last;
    int iy1 = feature.iy1Last;
    int iy2 = feature.iy2Last;
    int idx = ix2 - ix1;
    int idy = iy2 - iy1;
    Graphics gc = feature.MP.gc;
    gc.setColor(selColor);
    int diameter = hRadius+hRadius;
    int midx = (ix2+ix1)/2;
    int midy = (iy2+iy1)/2;
    gc.drawRect(ix1,iy1,idx,idy);
    gc.drawOval(ix1-hRadius,iy1-hRadius,diameter,diameter);
    gc.drawOval(ix2-hRadius,iy1-hRadius,diameter,diameter);
    gc.drawOval(ix2-hRadius,iy2-hRadius,diameter,diameter);
    gc.drawOval(ix1-hRadius,iy2-hRadius,diameter,diameter);
    gc.drawOval(ix1-hRadius,midy-hRadius,diameter,diameter);
    gc.drawOval(ix2-hRadius,midy-hRadius,diameter,diameter);
    gc.drawOval(midx-hRadius,iy1-hRadius,diameter,diameter);
    gc.drawOval(midx-hRadius,iy2-hRadius,diameter,diameter);
    gc.drawOval(midx-hRadius,midy-hRadius,diameter,diameter);
    gc.setColor(feature.MP.theme.cfg);
  }

  /** Gives the Handle near the cursor location
   *
   *  @param feature Feature that is stretchable
   *  @param px cursor x
   *  @param py cursor y
   *  @return Handle the cursor is near, or Handle.NONE if not near any of them
   */
  @InternalUseOnly("Public for testing purposes only")
  public static Handle getHandle(Feature feature, int px, int py) {
    int ix1 = feature.ix1Last;
    int ix2 = feature.ix2Last;
    int iy1 = feature.iy1Last;
    int iy2 = feature.iy2Last;
    int midx = (ix2+ix1)/2;
    int midy = (iy2+iy1)/2;
    if ((px>ix1-sRadius) && (px<ix1+sRadius)
        && (py>iy1-sRadius) && (py<iy1+sRadius)) return Handle.NW;
    if ((px>midx-sRadius) && (px<midx+sRadius)
        && (py>iy1-sRadius) && (py<iy1+sRadius)) return Handle.N;
    if ((px>ix2-sRadius) && (px<ix2+sRadius)
        && (py>iy1-sRadius) && (py<iy1+sRadius)) return Handle.NE;
    if ((px>ix1-sRadius) && (px<ix1+sRadius)
        && (py>midy-sRadius) && (py<midy+sRadius)) return Handle.W;
    if ((px>ix2-sRadius) && (px<ix2+sRadius)
        && (py>midy-sRadius) && (py<midy+sRadius)) return Handle.E;
    if ((px>ix1-sRadius) && (px<ix1+sRadius)
        && (py>iy2-sRadius) && (py<iy2+sRadius)) return Handle.SW;
    if ((px>midx-sRadius) && (px<midx+sRadius)
        && (py>iy2-sRadius) && (py<iy2+sRadius)) return Handle.S;
    if ((px>ix2-sRadius) && (px<ix2+sRadius)
        && (py>iy2-sRadius) && (py<iy2+sRadius)) return Handle.SE;
    if ((px>midx-sRadius) && (px<midx+sRadius)
        && (py>midy-sRadius) && (py<midy+sRadius)) return Handle.MOVE;
    return Handle.NONE;
  }

  /** Get name of matching cursor type from Handle
   *
   *  @param handle Active Handle
   *  @return name for appropriate cursor type
   */
  @InternalUseOnly("Public for testing purposes only")
  public static String cursorFromHandle (Handle handle) {
    String handleName = handle.toString();
    if ("MOVE".equals(handleName)) return handleName;
    if ("NONE".equals(handleName)) return "Default";
    return handleName + "Resize";
  }

  /** Get the direction of the resize in terms of X and Y
   *  @param handle ResizeBox handle, which gives resize direction on screen
   *  @param view   MPlot view
   *  @return Direction, which gives resize in terms of X and Y
   */
  @InternalUseOnly("Public for testing purposes only")
  public static Direction directionFromHandleView (Handle handle, int view) {
    boolean yxView   = ResizeBox.isYXView(view);
    boolean xInverse = ResizeBox.isInverseX(view);
    boolean yInverse = ResizeBox.isInverseY(view);
    boolean bothInverse = xInverse && yInverse;
    switch (handle) {
      case NW:
        if (yxView) {
          if (bothInverse) return Direction.POSXNEGY;
          if (xInverse   ) return Direction.POSXY;
          if (yInverse   ) return Direction.NEGXY;
          return Direction.NEGXPOSY;
        }
        if (bothInverse) return Direction.NEGXPOSY;
        if (xInverse   ) return Direction.NEGXY;
        if (yInverse   ) return Direction.POSXY;
        return Direction.POSXNEGY;
      case N:
        if (yxView) {
          if (yInverse   ) return Direction.NEGY;
          return Direction.POSY;
        }
        if (xInverse   ) return Direction.NEGX;
        return Direction.POSX;
      case NE:
        if (yxView) { // yes, this section can go ... keeping line number matching
          if (bothInverse) return Direction.NEGXY;
          if (xInverse   ) return Direction.NEGXPOSY;
          if (yInverse   ) return Direction.POSXNEGY;
          return Direction.POSXY;
        }
        if (bothInverse) return Direction.NEGXY;
        if (xInverse   ) return Direction.NEGXPOSY;
        if (yInverse   ) return Direction.POSXNEGY;
        return Direction.POSXY;
      case W:
        if (yxView) {
          if (xInverse   ) return Direction.POSX;
          return Direction.NEGX;
        }
        if (yInverse   ) return Direction.POSY;
        return Direction.NEGY;
      case E:
        if (yxView) {
          if (xInverse   ) return Direction.NEGX;
          return Direction.POSX;
        }
        if (yInverse   ) return Direction.NEGY;
        return Direction.POSY;
      case SW:
        if (yxView) { // yes, this section can go ... keeping line number matching
          if (bothInverse) return Direction.POSXY;
          if (xInverse   ) return Direction.POSXNEGY;
          if (yInverse   ) return Direction.NEGXPOSY;
          return Direction.NEGXY;
        }
        if (bothInverse) return Direction.POSXY;
        if (xInverse   ) return Direction.POSXNEGY;
        if (yInverse   ) return Direction.NEGXPOSY;
        return Direction.NEGXY;
      case S:
        if (yxView) {
          if (yInverse   ) return Direction.POSY;
          return Direction.NEGY;
        }
        if (xInverse   ) return Direction.POSX;
        return Direction.NEGX;
      case SE:
        if (yxView) {
          if (bothInverse) return Direction.NEGXPOSY;
          if (xInverse   ) return Direction.NEGXY;
          if (yInverse   ) return Direction.POSXY;
          return Direction.POSXNEGY;
        }
        if (bothInverse) return Direction.POSXNEGY;
        if (xInverse   ) return Direction.POSXY;
        if (yInverse   ) return Direction.NEGXY;
        return Direction.NEGXPOSY;
      case MOVE:
        return Direction.MOVE;
      case NONE:
        return Direction.NONE;
    }
    return Direction.NONE;
  }

  /** Update the Stretchable Feature's bounds based on current corner of Resize Box
   *
   *  @param feature Stretchable Feature
   *  @param handle  Resize Hanlde that is active
   *  @param curr    Current Position of cursor / corner of Resize Box
   *  @param view    MPlot view
   */
  static void updateBounds (Feature feature, Handle handle, Position curr, int view) {
    Direction direction = directionFromHandleView(handle,view);
    updateBounds(feature,direction,curr);
  }

  /** Update the Stretchable Feature's bounds based on the current corner of the Resize Box
   *
   *  @param feature   Stretchable Feature
   *  @param direction Actual X,Y direction of expansion
   *  @param curr      Current Position of cursor / corner of Resize Box
   *  @since NextMidas 4.2.1
   */
  @InternalUseOnly("Public for testing purposes only")
  public static void updateBounds (Feature feature, Direction direction, Position curr) {
    boolean featHasT = feature.toTable().containsKey("DT");
    if (debugResizing) {
      System.out.println("ResizeBox.updateBounds debug information");
      if (featHasT) System.out.println("    feature has T");
      System.out.println("    direction:"+direction);
      System.out.println("    resize box corner / cursor position:"+curr);
    }
    switch (direction) {
      case NEGXY:
        feature.setXMinMax(curr.x, feature.getXMax());
        feature.setYMinMax(curr.y, feature.getYMax());
        if (featHasT) feature.setTMinMax(curr.t, feature.getTMax());
        break;
      case NEGY:
        feature.setYMinMax(curr.y, feature.getYMax());
        if (featHasT) feature.setTMinMax(curr.t, feature.getTMax());
        break;
      case POSXNEGY:
        feature.setXMinMax(feature.getXMin(), curr.x);
        feature.setYMinMax(curr.y, feature.getYMax());
        if (featHasT) feature.setTMinMax(curr.t, feature.getTMax());
        break;
      case NEGX:
        feature.setXMinMax(curr.x, feature.getXMax());
        break;
      case POSX:
        feature.setXMinMax(feature.getXMin(), curr.x);
        break;
      case NEGXPOSY:
        feature.setXMinMax(curr.x, feature.getXMax());
        feature.setYMinMax(feature.getYMin(), curr.y);
        if (featHasT) feature.setTMinMax(feature.getTMin(), curr.t);
        break;
      case POSY:
        feature.setYMinMax(feature.getYMin(), curr.y);
        if (featHasT) feature.setTMinMax(feature.getTMin(), curr.t);
        break;
      case POSXY:
        feature.setXMinMax(feature.getXMin(), curr.x);
        feature.setYMinMax(feature.getYMin(), curr.y);
        if (featHasT) feature.setTMinMax(feature.getTMin(), curr.t);
        break;
      case MOVE:
        double xShift = curr.x - feature.getX();
        double yShift = curr.y - feature.getY();
        double tShift = curr.t - feature.getT();
        feature.setXMinMax(feature.getXMin()+xShift, feature.getXMax()+xShift);
        feature.setYMinMax(feature.getYMin()+yShift, feature.getYMax()+yShift);
        if (featHasT) feature.setTMinMax(feature.getTMin()+tShift, feature.getTMax()+tShift);
        break;
      case NONE:
        break;
    }
  }

  /** Process MPlot drag message in light of having an elastic Feature
   *
   *  @param elasticFeature MPlot elastic feature
   *  @param elasticHandle  active Handle
   *  @return Status of 'Message Consumed'
   */
  static int processDrag (Feature elasticFeature, Handle elasticHandle) {
    MPlot MP = elasticFeature.MP;
    if (MP.isStandardXYView() || MP.isLatLonView()) {
      // resizing elasticFeature
      MBox box = MP.box;
      box.reform();
      ResizeBox.updateBounds(elasticFeature, elasticHandle,
          MP.getPosition(box.x+box.w,  box.y+box.h), MP.view);
      return MPlot.MSG_CONSUMED;
    }
    else {
      Shell.warning("Resizing of elastic features is only supported in standard XY and LAtLon views");
      return ~MPlot.MSG_CONSUMED;
    }
  }

  /** Is the given View supported?
   *
   *  @param view MPlot view
   *  @return true if elastic features are supported in this view
   */
  public static boolean isSupportedView (int view) {
    return ((view == MPlot.V_IXIY)   || (view == MPlot.V_IYIX) ||
            (view == MPlot.V_IXY)    || (view == MPlot.V_IYX)  ||
            (view == MPlot.V_XIY)    || (view == MPlot.V_YIX)  ||
            (view == MPlot.V_XY)     || (view == MPlot.V_YX)   ||
            (view == MPlot.V_LATLON) || (view == MPlot.V_LATLONCONTINUOUS));
  }

  static boolean isInverseX (int view) {
    return ((view == MPlot.V_IXIY)   || (view == MPlot.V_IYIX) ||
            (view == MPlot.V_IXY)    || (view == MPlot.V_YIX));
  }

  static boolean isInverseY (int view) {
    return ((view == MPlot.V_IXIY)   || (view == MPlot.V_IYIX) ||
            (view == MPlot.V_IYX)    || (view == MPlot.V_XIY));
  }

  static boolean isYXView (int view) {
    return ((view == MPlot.V_IYIX)   || (view == MPlot.V_IYX)  ||
             (view == MPlot.V_YIX)   || (view == MPlot.V_YX)   ||
            (view == MPlot.V_LATLON) || (view == MPlot.V_LATLONCONTINUOUS));
  }
}
