package nxm.sys.libg;

import java.awt.Color;
import java.awt.Graphics;
import java.util.List;

import nxm.sys.inc.Drawable;
import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;

/**
 * This is a specialized legend for use within {@link MPlot}.
 * @author J. Schoen
 * @since NeXtMidas 1.9.3
 */
public final class GLegend extends GMenu {

  private MPlot   mplot;          // The associated plot
  private Entry[] entries;        // All entries
  private int     nentries;       // Number of entries in entries array
  private int     altwidth  = 20;
  private int     altheight = 12;

  /** Comma separated list of flags */
  public static final String flagsList = GMenu.flagsList+",Symbol,ReOrder";
  /** SYMBOL flag */
  public static final int SYMBOL=0x10000000;
  /** REORDER flag */
  public static final int REORDER=0x20000000;

  /** Creates a new instance. */
  GLegend (Object parent, String name, MPlot mplot, int flags, MessageHandler mh) { // package-private only used in MPlot
    super("GLegend",parent,name,flags|STICKY|TOGGLE,mh);
    open (name, mplot);
    addTo (parent);
  }

  @Override
  public void setCorner (String corner) {
    // overrides version in GMenu so placement is relative to PLOT not the PANEL
    if (StringUtil.isNull(corner) || mplot==null) return;
    setCorner(mplot, corner);
  }

  /** Open the legend
   *  @param name the name of the plot
   *  @param mplot the MPlot that is creating the legend
   */
  public void open (String name, MPlot mplot) {
    this.mplot = mplot;
    updateEntries();
    setAltSize(altwidth,altheight);

    // The next three lines match GMenu.open(..) except for the parameters to setItems(..)
    open(name);
    setItems(entries, nentries);
    resize(1);  // calculate size
  }

  /**
   * Sets the default position for the widget.
   * @param corner The corner the legend should be in.
   * @param winBound The bounding window (The MPlot window.).
   * @since NeXtMidas 3.8.1
   */
  protected void setCorner (MPlot plot, String corner) {
    MBox newPos = new MBox(plot.panel.getLocation().x, plot.panel.getLocation().y, pos.w, pos.h);
    // for standalone commands, MW is null--make position (0,0)
    if (plot.MW == null) {
      newPos.x = 0;
      newPos.y = 0;
    }
    corner = corner.trim().toUpperCase();
    switch (corner) {
      case "TL":
      case "TOPLEFT":
      //default position, do nothing
        break;
      case "TR":
      case "TOPRIGHT":
        newPos.x = newPos.x + plot.pos.w - pos.w;
        break;
      case "BL":
      case "BOTTOMLEFT":
        newPos.y = newPos.y + plot.pos.h - pos.h;
        break;
      case "BR":
      case "BOTTOMRIGHT":
        newPos.x = newPos.x + plot.pos.w - pos.w;
        newPos.y = newPos.y + plot.pos.h - pos.h;
        break;
      default:
        Shell.warning("GWidget: Unknown corner (" + corner + ")");
        break;
    }
    setBounds(newPos);
  }

  /** Updates the array of entries. */
  private void updateEntries () {
    int nlayers = mplot.layers.getSize();

    if ((entries == null) || (entries.length < nlayers)) {
      entries = new Entry[nlayers];
    }
    nentries = 0;

    for (int i=0; i<nlayers; i++) {
      Layer   lay  = (Layer)mplot.layers.get(i);
      if (lay instanceof LayerDrawable && ((LayerDrawable)lay).isListLayers()) {
        LayerDrawable layerDrawables = (LayerDrawable) lay;
        List<Drawable> drawables = layerDrawables.getDrawablesInstance();
        for (Drawable drawable : drawables) {
          if (drawable instanceof Layer) addLayerToList((Layer) drawable);
        }
      }
      else addLayerToList(lay);
    }
    for (int i = nentries; i < entries.length; i++) {
      entries[i] = null; // clear out any old entries (possibly from layers that have been removed)
    }
  }

  private void addLayerToList (Layer lay) {
    Entry   ent  = lay.getLegendEntry();
    Entry[] sent = lay.getLegendSubEntries();
    int     num  = (sent == null)? 1 : (sent.length + 1);

    if (nentries+num > entries.length) {
      Entry[] temp = new Entry[nentries+num+8];
      System.arraycopy(entries, 0, temp, 0, nentries);
      entries = temp;
    }

    entries[nentries++] = ent;

    if (sent != null) {
      System.arraycopy(sent, 0, entries, nentries, sent.length);
      nentries+=sent.length;
    }
  }

  @Override
  public void select (int i, int button) {
    if (button == 3) {
      close();
    }
    else {
      entries[i-1].toggle();
      refresh();
    }
  }

  @Override
  public void selectAlt (int i, int button) {
    if (button == 3) {
      close();
    }
    else {
      entries[i-1].configure();
      refresh();
    }
  }

