#include "EPoll.h"
#include "Socket.h"
#include "Exception.h"
#include "Log.h"

namespace core {

   Socket::Socket(EPoll &ePoll) : ePoll(ePoll) {
      Log(LOG_DEBUG_2) << "BMASocket object created.";
      Log(LOG_DEBUG_3) << "Buffer size set to default (4096).";
      buffer = (char *)malloc(4096);
      length = 4096;
   }
   
   Socket::~Socket() {
      ePoll.unregisterSocket(this);
      close(descriptor);
      free(buffer);
   }
   
   void Socket::setDescriptor(int descriptor) {
      Log(LOG_DEBUG_3) << "Descriptor set to " << descriptor << " for Socket.";
      if(descriptor < 3)
	throw Exception("Descriptor out of range", __FILE__, __LINE__);
      this->descriptor = descriptor;
      onTLSInit();
   }
   
   int Socket::getDescriptor() {
      return descriptor;
   }
   
   void Socket::onTLSInit() {}
   
   void Socket::setBufferSize(int length) {
      buffer = (char *)realloc(buffer, length);
      this->length = length;
   }
   
   void Socket::onRegistered() {
      onConnected();
   }
   
   void Socket::onUnregistered() {
      //   onDisconnected();
   }
   
   void Socket::eventReceived(struct epoll_event event) {
      
      //   std::stringstream stream;
      //   stream << "Event received on socket " << event.data.fd << ": ";  
      //   if(event.events & EPOLLRDHUP) stream << "EPOLLRDHUP ";
      //   if(event.events & EPOLLIN) stream << "EPOLLIN ";
      //   if(event.events & EPOLLOUT) stream << "EPOLLOUT ";
      //   if(event.events & EPOLLERR) stream << "EPOLLERR ";
      //   stream << "[" << event.events << "]";
      //   BMALog(LOG_DEBUG_4) << stream.str();
      // 
      if(event.events & EPOLLRDHUP) {
	 Log(LOG_DEBUG_2) << "Socket " << descriptor << " received disconnect from client.";
	 shutdown();
	 return;
      }
      
      if(event.events & EPOLLIN) 
	receiveData(buffer, length);
      
      if(event.events & EPOLLOUT) 
	writeSocket();
      
      enable(true);
   }
   
   void Socket::enable(bool mode) {
      if(mode) {
	 if(fifo.empty()) 
	   active ? resetRead(): setRead();
	 else
	   active ? resetReadWrite(5): setReadWrite();
	 active = true;
      }
      else 
	clear();
   }
   
   void Socket::receiveData(char *buffer, int bufferLength) {
      
      if(bufferLength <= 0)
	throw Exception("Request to receive data with a zero buffer length.", __FILE__, __LINE__, -1);
      
      int len;
      int error = -1;
      
      if((len = ::read(getDescriptor(), buffer, bufferLength)) >= 0) 
	onDataReceived(std::string(buffer, len));       
      else {
	 
	 error = errno;
	 
	 switch (error) {
	    
	    // When a listening socket receives a connection 
	    // request we get one of these.
      	    // 
	  case ENOTCONN: 
            onDataReceived(std::string(buffer, 0));                
            break;
	    
	  case ECONNRESET:
            break;
	    
	  default:
            throw Exception("Error in read of data from socket.", __FILE__, __LINE__, error);
	 }
      }
   }
   
   void Socket::onConnected() {
   }
   
   void Socket::writeSocket() {
      lock.lock();
      if(fifo.size() > 0) {
	 ::write(descriptor, fifo.front().c_str(), fifo.front().length());
	 fifo.pop();
	 enable(true);
      }
      lock.unlock();
   }
   
   void Socket::write(std::string data) {
      lock.lock();
      fifo.emplace(data);   
      enable(true);
      lock.unlock();
   }
   
   void Socket::output(std::stringstream &out) {
      out << "|" << descriptor << "|";
   }
   
   void Socket::setRead() {
      event.data.fd = descriptor;
      event.events = EPOLLIN | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;   
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, descriptor, NULL);  
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, event.data.fd, &event);      
   }
   
   void Socket::setWrite() {
      event.data.fd = descriptor;
      event.events = EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;   
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, descriptor, NULL);  
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, descriptor, &event);      
   }
   
   void Socket::setReadWrite() {
      event.data.fd = descriptor;
      event.events = EPOLLIN | EPOLLWRNORM | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;   
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, descriptor, NULL);  
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_ADD, descriptor, &event);      
   }
   
   void Socket::resetRead() {
      event.data.fd = descriptor;
      event.events = EPOLLIN | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;   
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, descriptor, &event);      
   }
   
   void Socket::resetWrite() {
      event.data.fd = descriptor;
      event.events = EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, descriptor, &event);      
   }
   
   void Socket::resetReadWrite(int x) {
      event.data.fd = descriptor;
      event.events = EPOLLIN | EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP | EPOLLET;
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_MOD, descriptor, &event);      
   }
   
   void Socket::clear() {
      epoll_ctl(ePoll.getDescriptor(), EPOLL_CTL_DEL, descriptor, NULL);
   }
   
   void Socket::shutdown() {
      Log (LOG_DEBUG_2) << "Shutdown requested on socket " << descriptor << ".";
      shutDown = true;
		enable(false);
		delete this;
   }
   
}
