/*
 * $Id: ExtensionTree.java 4586 2008-11-30 18:12:31Z uckelman $
 *
 * Copyright (c) 2000-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.configure;

import java.awt.Color;
import java.awt.Component;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.Configurable;
import VASSAL.build.module.ExtensionElement;
import VASSAL.build.module.ModuleExtension;
import VASSAL.build.module.documentation.HelpWindow;
import VASSAL.build.widget.PieceSlot;
import VASSAL.launch.EditorWindow;
import VASSAL.tools.ReflectionUtils;

/**
 * The configuration tree for editing a module extension
 */
public class ExtensionTree extends ConfigureTree {
  private static final long serialVersionUID = 1L;

  private ModuleExtension extension;

  public ExtensionTree(Configurable root, HelpWindow helpWindow, ModuleExtension extention, EditorWindow editorWindow) {
    super(root, helpWindow, editorWindow);
    this.extension = extention;
    setCellRenderer(new ExtensionRenderer());
  }

  private boolean isEditable(DefaultMutableTreeNode node) {
    if (node != null) {
      for (ExtensionElement el :
           extension.getComponentsOf(ExtensionElement.class)) {
        if (el.getExtension() == node.getUserObject()) {
          return true;
        }
      }
      if (node.getParent() instanceof DefaultMutableTreeNode) {
        return isEditable((DefaultMutableTreeNode) node.getParent());
      }
    }
    return false;
  }

  public Configurable[] getPath(DefaultMutableTreeNode node) {
    Object[] nodePath = node.getUserObjectPath();
    Configurable[] path = new Configurable[nodePath.length - 1];
    for (int i = 0; i < path.length; ++i) {
      path[i] = (Configurable) nodePath[i + 1];
    }
    return path;
  }
  
  public void externalInsert(Configurable parent, Configurable child) {
    super.externalInsert(parent, child);
    if (!isEditable(parent)) {
      extension.add(new ExtensionElement(child, getPath(getTreeNode(parent))));
    }
  }

  protected Action buildAddAction(final Configurable target, final Class<? extends Buildable> newConfig) {
    return new AbstractAction("Add " + getConfigureName(newConfig)) {
      private static final long serialVersionUID = 1L;

      public void actionPerformed(ActionEvent evt) {
        Configurable ch = null;
        try {
          ch = (Configurable) newConfig.getConstructor().newInstance();
        }
        catch (Throwable t) {
          ReflectionUtils.handleNewInstanceFailure(t, newConfig);
        }  

        if (ch != null) {
          final Configurable child = ch;

          child.build(null);
          if (child instanceof PieceSlot) {
            ((PieceSlot) child).updateGpId(extension);
          }
          if (child.getConfigurer() != null) {
            if (insert(target, child, getTreeNode(target).getChildCount())) {
              PropertiesWindow w = new PropertiesWindow((Frame) SwingUtilities.getAncestorOfClass(Frame.class, ExtensionTree.this), false, child, helpWindow) {
                private static final long serialVersionUID = 1L;

                public void save() {
                  super.save();
                  if (!isEditable(target)) {
                    ExtensionElement el = new ExtensionElement(child, getPath(getTreeNode(target)));
                    extension.add(el);
                  }
                }

                public void cancel() {
                  ExtensionTree.this.remove(target, child);
                  dispose();
                }
              };
              w.setVisible(true);
            }
          }
          else {
            boolean inserted = insert(target, child, getTreeNode(target).getChildCount());
            if (inserted && !isEditable(getTreeNode(target))) {
              extension.add(new ExtensionElement(child, getPath(getTreeNode(target))));
            }
          }
        }
      }
    };
  }

  protected Action buildImportAction(final Configurable target) {
    Action a = new AbstractAction("Add Imported Class") {
      private static final long serialVersionUID = 1L;

      public void actionPerformed(ActionEvent evt) {
        final Configurable child = importConfigurable();
        if (child != null) {
          try {
            child.build(null);
            final Configurable c = target;
            if (child.getConfigurer() != null) {
              PropertiesWindow w = new PropertiesWindow((Frame) SwingUtilities.getAncestorOfClass(Frame.class, ExtensionTree.this), false, child, helpWindow) {
                private static final long serialVersionUID = 1L;

                public void save() {
                  super.save();
                  insert(c, child, getTreeNode(c).getChildCount());
                  if (!isEditable(target)) {
                    ExtensionElement el = new ExtensionElement(child, getPath(getTreeNode(target)));
                    extension.add(el);
                  }
                }

                public void cancel() {
                  dispose();
                }
              };
              w.setVisible(true);
            }
            else {
              insert(c, child, getTreeNode(c).getChildCount());
            }
          }
          // FIXME: review error message
          catch (Exception ex) {
            JOptionPane.showMessageDialog
                (getTopLevelAncestor(),
                 "Error adding " + getConfigureName(child) +
                 " to " + getConfigureName(target) + "\n" + ex.getMessage(),
                 "Illegal configuration",
                 JOptionPane.ERROR_MESSAGE);
          }
        }
      }
    };
    return a;
  }

  private boolean isEditable(final Configurable target) {
    return isEditable(getTreeNode(target));
  }

  protected Action buildEditAction(Configurable target) {
    return isEditable(target) ? super.buildEditAction(target) : null;
  }

  protected Action buildCutAction(Configurable target) {
    return isEditable(target) ? super.buildCutAction(target) : null;
  }

