/*
 * $Id: EventLog.java 4560 2008-11-28 15:40:49Z 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.build.module;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import VASSAL.build.AbstractBuildable;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.configure.StringConfigurer;
import VASSAL.tools.SequenceEncoder;

public class EventLog extends AbstractBuildable
                      implements CommandEncoder, GameComponent {
  public static final String EVENT_LIST = "Events"; //$NON-NLS-1$

  private List<Event> myEvents;
  private List<Event> savedEvents;

  public void addTo(Buildable b) {
    GameModule mod = GameModule.getGameModule();
    mod.addCommandEncoder(this);
    mod.getGameState().addGameComponent(this);
    mod.getPrefs().addOption(new StringConfigurer(EVENT_LIST, null));
    myEvents = new ArrayList<Event>();
    savedEvents = new ArrayList<Event>();

    for (Event e : decodedEvents((String) mod.getPrefs().getValue(EVENT_LIST)))
      myEvents.add(e); 
  }

  public void clearSaved() {
    savedEvents.clear();
  }

  public void store(Event e) {
    savedEvents.add(e);
  }

  public void log(Event e) {
    myEvents.add(e);
    GameModule.getGameModule()
              .getPrefs()
              .getOption(EVENT_LIST)
              .setValue(encodedEvents(myEvents));
  }

  public Command decode(String s) {
    if (s.startsWith(EVENT_LIST)) {
      return new StoreEvents(this, s.substring(EVENT_LIST.length()));
    }
    else {
      return null;
    }
  }

  public String encode(Command c) {
    if (c instanceof StoreEvents) {
      return EVENT_LIST + ((StoreEvents) c).getEvents();
    }
    else {
      return null;
    }
  }

  public void setup(boolean starting) {
    if (!starting) {
      clearSaved();
    }
  }

  public Command getRestoreCommand() {
    return new StoreEvents(this, encodedEvents(savedEvents));
  }
  
  /**
   * Decodes a <code>String</code> into a sequence of <code>Event</code>s.
   *
   * @param s the event string
   * @return the events represented by the string
   */
  public static Iterable<Event> decodedEvents(final String s) {
    return new Iterable<Event>() {
      public Iterator<Event> iterator() {
        return new Iterator<Event>() {
          private final SequenceEncoder.Decoder se =
            new SequenceEncoder.Decoder(s, '|');

          public boolean hasNext() {
            return se.hasMoreTokens();
          }
  
          public Event next() {
            final SequenceEncoder.Decoder sub =
              new SequenceEncoder.Decoder(se.nextToken(), ',');
            return new Event(Long.parseLong(sub.nextToken()),
                             sub.nextToken(), sub.nextToken());
          }
      
          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }

  /** Use {@link #decodedEvents()} instead. */
  @SuppressWarnings("unchecked")
  @Deprecated
  public static Enumeration decodeEvents(String s) {
    ArrayList<Event> l = new ArrayList<Event>();
    SequenceEncoder.Decoder se = new SequenceEncoder.Decoder(s, '|');
    while (se.hasMoreTokens()) {
      SequenceEncoder.Decoder sub =
        new SequenceEncoder.Decoder(se.nextToken(), ',');
      l.add(new Event(Long.parseLong(sub.nextToken()),
                      sub.nextToken(), sub.nextToken()));
    }
    return Collections.enumeration(l);
  }

  /**
   * Encodes a sequence of <code>Event</code>s into a <code>String</code>.
   *
   * @param events the sequence of events
   * @return the string representing the events
   */
  public static String encodedEvents(Iterable<Event> events) {
    final SequenceEncoder se = new SequenceEncoder('|');
    for (Event e : events) {
      final SequenceEncoder sub = new SequenceEncoder(',');
      sub.append(e.getTime())
         .append(e.getUser())
         .append(e.getAction());
      se.append(sub.getValue());
    }
    return se.getValue();
  }

  /** Use {@link #encodedEvents(Iterable<Event>)} instead. */
  @SuppressWarnings("unchecked")
  @Deprecated
  public static String encodeEvents(Enumeration e) {
    final SequenceEncoder se = new SequenceEncoder('|');
    while (e.hasMoreElements()) {
      final Event evt = (Event) e.nextElement();
      final SequenceEncoder sub = new SequenceEncoder(',');
      sub.append(evt.getTime())
         .append(evt.getUser())
         .append(evt.getAction());
      se.append(sub.getValue());
    }
    return se.getValue();
  }

  public static class Event {
    private long time;
    private String user;
    private String action;

    public Event(long time, String user, String action) {
      this.time = time;
      this.user = user;
      this.action = action;
    }

    public long getTime() {
      return time;
    }

    public String getUser() {
      return user;
    }

    public String getAction() {
      return action;
    }
  }

  public static class StoreEvents extends Command {
    private EventLog log;
    private String events;

    public StoreEvents(EventLog log, String events) {
      this.log = log;
      this.events = events;
    }

    public String getEvents() {
      return events;
    }

    public void executeCommand() {
      log.clearSaved();
      for (Event e : decodedEvents(events)) log.store(e);
    }

    public Command myUndoCommand() {
      return null;
    }
  }

  public String[] getAttributeNames() {
    return new String[0];
  }

  public void setAttribute(String name, Object value) {
  }

  public String getAttributeValueString(String name) {
    return null;
  }
}
