/*
 * Created on Jun 22, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.broadsoft.clients.oci.bcct;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import com.broadsoft.clients.oci.util.Serializer;

/**
 * @author mgoupil
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class BcctClient
{
  private Socket       m_socket;
  private InputStream  m_inStream;
  private OutputStream m_outStream;
  private Packet       m_inMsg;
  private Receiver     m_receiver;
  private short        m_protocol;
  private Object       m_waitObj = new Object();
  
  public static final short PROTOCOL_ASOSS = 4;
  public static final short PROTOCOL_NSOSS = 5;
  public static final short PROTOCOL_OCI   = 6;

  public BcctClient(short protocol)
  {
    m_protocol = protocol;
  }
  
  public void connect(String host, int port) throws Exception
  {
    try
    {
      // Create the socket to the server
      m_socket = new Socket(host, port);
      m_inStream  = m_socket.getInputStream();
      m_outStream = m_socket.getOutputStream();
      m_receiver = new Receiver(m_inStream);
      m_receiver.start();
      
      // Send message to connect to the OCI server
      Packet regPacket = new Packet(Packet.TYPE_PROT_REG_REQUEST, m_protocol);
      Packet regReply = sendWaitReply(regPacket, 30);
      if (regReply == null)
      {
        throw new Exception("Timeout waiting for BCCT registration reply");
      }
      if (regReply.header.status != Packet.STATUS_SUCCESS) 
      {
        throw new Exception("Server refused to register protocol, error code: " + regReply.header.errorCode);
      }
    }
    catch(java.net.NoRouteToHostException e)
    {
      System.out.println(e.getMessage() + "\nValidate that the address " +
                         host+ " and port " + port + 
                         " are valid and the remote host is up and running");
      m_socket=null;
      throw new Exception(e);
    }
    catch(java.net.ConnectException e)
    {
      System.out.println(e.getMessage() + "\nValidate that the address " +
                         host + " and port " + port +
                         " are valid and the remote host is up and running");
      m_socket = null;
      throw new Exception(e);
    }
    catch(Exception e)
    {
      System.out.println("Exception: " + e.getMessage());
      e.printStackTrace();
      m_receiver.stop();
      m_socket.close();
      m_socket = null;
      throw new Exception(e);
    }
  }
  
  public void disconnect() throws Exception
  {
    Packet l_unreg = new Packet(Packet.TYPE_PROT_UNREG, m_protocol);
    send(l_unreg);
    m_receiver.stop();
    m_socket.close();
  }
  
  public boolean isClosed()
  {
    return m_socket.isClosed();
  }
  
  public String sendMessage(String message, int timeout) throws Exception
  {
    // Create a packet
    ByteBuffer l_buff = Serializer.serializeString(message);
    Packet l_pkt = new Packet(Packet.TYPE_MESSAGE, m_protocol, l_buff);
    l_pkt.header.type          = Packet.TYPE_MESSAGE;
    l_pkt.header.replyExpected = true;
    l_pkt.header.nextKeepAlive = 0;
    l_pkt.header.payloadSize   = l_buff.limit();
    
    l_pkt = sendWaitReply(l_pkt, timeout);
    
    // Check result
    if (l_pkt.header.type != Packet.TYPE_RESPONSE)
    {
      System.out.println("Received a message that is not a response!! (" + Packet.Header.typeToString(l_pkt.header.type) +")");
      return null;
    }
    
    if (l_pkt.header.status != Packet.STATUS_SUCCESS)
    {
      System.out.println("Received an unsuccessful reply!!");
      if (l_pkt.header.payloadSize > 0)
        System.out.println("Error: " + (String)Serializer.unserializeString(l_pkt.payload));
      
      return null;
    }
    
    if (l_pkt.header.payloadSize <= 0)
    {
      System.out.println("Received a response without payload!!");
      return null;
    }
    
    return (String)Serializer.unserializeString(l_pkt.payload);
  }
  
  private Packet sendWaitReply(Packet message, int timeout) throws Exception
  {
    m_inMsg = null;
    send(message);
    synchronized(m_waitObj)
    {
      if (timeout < 0)
      {
        m_waitObj.wait();
      }
      else
      {
        m_waitObj.wait(timeout*1000);
      }
    }
    
    return m_inMsg;
  }
  
  private void send(Packet packet) throws Exception
  {
    ByteBuffer l_outBuffer = ByteBuffer.allocate(Packet.Header.HEADER_SERIALIZED_SIZE + packet.header.payloadSize);
    try
    {
      packet.serialize(l_outBuffer);
    }
    catch (Exception e)
    {
      throw new Exception("Failed to serialized packet: ", e);
    }
    
    try
    {
      m_outStream.write(l_outBuffer.array());
    }
    catch (IOException e)
    {
      throw new Exception("Failed to write packet: ", e);
    }
  }
  
  private void handleIncomingMessage(Packet packet)
  {
    // In case of KEEP-ALIVE, simply reply with a KEEP-ALIVE
    if (packet.getType() == Packet.TYPE_KEEP_ALIVE)
    {
      // Reply to the keep-alive as if sending a new keep-alive
      Packet.Header l_header = new Packet.Header();
      l_header.type          = packet.header.type;
      l_header.protocol      = packet.header.protocol;
      l_header.nextKeepAlive = packet.header.nextKeepAlive;
      l_header.payloadSize   = packet.header.payloadSize;
      try
      {
        send(packet);
      }
      catch (Exception e)
      {
        System.out.println("Failed to send Keep-Alive: " + e.getMessage());
      }
    }
    else
    {
      // Incoming message, deliver it
      m_inMsg = packet;
      synchronized(m_waitObj)
      {
        m_waitObj.notify();
      }
    }
  }
  
  private class Receiver implements Runnable
  {
    private boolean     m_run = false;
    private Thread      m_thread;
    private InputStream m_inStream;
    
    public Receiver(InputStream inStream)
    {
      m_inStream = inStream;
    }
    
    public void start()
    {
      m_run    = true;
      m_thread = new Thread(this, "BcctReceiver");
      m_thread.start();
    }
    
    public void stop()
    {
      m_run = false;
    }
    
    public void run() 
    {
      while (m_run)
      {
        try
        {
          // First read the BCCT header
          byte[] buffer = new byte[Packet.Header.HEADER_SERIALIZED_SIZE];
          int size = m_inStream.read(buffer);
          if (size<=0)
          {
            return;
          }

          ByteBuffer l_buf = ByteBuffer.allocate(size);
          l_buf.put(buffer, 0, size);
          Packet l_packet = new Packet(l_buf);
          if (l_packet == null)
            continue;
          
          // From the header determine if there is a payload to read
          if (l_packet.header.payloadSize > 0)
          {
            buffer = new byte[l_packet.header.payloadSize];
            while (size-Packet.Header.HEADER_SERIALIZED_SIZE < l_packet.header.payloadSize)
            {
              int tmpSize = m_inStream.read(buffer);
              ByteBuffer l_tmpBuf = l_buf;
              l_tmpBuf.flip();
              l_buf = ByteBuffer.allocate(size+tmpSize);
              l_buf.put(l_tmpBuf);
              l_buf.put(buffer, 0, tmpSize);
              size += tmpSize;
            }
            l_packet = new Packet(l_buf);
          }
          handleIncomingMessage(l_packet);
        }
        catch (SocketException e)
        {
          if (m_run)
            System.out.println("Failed to read, socket exception: " + e.getMessage());
          return;
        }
        catch (IOException e)
        {
          System.out.println("Failed to read, IO exception: " + e.getMessage());
          return;
        }
        catch (Exception e)
        {
          System.out.println("Failed to read, exception: " + e.getMessage());
          return;
        }
      }
    }
  }
  
  public static void main(String[] args)
  {
    String l_msg = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
                   "<BroadsoftDocument protocol=\"OSSP\"  version=\"12.0\">\n" +
                   "<command commandType=\"requestAuthentication\">\n" +
                   "<commandData>\n" +
                   "<loginInfo>\n" +
                   "<loginId>admin</loginId>\n" +
                   "</loginInfo>\n" +
                   "</commandData>\n" +
                   "</command>\n" +
                   "</BroadsoftDocument>";

    BcctClient l_client = new BcctClient(PROTOCOL_ASOSS);
    try
    {
      l_client.connect("localhost", 2220);
      l_client.sendMessage(l_msg, 30);
      l_client.disconnect();
    }
    catch (Exception e)
    {
      System.out.println("Exception: " + e.getMessage());
    }
  }
}
