package games.strategy.engine.history;

import java.io.Serializable;

import javax.swing.SwingUtilities;

import games.strategy.engine.data.Change;
import games.strategy.engine.data.PlayerID;

/**
 * Used to write to a history object. Delegates should use a
 * DelegateHistoryWriter
 */
public class HistoryWriter implements Serializable {
  private static final long serialVersionUID = 4230519614567508061L;
  private final History m_history;
  private HistoryNode m_current;

  public HistoryWriter(final History history) {
    m_history = history;
  }

  private void assertCorrectThread() {
    if (m_history.getGameData().areChangesOnlyInSwingEventThread() && !SwingUtilities.isEventDispatchThread()) {
      throw new IllegalStateException("Wrong thread");
    }
  }

  /**
   * Can only be called if we are currently in a round or a step.
   */
  public void startNextStep(final String stepName, final String delegateName, final PlayerID player,
      final String stepDisplayName) {
    assertCorrectThread();
    // we are being called for the first time
    if (m_current == null) {
      startNextRound(m_history.getGameData().getCurrentRound());
    }
    if (isCurrentEvent()) {
      closeCurrent();
    }
    // stop the current step
    if (isCurrentStep()) {
      closeCurrent();
    }
    if (!isCurrentRound()) {
      throw new IllegalStateException("Not in a round");
    }
    final Step currentStep = new Step(stepName, delegateName, player, m_history.getChanges().size(), stepDisplayName);
    addToAndSetCurrent(currentStep);
  }

  public void startNextRound(final int round) {
    assertCorrectThread();
    if (isCurrentEvent()) {
      closeCurrent();
    }
    if (isCurrentStep()) {
      closeCurrent();
    }
    if (isCurrentRound()) {
      closeCurrent();
    }
    final Round currentRound = new Round(round, m_history.getChanges().size());
    m_current = (HistoryNode) m_history.getRoot();
    addToAndSetCurrent(currentRound);
  }

  private void closeCurrent() {
    assertCorrectThread();
    final HistoryNode old = m_current;
    m_history.getGameData().acquireWriteLock();
    try {
      // remove steps where nothing happened
      if (isCurrentStep()) {
        final HistoryNode parent = (HistoryNode) m_current.getParent();
        if (m_current.getChildCount() == 0) {
          final int index = parent.getChildCount() - 1;
          parent.remove(m_current);
          m_history.nodesWereRemoved(parent, new int[] {index}, new Object[] {m_current});
        }
        m_current = parent;
        return;
      }
      m_current = (HistoryNode) m_current.getParent();
      ((IndexedHistoryNode) old).setChangeEndIndex(m_history.getChanges().size());
    } finally {
      m_history.getGameData().releaseWriteLock();
    }
  }

  private void addToAndSetCurrent(final HistoryNode newNode) {
    addToCurrent(newNode);
    m_current = newNode;
  }

  private void addToCurrent(final HistoryNode newNode) {
    m_history.getGameData().acquireWriteLock();
    try {
      m_history.insertNodeInto(newNode, m_current, m_current.getChildCount());
    } finally {
      m_history.getGameData().releaseWriteLock();
    }
    m_history.goToEnd();
  }

  public void startEvent(final String eventName) {
    assertCorrectThread();
    if (isCurrentEvent()) {
      closeCurrent();
    }
    if (!isCurrentStep()) {
      throw new IllegalStateException("Cant add an event, not a step. "
          + "Must be in a step to add an event to the step. \nTrying to add event: " + eventName);
    }
    final Event event = new Event(eventName, m_history.getChanges().size());
    addToAndSetCurrent(event);
  }

  private boolean isCurrentEvent() {
    return m_current instanceof Event;
  }

  private boolean isCurrentRound() {
    return m_current instanceof Round;
  }

  private boolean isCurrentStep() {
    return m_current instanceof Step;
  }

  /**
   * Add a child to the current event.
   */
  public void addChildToEvent(final EventChild node) {
    assertCorrectThread();
    if (!isCurrentEvent()) {
      new IllegalStateException("Not in an event, but trying to add child:" + node + " current is:" + m_current)
          .printStackTrace(System.out);
      startEvent("???");
    }
    addToCurrent(node);
  }

  /**
   * Add a change to the current event.
   */
  public void addChange(final Change change) {
    assertCorrectThread();
    if (!isCurrentEvent() && !isCurrentStep()) {
      new IllegalStateException("Not in an event, but trying to add change:" + change + " current is:" + m_current)
          .printStackTrace(System.out);
      startEvent("Bad Event for change: \n" + change.toString());
    }
    m_history.changeAdded(change);
  }

  public void setRenderingData(final Object details) {
    assertCorrectThread();
    if (!isCurrentEvent()) {
      new IllegalStateException("Not in an event, but trying to set details:" + details + " current is:" + m_current)
          .printStackTrace(System.out);
      startEvent("???");
    }
    m_history.getGameData().acquireWriteLock();
    try {
      ((Event) m_current).setRenderingData(details);
    } finally {
      m_history.getGameData().releaseWriteLock();
    }
    m_history.goToEnd();
  }
}