  protected Action buildPasteAction(final Configurable target) {
    Action a = null;
    if (isEditable(target)) {
      a = super.buildPasteAction(target);
    }
    else {
      a = new AbstractAction("Paste") {
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
          if (cutData != null) {
            DefaultMutableTreeNode targetNode = getTreeNode(target);
            Configurable cutTarget = (Configurable) cutData.getUserObject();
            if (remove(getParent(cutData), cutTarget)) {
              if (insert(target, cutTarget, targetNode.getChildCount())) {
                extension.add(new ExtensionElement(cutTarget, getPath(targetNode)));
              }
            }
          }
          else if (copyData != null) {
            final Configurable copyTarget =
              (Configurable) copyData.getUserObject();

            Configurable clone = null;
            try {
              clone = copyTarget.getClass().getConstructor().newInstance();
            }
            catch (Throwable t) {
              ReflectionUtils.handleNewInstanceFailure(
                t, copyTarget.getClass());
            }

            if (clone != null) {
              clone.build(
                  copyTarget.getBuildElement(Builder.createNewDocument()));

              if (insert(target, clone, getTreeNode(target).getChildCount())) {
                updateGpIds(clone);
                extension.add(
                  new ExtensionElement(clone, getPath(getTreeNode(target))));
              }
            }
          }
          cutData = null;
          updateEditMenu();
        }
      };
      a.setEnabled(isValidPasteTarget(target));
    }
    return a;
  }
  
  protected boolean isValidPasteTarget(Configurable target) {
    return
      (cutData != null &&
      super.isValidParent(target, (Configurable) cutData.getUserObject()) &&
      isEditable(getParent(cutData))) ||
      (copyData != null &&
      super.isValidParent(target, (Configurable) copyData.getUserObject()));
  }

  /**
   * Allocate new PieceSlot Id's to any PieceSlot sub-components
   * 
   * @param c Configurable to update
   */
  protected void updateGpIds(Configurable c) {
    if (c instanceof PieceSlot) {
      ((PieceSlot) c).updateGpId(extension);
    }
    else {
      for (Configurable conf : c.getConfigureComponents()) updateGpIds(conf);
    }
  }
  
  protected Action buildMoveAction(Configurable target) {
    return isEditable((DefaultMutableTreeNode) getTreeNode(target).getParent()) ? super.buildMoveAction(target) : null;
  }

  protected Action buildDeleteAction(final Configurable target) {
    Action action = null;
    final DefaultMutableTreeNode targetNode = getTreeNode(target);
    if (targetNode.getParent() != null
        && isEditable(targetNode)) {
      final Configurable parent = (Configurable) ((DefaultMutableTreeNode) targetNode.getParent()).getUserObject();
      action = new AbstractAction("Delete") {
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent evt) {
          boolean removed = remove(parent, target);
          if (removed && !isEditable(parent)) {
            // We've removed an ExtensionElement
            for (ExtensionElement el :
                 extension.getComponentsOf(ExtensionElement.class)) {
              if (el.getExtension() == target) {
                extension.remove(el);
                break;
              }
            }
          }
        }
      };
    }
    return action;
  }

  protected Action buildCloneAction(final Configurable target) {
    final DefaultMutableTreeNode targetNode = getTreeNode(target);
    final DefaultMutableTreeNode parentNode =
      (DefaultMutableTreeNode) targetNode.getParent();

    if (isEditable(parentNode)) {
      return super.buildCloneAction(target);
    }

    if (parentNode != null) {
      return new AbstractAction("Clone") {
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent evt) {
          Configurable clone = null;
          try {
            clone = target.getClass().getConstructor().newInstance();
          }
          catch (Throwable t) {
            ReflectionUtils.handleNewInstanceFailure(t, target.getClass());
          }

          if (clone != null) {
            clone.build(target.getBuildElement(Builder.createNewDocument()));

            if (insert((Configurable) parentNode.getUserObject(),
                       clone, parentNode.getChildCount())) {
              extension.add(new ExtensionElement(clone, getPath(parentNode)));
            }
          }
        }
      };
    }
    else {
      return null;
    }
  }

  protected Action buildEditPiecesAction(Configurable target) {
    Action a = null;
    if (isEditable(target)) {
      a = super.buildEditPiecesAction(target);
    }
    return a;
  }

  public void mousePressed(MouseEvent e) {
    TreePath path = getPathForLocation(e.getX(), e.getY());
    if (path != null) {
      if (isEditable((DefaultMutableTreeNode) path.getLastPathComponent())) {
        super.mousePressed(e);
      }
    }
  }

  protected boolean isValidParent(Configurable parent, Configurable child) {
    return super.isValidParent(parent, child) && isEditable(parent);
  }
  
  protected void updateEditMenu() {
    super.updateEditMenu();
    deleteAction.setEnabled(selected != null && isEditable(selected));
    cutAction.setEnabled(selected != null && isEditable(selected));
//    cutAction.setEnabled(selected != null && isEditable(selected));
    propertiesAction.setEnabled(selected != null && isEditable(selected) && selected.getConfigurer() != null);
  }
  
  /**
   * Change color of component names based on editable status
   */
  class ExtensionRenderer extends Renderer {

    private static final long serialVersionUID = 1L;

    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,
        boolean expanded, boolean leaf, int row, boolean hasFocus) {

      Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row,
          hasFocus);
      c.setForeground(isEditable((DefaultMutableTreeNode) value) ? Color.BLACK : Color.GRAY);

      return c;
    }

  }
}
