package nxm.sys.prim;

import java.awt.Color;
import java.awt.TextArea;
import java.awt.Toolkit;
/* For cut/copy/paste functions. */
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;

import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;

import nxm.sys.lib.Convert;
import nxm.sys.lib.Message;
import nxm.sys.lib.Table;
import nxm.sys.libg.GMenu;
import nxm.sys.libg.GPrimitive;
import nxm.sys.libg.GValue;
import nxm.sys.libg.MColor;
import nxm.sys.libg.MJTextArea;
import nxm.sys.libg.MWindow;

/** Display lines of text (single font and color). <br>
    <br>
    This primitive can display text in a box.  If using the AWT
    {@link TextArea} class, it is limited to a single font and color.
    Lines may be added via messages. The <code>/EDIT</code> switch
    may be used to make the text editable. The <code>/SCROLL</code>
    switch can be used to control scrollbars. <br>
    <br>
    <b>Since NeXtMidas 1.7.5:</b> <br>
    Support has been added for the use of "cut"/"copy"/"paste" messages
    (<code>new Message("TEXTBOX",0,"CUT")</code>,
    <code>new Message("TEXTBOX",0,"COPY")</code>, and
    <code>new Message("TEXTBOX",0,"PASTE")</code>) as may be sent
    from {@link MJTextArea} or accessed via the menu.
    <br><br>
    <b>Since NeXtMidas 2.7.1:</b> <br>
    Support has been added for the use of the Swing JTextArea, through the
    use of the SWING graphics mode option in Theme.
    @author Jay Hallam
    @author J. Czechowski IV
*/
public class textdisplay extends GPrimitive {
  /** The (SWING) text area GUI to use. */
  private MJTextArea mjta;
  /** The scrollpane, if using the /scroll switch */
  private JScrollPane scrollPane;

  /** The bitmask that is and'ed with an exit message to verify if the exit message should be used. */
  private int exitMask = -1;

  /** Indicates that exiting should be allowed from the "Exit" option in the menu. */
  private static final int EXIT_MENU=0x1; //, EXIT_MESSAGE=0x2;

  /** The flags matching the exit options. */
  public static String exitFlagsList = "Menu";//,Message";

  /** The scroll bar policy. */
  private String scrollString = "NONE";

  /** default constructor */
  public textdisplay () {}

  public int open() {
    String title = MA.getS("/TITLE", "NeXtMidas TextBox");
    Object input = null;
    input = MA.getO("TEXT",null);
    String initialText = ""+input;
    if (input instanceof Table) {
       initialText = convertTableToText((Table)input,null,0);
    }

    //String theme = MA.getU("/THEME");
    int rows = MA.getL("/ROWS", 20);
    int cols = MA.getL("/COLS", 80);
    boolean editAble = MA.getState("/EDIT", false);
    exitMask = MA.getOptionMask("/EXIT", exitFlagsList, exitMask);

    scrollString = MA.getS("/SCROLL", scrollString);

    MW = new MWindow(title, this);
    MW.open();
    MW.setTheme (MA.getO("/THEME"));
    MW.addTo(this);

    setupTextArea(initialText, rows, cols, editAble);

// JC4: Not sure why this is here... but removing it seems to fix Bug 1165
//    MW.setSize((int)mta.getMinimumSize().getWidth(),
//               (int)mta.getMinimumSize().getHeight());

    resize();
    return NORMAL;
  }

  public int process () {
    return NOOP;
  }

  public int close () {
    MW.close();
    MW = null;
    mjta = null;
    return FINISH;
  }

