#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <list>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <direct.h>
#include <windows.h>
#include <libloaderapi.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <mmsystem.h>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>

#include "csv.h"
#include "str.h"
#include "str_tok.h"
#include "file.h"
#include "st.h"
//#include "curses.h"

using namespace std;
using namespace csv_reader;
using namespace string_tools;
using namespace io_tools;

class copy_command {
   bool r;
   bool v;
public:
   copy_command(bool recurse, bool verbose) {
      r = recurse;
      v = verbose;
   }
   void copy_file(const string & src, const string & dst) {
      if(CopyFile(src.c_str(), dst.c_str(), FALSE) == FALSE) {
         throw "unable to copy...";
      }
   }
   void copy_file_to_dir(const string & src, const string & dst) {
      string::size_type ptr = src.rfind('\\');
      if(ptr == string::npos) {
         string dst0 = dst + "\\" + src;
         if(CopyFile(src.c_str(), dst0.c_str(), FALSE) == FALSE) {
            throw "unable to copy...";
         }
      }
      else {
         string ff = src.substr(ptr+1);
         string dst0 = dst + "\\" + ff;
         if(CopyFile(src.c_str(), dst0.c_str(), FALSE) == FALSE) {
            throw "unable to copy...";
         }
      }
   }

   bool xcopy(const string & src0, const string & dst0) {
      File::dir_vector_type dir;
      list< string > src_file_list;
      list< string > dst_file_list;
      string dst = dst0;
      string src = src0;
      bool ret = true;
      File f_src(src), f_dst(dst);

      if(f_src.is_directory()) {
         string temp = f_src.get_name();
         dst += "\\" + temp;
         f_dst = File(dst);
         if(CreateDirectory(f_dst.get_full_path().c_str(), NULL) != FALSE) {
            if(v) cout << "create directory: " << dst << endl;
         }
      }

      dst_file_list.push_back(f_dst.get_full_path());
      src_file_list.push_back(f_src.get_full_path());
      while(src_file_list.size() && dst_file_list.size() && ret) {
         src = src_file_list.front();
         src_file_list.pop_front();
         dst = dst_file_list.front();
         dst_file_list.pop_front();
         f_dst = File(dst);
         f_src = File(src);
         if(!f_src.exists()) {
            continue;
         }
         if(f_src.is_directory() && f_dst.is_directory()) {
            if(f_src.dir(dir)) {
               for(size_t i = 0; i < dir.size(); i++) {
                  File ff(f_src.get_full_path() + "\\" + dir[i].get_name());
                  if(ff.exists() && ff.is_directory() && dir[i].get_name() != ".." && dir[i].get_name() != ".") {
                     if(File(dst + "\\" + dir[i].get_name()).exists()) {
                        dst_file_list.push_back(dst + "\\" + dir[i].get_name());
                        src_file_list.push_back(src + "\\" + dir[i].get_name());
                     }
                     else if(CreateDirectory((dst + "\\" + dir[i].get_name()).c_str(), NULL) != FALSE) {
                        if(v) cout << "create directory: " << dst << "\\" << dir[i].get_name() << endl;
                        dst_file_list.push_back(dst + "\\" + dir[i].get_name());
                        src_file_list.push_back(src + "\\" + dir[i].get_name());
                     }
                     else {
                        ret = false;
                        break;
                     }
                  }
               }
               if(ret)
               for(size_t i = 0; i < dir.size(); i++) {
                  File ff(src + "\\" + dir[i].get_name());
                  if(ff.is_file() && ff.exists() && dir[i].get_name() != ".." && dir[i].get_name() != ".") {
                     string temp = dst + "\\" + dir[i].get_name();
                     if(CopyFile(ff.get_full_path().c_str(), temp.c_str(), FALSE) != FALSE) {
                        if(v) cout << "copy file: " << temp << endl;
                     }
                     else {
                        ret = false;
                        break;
                     }
                  }
               }
            }
         }
      }

      return ret;
   }

