/*
Converts an integer to an ASCII string
*/
char * ClientSocket::itoa (int num) {
  char tmp, * out = NULL;
  int i = 0, len = 0, numb = num;
  while(numb / 10 >= 0) {
    numb /= 10;
    len++;
    if(numb == 0)
      break;
  }
  
  out = (char *) malloc(len + 1);
  if(out == NULL) {
    return NULL;
  }

  while((num / 10) >= 0) {
    out[i] = (num % 10) + '0'; 
    i++;
    num /= 10;
    if(num == 0) {
      break;
    }
  }


  for(i = 0; i < len/2; i++) {
    tmp = out[i];
    out[i] = out[(len - 1) - i];
    out[(len - 1) - i] = tmp;
  }

  out[len] = '\0';
  return out;
}

/*
Created a ClientSocket configured to optionally send encrypted data
*/
ClientSocket::ClientSocket(bool ssl = false) {
  this->sslconnect = ssl;
  this->ignoreErrors = false;
  this->bio = NULL;
  this->sslconn = NULL;
  if(!ssl) {
    this->ctx = NULL;
  } else {
    this->ctx = SSL_CTX_new(SSLv23_client_method());
  }
  SSL_load_error_strings();
  ERR_load_BIO_strings();
  OpenSSL_add_all_algorithms();
}

/*
Cleans up the ClientSocket
*/
ClientSocket::~ClientSocket() {
  BIO_free_all(bio);
  if(ctx != NULL) {
    SSL_CTX_free(ctx);
  }
}

/* 
Connect to a host
*/
bool ClientSocket::Connect(char * host, int port) {
  if(sslconnect) {
    int len = strlen(host) + (port / 10);
    char * buf = new char[len + 2];
    strcpy(buf, host);
    strcat(buf, ":");
    strcat(buf, itoa(port));
    bio = BIO_new_ssl_connect(ctx);
    BIO_get_ssl(bio, & sslconn);
    SSL_set_mode(sslconn, SSL_MODE_AUTO_RETRY);
    BIO_set_conn_hostname(bio, buf);
    delete [] buf;
    if(BIO_do_connect(bio) <= 0) {
	return false;
    }

    if(SSL_get_verify_result(sslconn) != X509_V_OK) {
      if(!ignoreErrors) {
	return false;
      }
    }
    return true;
  } else {
    int len = strlen(host) + (port / 10);
    char * buf = new char[len + 2];
    char * tmp = itoa(port);
    strcpy(buf, host);
    strcat(buf, ":");
    strcat(buf, tmp);
    bio = BIO_new_connect(buf);
    delete [] buf;
    delete [] tmp;
    if(bio == NULL) {
      return false;
    }
    if(BIO_do_connect(bio) <= 0) {
      return false;
    }
    return true;
  }
}

/*
Sets the SSL trust store to be type and path
*/
bool ClientSocket::SetTrustStore(StoreType type, char * path) {
  this->type = type;
  storepath = path;
  if(ctx == NULL) {
    return false;
  }
  if(type == FILESTORE) {
    if(!SSL_CTX_load_verify_locations(ctx, path, NULL)) {
      return false;
    }
    return true;
  } else {
    if(!SSL_CTX_load_verify_locations(ctx, NULL, path)) {
      return false;
    }
    return true;
  }
}

/*
Sets the ClientSocket to ignore SSL errors
*/
void ClientSocket::IgnoreSSLErrors() {
  ignoreErrors = true;
}

/*
Disconnect from host
*/
void ClientSocket::Disconnect() {
  BIO_reset(bio);
}

/*
Sends the message
*/
bool ClientSocket::Send(char* msg) {
  int len = strlen(msg);
  while(BIO_write(bio, msg, len) <= 0) {
    if(!BIO_should_retry(bio)) {
      return false;
    }
  }
  return true;
}

/*
Receive num bytes from the socket
*/
char * ClientSocket::Receive(int num) {
  char * out = new char[num + 1];
  int got;
  while((got = BIO_read(bio, out, num))) {
    if(got == 0) {
      delete [] out;
      return NULL;
    }
    if(got < 0) {
      if(!BIO_should_retry(bio)) {
	delete [] out;
	return NULL;
      }
      continue;
    }
    if(got == num) {
      out[num] = '\0';
      return out;
    } else {
      out[got] = '\0';
      return out;
    }
  }
  return NULL;
}

