#include <winsock2.h>
#include <winsock.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
#include "network.h"
#include <stdlib.h>
#include "error.h"

#define ERROR_STR "Network communication error: "
#define ERROR_STR_END " - Consult www.msdn.microsoft.com"

#define DEFALT_PASSWORD "LEGOCLIENT"
#define MAX_PASSWORD_LENGTH 10

#define TIMEOUT 3000

//******************************************************************************************************
//if you get a compile error with "undefined reference to WSAStartup...." remember to link to libwsock32
//In code blocks: project->build option->linker setting->add - and then locate the libwsock32.a file
//In other compilers you might be able to use #pragma comment(lib, "wsock32.lib") or something simular
//*******************************************************************************************************

unsigned int network_send(SOCKET *this_socket, unsigned char *this_buffer, unsigned int length){
  return send(*this_socket, (char *) this_buffer, length, 0); //Send "Hello server"
}

unsigned int network_connect(SOCKET *this_socket, sockaddr_in *this_sockadd_in){
  return connect( *this_socket, (sockaddr*) this_sockadd_in, sizeof(*this_sockadd_in));
}


Nxt_network::Nxt_network(){
}

Nxt_network::~Nxt_network(){
  disconnect();
};

void Nxt_network::send(unsigned char *buffer, unsigned int num_bytes){
   /*unsigned int i;
   i=0;
   while(i<num_bytes){
     printf("\nSend Buffer[%d]=0x%x\n", i, buffer[i]);
     i++;
   }*/
   int send_bytes = network_send(&this->my_sock, buffer, num_bytes);
   //printf("Send bytes: %d\n", send_bytes);
   if(send_bytes == -1 || (unsigned int) send_bytes != num_bytes ){
      string s;
      s = this->error_to_string(WSAGetLastError());
      throw Nxt_exception::Nxt_exception("send", "Nxt_network", NETWORK_COM_ERROR, s);
   }
   return;
}

void Nxt_network::connect(unsigned int port, string ip_add, Server_settings &settings, string password){
  unsigned char buffer[MAX_PASSWORD_LENGTH+1];
  unsigned short int pass_len, i;
  unsigned char answer[5];
  struct timeval  timeout;

  timeout.tv_sec = TIMEOUT;
  timeout.tv_usec = 0;
  ws_ver=MAKEWORD(2, 0); // winsock 2.0
  ws_status=WSAStartup(ws_ver, &ws_data);
  if(ws_status != 0){
    string s;
    s = this->error_to_string(WSAGetLastError());
    throw Nxt_exception::Nxt_exception("connect", "Nxt_network", NETWORK_COM_ERROR, s);
  }
  this->my_sock=socket(AF_INET, SOCK_STREAM, 0);
  if(my_sock == INVALID_SOCKET){
    string s;
    s = this->error_to_string(WSAGetLastError());
    throw Nxt_exception::Nxt_exception("connect", "Nxt_network", NETWORK_COM_ERROR, s);
  }
  sockaddr_in sock_in;
  sock_in.sin_port=htons(port);
  sock_in.sin_addr.s_addr=inet_addr(ip_add.c_str());
  sock_in.sin_family=AF_INET;
  if(setsockopt(my_sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)) == -1){
    string s;
    s = this->error_to_string(WSAGetLastError());
    throw Nxt_exception::Nxt_exception("connect", "Nxt_network", NETWORK_COM_ERROR, s);
  }
  if(network_connect(&this->my_sock,&sock_in)){
    string s;
    s = this->error_to_string(WSAGetLastError());
    throw Nxt_exception::Nxt_exception("send", "Nxt_network", NETWORK_COM_ERROR, s);
  }
  if(password.empty()){
     password=DEFALT_PASSWORD;
  }
  pass_len = password.length();
  if(pass_len > MAX_PASSWORD_LENGTH){
    pass_len = MAX_PASSWORD_LENGTH;
  }
  i=0;
  while(i <pass_len){
    buffer[i] = password[i];
    i++;
  }
  while(i < MAX_PASSWORD_LENGTH+1){
    buffer[i] = '\0';
    i++;
  }
  send(buffer,MAX_PASSWORD_LENGTH+1);
  receive(answer, 9);
  if(answer[4]){
    throw Nxt_exception::Nxt_exception("connect", "Nxt_network", answer[4] & 0xff);
  }
  settings.mode = (Server_mode) answer[3];
  settings.timeout = (0xff & answer[5]) | ((0xff & answer[6]) << 8)| ((0xff & answer[7]) << 16)| ((0xff & answer[8]) << 24);
  return;
}

void Nxt_network::disconnect(){
  Sleep(2000);
  WSACleanup();
  closesocket(my_sock);
  return;
}

void Nxt_network::receive(unsigned char *buffer, unsigned int length){
  int get_bytes = recv(my_sock, (char *) buffer, length, 0);
  if( get_bytes == -1){
    string s;
    s = this->error_to_string(WSAGetLastError());
    throw Nxt_exception::Nxt_exception("send", "Nxt_network", NETWORK_COM_ERROR, s);
  }
  if(get_bytes == 0 && length !=0){
     //hack to avoid that when the client is thrown off
     //a wrong error message might be thrown if the client pools the server for a message
     if(length >= 5){
       buffer[4] = 0x00;
     }
  }
  //the server will send these bytes if case of an error
  //if( buffer[4] <= (unsigned char) NETWORK_ERROR_HIGH && buffer[4] >= (unsigned char) NETWORK_ERROR_LOW ){
  //   throw Nxt_exception::Nxt_exception("receive", "Nxt_network", buffer[4] & 0xff);
  //}
  return;
}

//does nothing
void Nxt_network::flush(){
  return;
}

Connection_type Nxt_network::get_type(){
  return NXT_NETWORK;
}

std::string Nxt_network::error_to_string(int err){
        string s;
        char *error_s;
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL,
                           err,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPTSTR) &error_s,
                           0,
                           NULL)){
          std::stringstream error_string;
          error_string << ERROR_STR << WSAGetLastError() << ERROR_STR_END;
          s = error_string.str();
          return s;
        }
        s = error_s;
        //free(error_s);
        return s;
}