   bool copy(const string & src0, const string & dst0) {
      File::dir_vector_type dir;
      list< string > src_file_list;
      list< string > dst_file_list;
      string dst = dst0;
      string src = src0;
      bool ret = true;
      File f_src(src), f_dst(dst);

      if(f_src.is_directory()) {
         string temp = f_src.get_name();
         dst += "\\" + temp;
         f_dst = File(dst);
         if(CreateDirectory(f_dst.get_full_path().c_str(), NULL) != FALSE) {
            if(v) cout << "create directory: " << dst << endl;
         }
      }

      dst_file_list.push_back(f_dst.get_full_path());
      src_file_list.push_back(f_src.get_full_path());
      while(src_file_list.size() && dst_file_list.size() && ret) {
         src = src_file_list.front();
         src_file_list.pop_front();
         dst = dst_file_list.front();
         dst_file_list.pop_front();
         f_dst = File(dst);
         f_src = File(src);
         if(!f_src.exists()) {
            continue;
         }
         if(f_src.is_directory() && f_dst.is_directory()) {
            if(f_src.dir(dir)) {
               for(size_t i = 0; i < dir.size(); i++) {
                  File ff(f_src.get_full_path() + "\\" + dir[i].get_name());
                  if(ff.exists() && ff.is_directory() && dir[i].get_name() != ".." && dir[i].get_name() != ".") {
                     //if(CreateDirectory((dst + "\\" + dir[i].get_name()).c_str(), NULL) != FALSE) {
                        if(v) cout << "create directory: " << dst << "\\" << dir[i].get_name() << endl;
                        dst_file_list.push_back(dst + "\\" + dir[i].get_name());
                        src_file_list.push_back(src + "\\" + dir[i].get_name());
                     //}
                     //else {
                        //ret = false;
                        //break;
                     //}
                  }
               }
               for(size_t i = 0; i < dir.size(); i++) {
                  File ff(src + "\\" + dir[i].get_name());
                  if(ff.is_file() && ff.exists()) {
                     string temp = dst + "\\" + dir[i].get_name();
                     if(CopyFile(ff.get_full_path().c_str(), temp.c_str(), FALSE) != FALSE) {
                        if(v) cout << "copy file: " << temp << endl;
                     }
                     else {
                        ret = false;
                        break;
                     }
                  }
               }
            }
         }
      }

      return ret;
   }

   void copy_dir(const string & src, const string & dst) {
      if(r) {
         xcopy(src, dst);
      }
      else {
         //copy(src, dst);
         cout << src << " is a directory." << endl;
      }
   }

   int run(const string & src0, const string & dst0) {
      string src = src0;
      string dst = dst0;
      int rc = 0;
      if(dst == ".") {
         string::size_type ptr = src.rfind("\\");
         if(ptr != string::npos) {
            dst = src.substr(ptr+1);
         }
         else {
            dst = src;
         }
      }
      File f_src(src);
      File f_dst(dst);
      if(f_src.is_directory() && f_dst.is_directory()) {
         copy_dir(f_src.get_full_path(),f_dst.get_full_path());
      }
      /*
      else if(f_src.is_directory() && !f_dst.is_file()) {
         copy_dir(f_src.get_full_path(),f_dst.get_full_path());
      }
      */
      else if(f_src.is_file() && f_dst.is_directory()) {
         copy_file_to_dir(f_src.get_full_path(),f_dst.get_full_path());
      }
      else if(f_src.is_file()) {
         copy_file(f_src.get_full_path(),dst);
      }
      else {
         cerr << "cp error..." << endl;
         rc = 1;
      }
      return rc;
   }
};

#ifdef NO_MAIN
extern "C" {
extern int cp_main(int argc, char ** argv);
}
int cp_main(int argc, char ** argv)
#else
int main(int argc, char ** argv) 
#endif
{
   int rc = 0;
   bool recurse = false;
   bool verbose = false;
   string src,dst;
   for(size_t i = 1; i < argc; i++) {
      if(string(argv[i]) == "-r") {
         recurse = true;
      }
      else if(string(argv[i]) == "-v") {
         verbose = true;
      }
      else if(!src.size()) {
         src = argv[i];
      }
      else if(!dst.size()) {
         dst = argv[i];
      }
   }
   try {
      copy_command cp(recurse, verbose);
      rc = cp.run(src,dst);
   }
   catch(const char * message) {
      cerr << message << endl;
      rc = 1;
   }
   catch(...) {
      cerr << "unknown exception..." << endl;
      rc = 1;
   }
   return rc;
}