  @Override boolean isToggled (int i) { return entries[i-1].isEnabled(); }
  @Override int     getIndent (int i) { return entries[i-1].getIndent(); }


  @Override
  @Deprecated
  protected void refreshItemAlt (int i, MBox pos) {
    refreshItemAlt(g, i, pos);
  }

  @Override
  protected void refreshItemAlt (Graphics g, int i, MBox pos) {
    Entry   ent    = entries[i-1];
    Color[] colors = ent.getCMap();
    Line    line   = ent.getLine().copy();

    int x1 = pos.x,  x2 = x1+pos.w,  x  = (x1+x2)/2;
    int y1 = pos.y,                  y  = y1+pos.h/2;

    if ((colors == null) || (colors.length-1 <= 1)) {
      drawLegendLine(x, y, x1, x2, line, ent.getColor(), g);
    }
    else {
      int ncolors = colors.length-1;
      double scale = 2.0;
      if (ncolors<pos.h/2) y1 += (pos.h-2*(ncolors-1))/2;
      else scale = (double)pos.h/(ncolors-1);
      for (int c=0; c<ncolors; c++) drawLegendLine(x,y1+(int)(c*scale),x1,x2,line,colors[c+1],g);
    }
    g.setColor(theme.cwfg);
  }

  protected void drawLegendLine (int x, int y, int x1, int x2, Line lin, Color c, Graphics g) {
    g.setColor(c);
    Symbol sym  = lin.getSymbol();
    int    type = lin.getTypeInt();
    if ((type&Line.SOLID)!=0) g.drawLine(x1,y,x2,y);
    if ((type&Line.SYMBOL)!=0 && sym!=null) sym.draw(x,y,g,3);
  }

  @Override
  public String getLegend () {
    return "(LM=Toggle,MM=Configure,RM=Exit)";
  }

  /** Updates the legend, called by MPlot. */
  void update () { // package-private only used by MPlot
    updateEntries();
    setItems(entries, nentries);
    refresh();
  }


  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Entry definitions.
  //////////////////////////////////////////////////////////////////////////////////////////////////

  /** An entry in the legend. */
  public static class Entry {
    private final Layer layer;

    /** Creates a top-level entry for a layer. Use {@link GLegend.SubEntry} for a sub-entry
        below the layer entry.
        @param layer The layer (not null).
     */
    public Entry (Layer layer) {
      this.layer = layer;
    }

    /** Get the layer
     *  @return the layer */
    public final Layer getLayer () { return layer; }
    /** Get the entry name
     *  @return the entry name. */
    public String getName () { return layer.getName(); }
    /** Get the indentation level
     *  @return the indentation level (0=TopLevel). */
    public int getIndent () { return 0; }
    /** Get the line associated with the entry
     *  @return the line associated with the entry. */
    public Line getLine () { return layer.getLine(); }
    /** Get the entry color
     *  @return the entry color (not null). */
    public Color getColor () { return layer.getLine().getColor(); }
    /** Get the color map for the entry
     *  @return the color map for the entry (can be null). */
    public Color[] getCMap () { return layer.colors; }
    /** Is this entry enabled
     *  @return Is this entry enabled. This returns false for any SubEntry of a layer that is not enabled. */
    public boolean isEnabled () { return ((layer.enable&MPlot.EN_GLOBAL)!=0); }

    /** Gets the name of the entry. */
    public String toString () { return getName(); }

    /** Toggle this entry. */
    public void toggle() { layer.toggle(); }

    /** Configure this entry. */
    public void configure() { layer.configure(); }
  }

  /** A sub-entry in the legend. */
  public static class SubEntry extends Entry {
    private final String  name;
    private final int     indent;
    private final Line    line;
    private final Color   color;
    private final Color[] cmap;
    private final boolean enabled;

    /** Creates a sub-entry for a layer.
        @param layer   The associated layer (not null).
        @param name    The entry name (not null).
        @param indent  Indentation level (1=SubEntry, 2=SubSubEntry, etc.)
        @param line    The drawing line used (null - use line from layer).
        @param color   Color for the entry (null = use color from layer).
        @param cmap    Color map for the entry (null = n/a).
        @param enabled Is this entry enabled.
     */
    public SubEntry (Layer layer, String name, int indent, Line line,
                     Color color, Color[] cmap, boolean enabled) {
      super(layer);
      this.name    = name;
      this.indent  = indent;
      this.line    = line;
      this.color   = color;
      this.cmap    = cmap;
      this.enabled = enabled;
    }

    @Override public String  getName ()   { return name;   }
    @Override public int     getIndent () { return indent; }
    @Override public Line    getLine ()   { return (line  != null)? line  : super.getLine(); }
    @Override public Color   getColor ()  { return (color != null)? color : super.getColor(); }
    @Override public Color[] getCMap ()   { return cmap;   }
    @Override public boolean isEnabled () { return enabled && super.isEnabled(); }
  }
}
