/*
 * $Id: TextConfigurer.java 3888 2008-07-27 14:45:45Z 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.util.StringTokenizer;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import VASSAL.build.AutoConfigurable;
import VASSAL.tools.ScrollPane;
import VASSAL.tools.SequenceEncoder;

/**
 * A Configurer that allows multi-line string input via a JTextArea
 */
public class TextConfigurer extends Configurer implements ConfigurerFactory {
  private JTextArea textArea;
  private JPanel p;
  private boolean wordWrap;

  public TextConfigurer() {
    this(null, null, null);
  }

  public TextConfigurer(String key, String name) {
    this(key, name, "");
  }

  public TextConfigurer(String key, String name, String val) {
    super(key, name, val);
  }

  public TextConfigurer(String key, String name, String val, boolean wrap) {
    this(key, name, val);
    setWordWrap(wrap);
  }
  
  public Configurer getConfigurer(AutoConfigurable c, String key, String name) {
    this.key = key;
    this.name = name;
    return this;
  }

  public String getValueString() {
    return escapeNewlines((String) getValue());
  }

  public void setWordWrap(boolean b) {
    wordWrap = b;
  }
  
  /**
   * Encodes a string by replacing newlines with '|' characters
   * 
   * @param s
   * @return
   */
  public static String escapeNewlines(String s) {
    SequenceEncoder se = new SequenceEncoder('|');
    StringTokenizer st = new StringTokenizer(s, "\n\r", true);
    boolean wasNewLine = true;
    while (st.hasMoreTokens()) {
      String token = st.nextToken();
      switch (token.charAt(0)) {
      case '\n':
        if (wasNewLine) {
          se.append("");
        }
        wasNewLine = true;
        break;
      case '\r':
        break;
      default:
        se.append(token);
        wasNewLine = false;
      }
    }
    return se.getValue() == null ? "" : se.getValue();
  }

  public void setValue(String s) {
    String text = restoreNewlines(s);
    setValue((Object) text);
  }

  public void setValue(Object o) {
    super.setValue(o);
    if (!noUpdate && textArea != null) {
      textArea.setText((String) o);
    }
  }

  /**
   * Restores a string by replacing '|' with newlines
   * 
   * @param s
   * @return
   */
  public static String restoreNewlines(String s) {
    SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(s, '|');
    String text = "";
    while (st.hasMoreTokens()) {
      text += st.nextToken();
      if (st.hasMoreTokens()) {
        text += "\n";
      }
    }
    return text;
  }

  public java.awt.Component getControls() {
    if (p == null) {
      p = new JPanel();
      p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
      textArea = new JTextArea(6, 20);
      if (wordWrap) {
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
      }
      textArea.addKeyListener(new java.awt.event.KeyAdapter() {
        public void keyReleased(java.awt.event.KeyEvent evt) {
          queueForUpdate(textArea.getText());
        }
      });
      textArea.setText((String) getValue());
      JScrollPane scroll = new ScrollPane(textArea);
      if (name != null) {
        scroll.setBorder(new TitledBorder(name));
      }
      p.add(scroll);
    }
    return p;
  }

  private long lastUpdate = System.currentTimeMillis();
  private String updatedValue;
  private boolean updateQueued=false;
  private long updateFrequencey = 1000L;

  private void queueForUpdate(String s) {
    updatedValue = s;
    if (System.currentTimeMillis() > lastUpdate + updateFrequencey) {
      executeUpdate();
    }
    else if (!updateQueued) {
      updateQueued = true;
      Runnable delayedUpdate = new Runnable() {
        public void run() {
          try {
            Thread.sleep(updateFrequencey);
          }
          catch (InterruptedException e) {
          }

          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              executeUpdate();
            }
          });
        }
      };
      new Thread(delayedUpdate).start();
    }
  }

  private void executeUpdate() {
    noUpdate = true;
    setValue((Object) updatedValue);
    lastUpdate = System.currentTimeMillis();
    updateQueued = false;
    noUpdate = false;
  }
}