  public int processMessage (Message msg) {
    if (MW.processMessage(msg) > 0) {
      // Message processed by MWindow
    }
    else if (msg.name.equals("RESIZE")) {
      resize();
    }
    else if (msg.name.equals("EXIT")) {
      // jph -- exit by message must always be allowed unless the menu exit
      // is re-implemented to not send an EXIT message
      //if ((exitMask&EXIT_MESSAGE)!=0) {
        return FINISH;
      //}
    }
    else if (msg.name.equals("TITLE")) {
      MW.setTitle((String)msg.data);
    }
    else if (msg.name.startsWith("APPEND") || msg.name.equals("TEXT")) {
      String text = "";
      if (msg.data instanceof Table) {
        text = convertTableToText((Table)msg.data,null,0);
      } else {
        text = (String)msg.data;
      }
      if (msg.name.equalsIgnoreCase("APPEND")) {
        mjta.append(text);
      } else {
        mjta.appendLine(text);
      }
    }
    else if (msg.name.equals("CLEAR")) {
      mjta.setText("");
    }
    else if (msg.name.equals("REPLACE")) {
      String text = "";
      if (msg.data instanceof Table) {
        text = convertTableToText((Table)msg.data,null,0);
      } else {
        text = (String)msg.data;
      }
      mjta.setText(text);
    }
    else if (msg.name.equals("MENU")) {
      // Pop up the various options available for this graphic.
      String menu;
      if (isEditAble()) menu = "Rows,Cols,Cut,Copy,Paste";
      else              menu = "Rows,Cols,Copy";

      if ((exitMask&EXIT_MENU)!=0)  menu = menu + ",Exit";

      //new GMenu(MW,"TextBox",menu,0,0,this);
      new GMenu(this.MW,"TextBox",menu,0,0,this);
    }
    else if (msg.name.equals("TEXTBOX")) {
      // Process the MENU mouse selection
      if (msg.data.equals("ROWS")) {
        int rows = getRows();
        new  GValue (MW, "Rows", rows , 1, 1000, 1 ,0,this);
      }
      else if (msg.data.equals("COLS")) {
        int cols = getCols();
        new  GValue (MW, "Cols", cols, 1, 1000, 1, 0, this);
      }
      else if (msg.data.equals("CUT")) {
        try {
          if (isEditAble()) { /* Disable if not editable. */
            StringSelection data
              = new StringSelection(getSelectedText());

            Clipboard clipboard
              = Toolkit.getDefaultToolkit().getSystemClipboard();

            clipboard.setContents(data, data);


            String text = getText();
            setText(text.substring(0,getSelectionStart())
                           + text.substring(getSelectionEnd(),
                                            text.length()));
          }
        } catch (Exception e) {
          /* Do nothing by default. */
        }
      } else if (msg.data.equals("COPY")) {
        try {
          StringSelection data
            = new StringSelection(getSelectedText());

          Clipboard clipboard
            = Toolkit.getDefaultToolkit().getSystemClipboard();

          clipboard.setContents(data, data);


        } catch (Exception e) {
          /* Do nothing by default. */
        }
      }
      else if (msg.data.equals("PASTE")) {
        try {
          if (isEditAble()) { /* Disable if not editable. */
            Clipboard clipboard
              = Toolkit.getDefaultToolkit().getSystemClipboard();

            Transferable data = clipboard.getContents(null);

            if (data != null) {
              if (data.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                insert(
                       data.getTransferData(DataFlavor.stringFlavor).toString(),
                       getCaretPosition());
              }
            }
          }
        } catch (Exception e) {
          /* Do nothing by default. */
        }
      } else if (msg.data.equals("EXIT")) {
        msg.name = "EXIT";
        if ((exitMask&EXIT_MENU)!=0) MQ.put(msg);
      } else  {
        M.warning ("Unhandled menu choice "+msg.data);
      }
    }
    else if (msg.name.equals("ROWS")) {
      mjta.setRows(Convert.o2l(msg.data));
      MW.setSize(mjta.getWidth(), mjta.getHeight());
    }
    else if (msg.name.equals("COLS")) {
      mjta.setRows(Convert.o2l(msg.data));
      MW.setSize(mjta.getWidth(), mjta.getHeight());
    }
    else if (msg.name.equals("REFRESH")) {
      MW.refresh();
    }
    else if (msg.name.equals("POINTER")
          || msg.name.equals("SHOWN")
          || msg.name.equals("MOVED")
          || msg.name.equals("FOCUS") ) {
      //TBD -- What to do with these messages
    }
    else if (msg.name.equals("WINDOW")) {
      if (msg.data.toString().equals("CLOSING")) {
        msg.name = "EXIT";
        MQ.put(msg);
      }
    }
    else {
//      M.warning("TEXTDISPLAY: Got unknown message : "+ msg.name);
    }
    return NORMAL;
  }

