/*
 * $Id: SecretNotesController.java 4560 2008-11-28 15:40:49Z uckelman $
 *
 * Copyright (c) 2004 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.build.module.noteswindow;

import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;

import VASSAL.build.BadDataReport;
import VASSAL.build.GameModule;
import VASSAL.build.module.Chatter;
import VASSAL.build.module.GameComponent;
import VASSAL.build.module.GlobalOptions;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.configure.StringConfigurer;
import VASSAL.configure.TextConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.ScrollPane;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.WarningDialog;

public class SecretNotesController implements GameComponent, CommandEncoder, AddSecretNoteCommand.Interface {
  public static final String COMMAND_PREFIX = "SNOTE\t"; //$NON-NLS-1$

  private Controls controls;
  private JPanel panel;
  private List<SecretNote> notes;
  private List<SecretNote> lastSavedNotes;

  // Secret Note display table columns
  public static final int COL_HANDLE = 0;
  public static final int COL_DTM = 1;
  public static final int COL_NAME = 2;
  public static final int COL_REVEALED = 3;

  // Date formatter to save and restore date/times in the save file
  public static final DateFormat INTERNAL_DATE_FORMATTER =
      DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US);

  // Date formatter to display date/time to the player
  public static final DateFormat LOCAL_DATE_FORMATTER =
      DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());

  public SecretNotesController() {
    notes = new ArrayList<SecretNote>();
    controls = new Controls();
  }

  public Command getRestoreCommand() {
    Command comm = null;
    for (SecretNote note : notes) {
      Command c = new AddSecretNoteCommand(this, note);
      if (comm == null) {
        comm = c;
      }
      else {
        comm.append(c);
      }
    }
    return comm;
  }

  public void setup(boolean gameStarting) {
    if (!gameStarting) {
      notes.clear();
      controls.refresh();
    }
  }

  public Command decode(String command) {
    Command comm = null;
    if (command.startsWith(COMMAND_PREFIX)) {
      final SequenceEncoder.Decoder st = new SequenceEncoder.Decoder
          (command.substring(COMMAND_PREFIX.length()), '\t');
      String name = st.nextToken();
      String owner = st.nextToken();
      boolean hidden = "true".equals(st.nextToken()); //$NON-NLS-1$
      String text = TextConfigurer.restoreNewlines(st.nextToken());
      String handle = ""; //$NON-NLS-1$
      Date date = null;

      if (st.hasMoreTokens()) {
        String formattedDate = st.nextToken();
        try {
          date = INTERNAL_DATE_FORMATTER.parse(formattedDate);
        }
        catch (ParseException e) {
          ErrorDialog.dataError(new BadDataReport("Illegal date format",formattedDate,e));
        }
      }

      if (st.hasMoreTokens()) {
        handle = st.nextToken();
      }

      SecretNote note = new SecretNote(name, owner, text, hidden, date, handle);
      comm = new AddSecretNoteCommand(this, note);
    }
    return comm;
  }

  public String encode(Command c) {
    String s = null;
    if (c instanceof AddSecretNoteCommand) {
      SecretNote note = ((AddSecretNoteCommand) c).getNote();
      SequenceEncoder se = new SequenceEncoder('\t');
      String date = note.getDate() == null ? "" : INTERNAL_DATE_FORMATTER.format(note.getDate()); //$NON-NLS-1$
      s = COMMAND_PREFIX +
          se.append(note.getName())
          .append(note.getOwner())
          .append(note.isHidden())
          .append(TextConfigurer.escapeNewlines(note.getText()))
          .append(date)
          .append(note.getHandle()).getValue();
    }
    return s;
  }

  public void addSecretNote(SecretNote note) {
    int index = notes.indexOf(note);
    if (index >= 0) {
      notes.set(index, note);
    }
    else {
      notes.add(0, note);
    }
    controls.refresh();
  }


  public JComponent getControls() {
    if (panel == null) {
      panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
      JLabel l = new JLabel(Resources.getString("Notes.visible_once_revealed")); //$NON-NLS-1$
      l.setAlignmentX(0.0F);
      panel.add(l);
      panel.add(controls);
    }
    return panel;
  }

  public Command save() {
    Command comm = null;
    for (SecretNote secretNote : notes) {
      int index = lastSavedNotes.indexOf(secretNote);
      if (index < 0 ||
          lastSavedNotes.get(index).isHidden() != secretNote.isHidden()) {
        Command c = new AddSecretNoteCommand(this, secretNote);
        if (comm == null) {
          comm = c;
        }
        else {
          comm.append(c);
        }
        String msg;
        if (index < 0) {
          msg = "* " + Resources.getString("Notes.has_created", GlobalOptions.getInstance().getPlayerId(), secretNote.getName()) + " *"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        else {
          msg = "* " + Resources.getString("Notes.has_revealed", GlobalOptions.getInstance().getPlayerId(), secretNote.getName()) + " *"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        c = new Chatter.DisplayText(GameModule.getGameModule().getChatter(), msg);
        c.execute();
        comm.append(c);
      }
    }
    return comm;
  }

  public void captureState() {
    lastSavedNotes = new ArrayList<SecretNote>(notes);
  }

  public void restoreState() {
    notes.clear();
    notes.addAll(lastSavedNotes);
    controls.refresh();
  }

  private class Controls extends JPanel implements ItemListener {
    private static final long serialVersionUID = 1L;

    private JTextArea text;
    private JTable table;
    private JButton revealButton;
    private String[] columnNames = {Resources.getString("Notes.player"), Resources.getString("Notes.date_time"), Resources.getString("Notes.note_name"), Resources.getString("Notes.revealed")}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$

    public Controls() {
      setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

      table = new JTable(new MyTableModel());
      initColumns(table);

      table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      ListSelectionModel rowSM = table.getSelectionModel();
      rowSM.addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
          //Ignore extra messages.
          if (e.getValueIsAdjusting())
            return;

          ListSelectionModel lsm = (ListSelectionModel) e.getSource();
          if (lsm.isSelectionEmpty()) {
            //...//no rows are selected
          }
          else {
            displaySelected();
          }
        }
      });

      JScrollPane secretScroll = new ScrollPane(table);
      table.setPreferredScrollableViewportSize(new Dimension(500, 100));

      add(secretScroll);

      Box b = Box.createHorizontalBox();
      b.setAlignmentX(0.0F);

      JButton newButton = new JButton(Resources.getString(Resources.NEW));
      newButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          createNewNote();
        }
      });
      b.add(newButton);

      revealButton = new JButton(Resources.getString("Notes.reveal")); //$NON-NLS-1$
      revealButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          revealSelectedNote();
        }
      });
      revealButton.setEnabled(false);
      b.add(revealButton);
      add(b);


      text = new JTextArea(6, 20);
      text.setEditable(false);
      JScrollPane scroll = new ScrollPane(text);
      scroll.setBorder(new TitledBorder(Resources.getString("Notes.text"))); //$NON-NLS-1$
      add(scroll);
    }

    private void initColumns(JTable t) {
      TableColumn column = null;
      for (int i = 0; i < columnNames.length; i++) {
        column = t.getColumnModel().getColumn(i);
        int width;
        switch (i) {
          case COL_HANDLE:
            width = 60;
            break;
          case COL_DTM:
            width = 100;
            break;
          case COL_NAME:
            width = 280;
            break;
          case COL_REVEALED:
            width = 60;
            break;
          default :
            width = 100;
            break;
        }
        column.setPreferredWidth(width);
      }
    }

    public class MyTableModel extends AbstractTableModel {
      private static final long serialVersionUID = 1L;

      public String getColumnName(int col) {
        return columnNames[col].toString();
      }

      public int getRowCount() {
        return notes.size();
      }

      public int getColumnCount() {
        return columnNames.length;
      }

      public Object getValueAt(int row, int col) {
        SecretNote note = notes.get(row);
        switch (col) {
          case COL_HANDLE:
            return note.getHandle();
          case COL_DTM:
            return note.getDate() == null ? "" : LOCAL_DATE_FORMATTER.format(note.getDate()); //$NON-NLS-1$
          case COL_NAME:
            return note.getName();
          case COL_REVEALED:
            return Boolean.valueOf(!note.isHidden());
          default:
            return null;
        }
      }

      public Class<?> getColumnClass(int c) {
        return getValueAt(0, c).getClass();
      }

      public boolean isCellEditable(int row, int col) {
        return false;
      }

      public void setValueAt(Object value, int row, int col) {
      }
    }

    public void refresh() {
      table.setModel(new MyTableModel());
      initColumns(table);
      displaySelected();
    }

    private void revealSelectedNote() {
      int selectedRow = table.getSelectedRow();
      if (selectedRow < 0) {
        return;
      }
      String selectedName = (String) table.getValueAt(selectedRow, COL_NAME);
      SecretNote note = getNoteForName(selectedName);

      if (note.getOwner().equals(GameModule.getUserId())) {
        note = new SecretNote(note.getName(), note.getOwner(), note.getText(), false, note.getDate(), note.getHandle());
        if (note != null) {
          int i = notes.indexOf(note);
          notes.set(i, note);
          refresh();
        }
      }
    }

    public void createNewNote() {
      final Dialog parent =
        (Dialog) SwingUtilities.getAncestorOfClass(Dialog.class, this);

      final JDialog d;
      if (parent != null) {
        d = new JDialog(parent, true);
      }
      else {
        d = new JDialog(
          (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this),
          true
        );
      }

      d.setTitle(Resources.getString("Notes.delayed_note")); //$NON-NLS-1$

      final StringConfigurer name = new StringConfigurer(null,
        Resources.getString("Notes.name")); //$NON-NLS-1$
      final TextConfigurer text = new TextConfigurer(null,
        Resources.getString("Notes.text")); //$NON-NLS-1$

      d.setLayout(new BoxLayout(d.getContentPane(), BoxLayout.Y_AXIS));
      d.add(name.getControls());
      d.add(text.getControls());

      final Box buttonPanel = Box.createHorizontalBox();
      final JButton okButton = new JButton(Resources.getString(Resources.OK));
      okButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          final SecretNote note = new SecretNote(
            name.getValueString(), 
            GameModule.getUserId(),
            (String) text.getValue(),
            true
          );

          if (notes.contains(note)) {
            WarningDialog.show(Controls.this, "Notes.note_exists");
          }
          else {
            notes.add(0, note);
            refresh();
            d.dispose();
          }
        }
      });

      final PropertyChangeListener l = new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          okButton.setEnabled(name.getValueString() != null
                              && name.getValueString().length() > 0
                              && text.getValueString() != null
                              && text.getValueString().length() > 0);
        }
      };
      name.addPropertyChangeListener(l);
      text.addPropertyChangeListener(l);

      okButton.setEnabled(false);
      buttonPanel.add(okButton);
      final JButton cancelButton =
        new JButton(Resources.getString(Resources.CANCEL));
      cancelButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          d.dispose();
        }
      });
      d.add(buttonPanel);

      d.pack();
      d.setLocationRelativeTo(d.getOwner());
      d.setVisible(true);
    }

    public void itemStateChanged(ItemEvent e) {
      displaySelected();
    }

    private void displaySelected() {
      revealButton.setEnabled(false);
      text.setText(""); //$NON-NLS-1$

      int selectedRow = table.getSelectedRow();
      if (selectedRow < 0) {
        return;
      }
      String selectedName = (String) table.getValueAt(selectedRow, COL_NAME);
      SecretNote note = getNoteForName(selectedName);

      if (note != null) {
        if (note.getOwner().equals(GameModule.getUserId())) {
          text.setText(note.getText());
          revealButton.setEnabled(note.isHidden());
        }
        else {
          text.setText(note.isHidden() ? Resources.getString("Notes.message_not_revealed") : note.getText()); //$NON-NLS-1$
        }
      }
    }
  }

  public SecretNote getNoteForName(String s) {
    for (SecretNote n : notes) {
      if (n.getName().equals(s)) {
        return n;
      }
    }
    return null;
  }
}
