#include "pkgadd.h"
#include <fstream>
#include <iterator>
#include <cstdio>
#include <regex.h>
#include <unistd.h>

int pkgadd::run(int argc, char** argv)
{
   //
   // Check command line options
   //
   string o_root;
   string o_package;
   bool o_upgrade = false;
   bool o_force = false;

   for (int i = 1; i < argc; i++) {
      string option(argv[i]);
      if (option == "-r" || option == "--root") {
         check_argument(argv, argc, i);
         o_root = argv[i + 1];
         i++;
      } else if (option == "-u" || option == "--upgrade") {
         o_upgrade = true;
      } else if (option == "-f" || option == "--force") {
         o_force = true;
      } else if (option == "-v" || option == "--version") {
         print_version();
      } else if (option == "-h" || option == "--help") {
         print_help();
      } else if (option[0] == '-' || !o_package.empty()) {
         print_invalid_option(option);
      } else {
         o_package = option;
      }
   }

   if (o_package.empty())
      print_option_missing();

   //
   // Check UID
   //
   if (getuid()) {
      print_error() << "only root can install/upgrade packages" << endl;
      return EXIT_ERROR;
   }

   //
   // Install/upgrade package
   //
   db_open(o_root);

   pair<string, pkginfo_t> package = pkg_open(o_package);

   bool installed = db_find_pkg(package.first);
   if (installed && !o_upgrade) {
      print_error() << "package " << package.first << " already installed (use -u to upgrade)" << endl;
      return EXIT_ERROR;
   } else if (!installed && o_upgrade) {
      print_error() << "package " << package.first << " not previously installed (skip -u to install)" << endl;
      return EXIT_ERROR;
   }

   set<string> files = db_find_conflicts(package.first, package.second);

   if (!files.empty()) {
      if (o_force) {
         db_rm_files(files);  // Remove conflicts
      } else {
         copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n"));
         print_error() << "listed file(s) already installed (use -f to ignore and overwrite)" << endl;
         return EXIT_ERROR;
      }
   }

   if (o_upgrade)
      db_rm_pkg(package.first, make_keep_list(package.second.files, read_config()));

   db_add_pkg(package.first, package.second);

   db_commit();

   pkg_install(o_package);

   return EXIT_OK;
}

void pkgadd::print_help() const
{
   cout << "Usage: " << name() << " [OPTION]... [FILE]..." << endl
	<< "Add a package to the system." << endl << endl
        << "  -u, --upgrade       upgrade package with the same name" << endl
        << "  -f, --force         force install, overwrite conflicting files" << endl
        << "  -r, --root <path>   use alternative root directory" << endl
        << "  -h, --help          print this help and exit" << endl
        << "  -v, --version       output version information and exit" << endl << endl
	<< "Report bugs to <johne@rootlinux.org>." << endl;
   exit(EXIT_OK);
}

vector<rule_t> pkgadd::read_config() const
{
   vector<rule_t> rules;
   unsigned int linecount = 0;
   const string filename = root + PKGADD_CONF;
   ifstream in(filename.c_str());

   if (in) {
      while (!in.eof()) {
         string line;
         getline(in, line);
         linecount++;
         if (!line.empty() && line[0] != '#') {
            if (line.length() >= PKGADD_CONF_MAXLINE) {
               print_error() << filename << ":" << linecount << ": line too long, aborting" << endl;
               exit(EXIT_ERROR);
            }

            char event[PKGADD_CONF_MAXLINE];
            char pattern[PKGADD_CONF_MAXLINE];
            char action[PKGADD_CONF_MAXLINE];
            char dummy[PKGADD_CONF_MAXLINE];
            if (sscanf(line.c_str(), "%s %s %s %s\n", event, pattern, action, dummy) != 3) {
               print_error() << filename << ":" << linecount << ": too many arguments, aborting" << endl;
               exit(EXIT_ERROR);
            }

            if (!strcmp(event, "UPGRADE")) {
               rule_t rule;
               rule.event = rule_t::UPGRADE;
               rule.pattern = pattern;
               if (!strcmp(action, "YES")) {
                  rule.action = true;
               } else if (!strcmp(action, "NO")) {
                  rule.action = false;
               } else {
                  print_error() << filename << ":" << linecount << ": '" << action << "' unknown action, should be YES or NO, aborting" << endl;
                  exit(EXIT_ERROR);
               }
               rules.push_back(rule);
            } else {
               print_error() << filename << ":" << linecount << ": '" << event << "' unknown event, aborting" << endl;
               exit(EXIT_ERROR);
            }
         }
      }
      in.close();
   }

#ifndef NDEBUG
   cerr << "Configuration:" << endl;
   for (vector<rule_t>::const_iterator j = rules.begin(); j != rules.end(); j++) {
      cerr << "   " << (*j).pattern << "\t" << (*j).action << endl;
   }
   cerr << endl;
#endif

   return rules;
}

set<string> pkgadd::make_keep_list(const set<string>& files, const vector<rule_t>& rules) const
{
   set<string> keep_list;

   for (set<string>::const_iterator i = files.begin(); i != files.end(); i++) {
      for (vector<rule_t>::const_reverse_iterator j = rules.rbegin(); j != rules.rend(); j++) {
         if ((*j).event == rule_t::UPGRADE) {
            regex_t preg;
            if (regcomp(&preg, (*j).pattern.c_str(), REG_EXTENDED | REG_NOSUB)) {
               print_error() << "error compiling regular expression '" << (*j).pattern << "', aborting" << endl;
               exit(EXIT_ERROR);
            }

            if (!regexec(&preg, (*i).c_str(), 0, 0, 0)) {
               if (!(*j).action)
                  keep_list.insert(keep_list.end(), *i);
               break;
            }
         }
      }
   }

#ifndef NDEBUG
   cerr << "Keep list:" << endl;
   for (set<string>::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) {
      cerr << "   " << (*j) << endl;
   }
   cerr << endl;
#endif

   return keep_list;
}