  /** A recursive method to convert a table into a String of indented lines.
      For example, the table <code>{x=1,INNER={I1=1,I2=2},Y=2}</code> becomes:
      <pre>
         X = 1
           I1 = 1
           I2 = 2
         Y = 2
      </pre>
      @param table       The table to search.
      @param tableAsText The table as text, set to "" on first invocation.
      @param level       The current level of recursion. Should be set to zero
                         unless being called recursively.
      @return The converted table.
  */
  public static String convertTableToText (Table table, String tableAsText, int level) {
    if (tableAsText == null) tableAsText = "";
    for (Table.Iterator ti=table.iterator(); ti.getNext(); ) {
      String key = ti.key;
      if (key != null) {
        Object result = ti.value;   // get object associated with key
        if (result != null) {
          if (result instanceof Table) {
            tableAsText = convertTableToText ((Table)result,tableAsText,level+1);
          } else {
            String pad = "";
            for(int i=0; i<level; i++) pad = "  "+pad;
            tableAsText = tableAsText + pad+key+" = "+result+"\n";
          }
        }
      }
    }
    return tableAsText;
  }

  /** Resize the text terminal. */
  public void resize () {
    if (mjta != null)  mjta.setSize(MW.pos.w, MW.pos.h);
    //if (scrollPane != null) scrollPane.setSize(MW.pos.w, MW.pos.h);
    if (scrollPane != null) scrollPane.setBounds(mjta.getBounds());
  }

  private void setupTextArea (String initialText, int rows, int cols, boolean editAble) {
    mjta = new MJTextArea (initialText, rows, cols);
    mjta.setEditable (editAble);
    mjta.setMessageHandler (MW.mh);
    mjta.setAttributes (MW.panel);
    if (scrollString.equals("NONE")) {
      MW.panel.add(mjta);  // no scrollpane
    }
    else {
      scrollPane = new JScrollPane(mjta);
      if (scrollString.indexOf('H')>-1) {
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        mjta.setLineWrap(false);
      }
      if (scrollString.indexOf('V')>-1) {
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
      }
      resize();
      MW.panel.add(scrollPane);
    }

    mjta.addKeyListener(MW);   /* Catches the ^P as a "POP" message. */
    mjta.addMouseListener(MW); /* Keeps track of mouse position for menu disp. */
  }

  private boolean isEditAble () {
    return mjta.isEditable();
  }

  private int getRows () {
    return mjta.getHeight() / mjta.getFont().getSize();
  }

  private int getCols () {
    return  mjta.getWidth() / mjta.getFont().getSize();
  }

  private String getText () {
    return  mjta.getText();
  }

  private String getSelectedText () {
    return  mjta.getSelectedText();
  }

  private int getSelectionStart () {
    return  mjta.getSelectionStart();
  }

  private int getSelectionEnd () {
    return  mjta.getSelectionEnd();
  }

  private void setText (String text) {
    mjta.setText(text);
  }

  private void insert (String text, int position) {
    mjta.insert(text, position);
  }

  private int getCaretPosition () {
    return mjta.getCaretPosition();
  }

  /** Sets the color of the selected text
      @param color The color to set to
      @since NeXtMidas 2.9.0
   */
  public void setSelectedTextColor (String color) {
    setSelectedTextColor(MColor.getColor(color));
  }

  /** Sets the color of the selected text
      @param c The color to set to
      @since NeXtMidas 2.9.0
   */
  public void setSelectedTextColor (Color c) {
    mjta.setSelectedTextColor(c);
  }

  /** Gets the color of the selected text
      @return The color
      @since neXtMidas 2.9.0
   */
  public String getSelectedTextColorString () {
    return MColor.toString(getSelectedTextColor());
  }

  /** Gets the color of the selected text
      @return The color
      @since neXtMidas 2.9.0
   */
  public Color getSelectedTextColor () {
    return mjta.getSelectedTextColor();
  }

  /** Sets the background selection color of the selection
      @param c The Color to set
      @since NeXtMidas 2.9.0
   */
  public void setSelectionColor (String c) {
    mjta.setSelectionColor(MColor.getColor(c));
  }

  /** Sets the background selection color of the selection
      @param c The Color to set
      @since NeXtMidas 2.9.0
   */
  public void setSelectionColor (Color c) {
    mjta.setSelectionColor(c);
  }

  /** Gets the background color of the selection
      @return The color
      @since NeXtMidas 2.9.0
   */
  public String getSelectionColorString () {
    return MColor.toString(mjta.getSelectionColor());
  }
  /** Gets the background color of the selection
      @return The color
      @since NeXtMidas 2.9.0
   */
  public Color getSelectionColor () {
    return mjta.getSelectionColor();
  }
}
