package de.reinhardt_karlheinz.pcc.interfaces;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import de.reinhardt_karlheinz.pcc.enums.ConnectionType;
import de.reinhardt_karlheinz.pcc.events.PCCMessageEvent;
import de.reinhardt_karlheinz.pcc.events.PCConnectionEvent;
import de.reinhardt_karlheinz.pcc.listeners.PCCMsgListener;
import de.reinhardt_karlheinz.pcc.listeners.PCConnectionListener;

public abstract class PCCConnection {

  private ConnectionType connType;

  // ## OTHER
  private boolean connection;

  // ### LISTENER STUFF ###

  private ArrayList<PCConnectionListener> _connectionListeners = new ArrayList<PCConnectionListener>();

  private ArrayList<PCCMsgListener> _messageListeners = new ArrayList<PCCMsgListener>();

  public PCCConnection(ConnectionType c) {
    connType = c;
    connection = false;
    addShutdownHook();
  }

  protected abstract void addShutdownHook();

  // ## server
  protected abstract void sendString(String s);

  public void sendCmd(String cmd) {
    sendString("\\" + cmd);
  }

  public void sendMsg(String msg) {
    // TODO msg and cmd format
    sendString(msg);
  }

  /**
   * evaluates input string<br>
   * throws MsgReceivedEvent and CommandReceivedEvent for the listeners
   * 
   * @param s
   *          to be evaluated String
   */
  protected void evaluateInputString(String s) {
    if (s != null && s.isEmpty() == false && "".equals(s) == false) {
      if (s.startsWith("\\")) {
        fireCommandReceivedEvent(s.substring(1));
      } else {
        fireMsgReceivedEvent(s);
      }
    }
  }

  /**
   * starts server Thread and will fire ConnectionEstablishedEvent when
   * connection is established.
   * 
   * 
   * @return true when connection established else false
   * @throws IOException
   */
  public abstract boolean startServer() throws IOException;

  /**
   * will close connections and fire connections lost event
   * 
   * @throws IOException
   */
  public abstract void stopServer() throws IOException;

  /**
   * is called by stopServer()
   * 
   * @throws IOException
   */
  protected abstract void closeConnections() throws IOException;

  // ### LISTENER STUFF ###
  public void addPCConnectionListener(PCConnectionListener listener) {
    _connectionListeners.add(listener);
  }

  public void removePCConnectionListener(PCConnectionListener listener) {
    _connectionListeners.remove(listener);
  }

  public void addPCCMsgListener(PCCMsgListener listener) {
    _messageListeners.add(listener);
  }

  public void removePCCMsgListener(PCCMsgListener listener) {
    _messageListeners.remove(listener);
  }

  /**
   * fires ConnectionEstablishedEvent
   */
  protected void fireConnectionEstablishedEvent() {
    PCConnectionEvent event = new PCConnectionEvent(this, connType);
    Iterator<PCConnectionListener> i = _connectionListeners.iterator();
    while (i.hasNext()) {
      i.next().onPCConnectionEstablished(event);
    }
  }

  /**
   * fires ConnectionLostEvent<br>
   * should only be called by method stopServer()
   */
  protected void fireConnectionLostEvent() {
    PCConnectionEvent event = new PCConnectionEvent(this, connType);
    Iterator<PCConnectionListener> i = _connectionListeners.iterator();
    while (i.hasNext()) {
      i.next().onPCConnectionLost(event);
    }
  }

  /**
   * fires MsgReceivedEvent
   */
  private void fireMsgReceivedEvent(String msg) {
    PCCMessageEvent event = new PCCMessageEvent(this, msg);
    Iterator<PCCMsgListener> i = _messageListeners.iterator();
    while (i.hasNext()) {
      i.next().onMsgReceived(event);
    }
  }

  /**
   * fires CommandReceivedEvents
   */
  private void fireCommandReceivedEvent(String msg) {
    PCCMessageEvent event = new PCCMessageEvent(this, msg);
    Iterator<PCCMsgListener> i = _messageListeners.iterator();
    while (i.hasNext()) {
      i.next().onCommandReceived(event);
    }
  }

  // ################

  protected void setConnected(boolean b) {
    connection = b;
  }

  public boolean isConnected() {
    return connection;
  }

  protected abstract boolean checkIfConnected();

  public ConnectionType getConnectionType() {
    return connType;
  }
}
