/*-----------------------------------------------------------------------------

Copyright (C) 2002, 2006, 2009, 2010, 2011.

A. Ronald Gallant
Post Office Box 659 
Chapel Hill NC 27514 
USA   

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

-----------------------------------------------------------------------------*/

#include "flagmap.h"

namespace flagmap {

  void optional_part_example(std::map<std::string,flagmap::fval>& flag)
  {
    flag["a"]    = fval("-a           single letter, no argument");
    flag["b"]    = fval("-b           single letter, no argument");
    flag["c"]    = fval("-c           single letter, no argument");
    flag["e"]    = fval("-c           single letter, no argument");
    flag["i"]    = fval("-i    value  single letter, int argument",'i'); 
    flag["d"]    = fval("-d    value  single letter, double argument",'d'); 
    flag["s"]    = fval("-s    name   single letter, string argument",'s'); 
    flag["ab"]   = fval("-ab          multiple letter, no argument");
    flag["be"]   = fval("-be          multiple letter, no argument");
    flag["abc"]  = fval("-abc         multiple letter, no argument");
    flag["int"]  = fval("-int  value  multiple letter, int argument",'i');
    flag["dbl"]  = fval("-dbl  value  multiple letter, double argument",'d');
    flag["str"]  = fval("-str  name   multiple letter, string argument",'s');
  }
  
  std::istream& process_filename_example(std::istream& fin, 
      std::string progname,
      std::map<std::string,flagmap::fval> flag,
      std::vector<std::string> file)
  {
    using namespace std;

    if (flag["f"].set) cout << "flag f set" << '\n';   
    if (flag["a"].set) cout << "flag a set" << '\n'; 
    if (flag["b"].set) cout << "flag b set" << '\n'; 
    if (flag["c"].set) cout << "flag c set" << '\n'; 
    if (flag["e"].set) cout << "flag e set" << '\n'; 
    if (flag["i"].set) cout << "flag i set, i = " << flag["i"].i << '\n';
    if (flag["d"].set) cout << "flag d set, d = " << flag["d"].d << '\n';
    if (flag["s"].set) cout << "flag s set, s = " << flag["s"].s << '\n';
    if (flag["ab"].set) cout << "flag ab set" << '\n'; 
    if (flag["be"].set) cout << "flag be set" << '\n'; 
    if (flag["abc"].set) cout << "flag abc set" << '\n'; 
    if (flag["int"].set) cout << "flag int set, i = " << flag["int"].i << '\n';
    if (flag["dbl"].set) cout << "flag dbl set, d = " << flag["dbl"].d << '\n';
    if (flag["str"].set) cout << "flag str set, s = " << flag["str"].s << '\n';

    char ch; 
    while (fin.get(ch)) cout << ch; 
    return fin;
  }

  void eat_space(char* str)
  {
    while (isspace(*str)) ++str;
    char* end = str;
    while (*end) ++end;
    --end;
    while (isspace(*end)) {*end = '\0'; --end;}
  }
  
  std::ostream& usage_message (std::ostream& fout, std::string progname, 
           std::map<std::string,flagmap::fval> flag) 
  {
    if (flag["filenames_expected"].set) {
      bool flag_f_exists = !(flag.end() == flag.find("f"));
      if (flag_f_exists) {
        fout << "usage:  " 
             << progname << " -f [remaining options] < filename \n"; 
        fout << "usage:  " 
             << progname << " [remaining options] filename(s) \n"; 
      }
      else {
        fout << "usage:  " 
             << progname << " [options] filename(s) \n"; 
        fout << "usage:  " 
             << progname << " [options] < filename \n"; 
      }
    }
    else {
      fout << "usage:  " << progname << " [options] \n"; 
    }

    std::map<std::string,fval>::const_iterator fitr = flag.begin();
    fout << "options: " << fitr->second.msg << '\n';
    for (++fitr ; fitr != flag.end() ; ++fitr) {
      if (fitr->second.exists() && (strlen(fitr->second.msg) > 0)) {
        fout << "         " << fitr->second.msg << '\n';
      }
    }
    return fout;
  }

  void eoflg(std::string progname, const char* flg) 
  {
    std::cerr << "Error, "<<progname<<", attempt to read past "<< flg 
      << ", probably due to missing argument(s).\n";
    exit(1);
  }
  
  void badflag(std::string progname, const char* flg) 
  {
    std::cerr<<"Error, "<<progname<<", unknown flag: "<< flg << ".\n";
  }
  
  void badflag(std::string progname, const char* flg, const char* str, char c) 
  {
    switch (c) {
    case 'i':
      std::cerr<<"Error, "<<progname<<", bad argument, flag " << flg <<
        " expects an int but saw: " << str << ".\n";
      break;
    case 'd':
      std::cerr<<"Error, "<<progname<<", bad argument, flag " << flg<<
        " expects a double but saw: " << str << ".\n";
      break;
    }
  }

  int process_flag(int argc, char** argp, std::string& progname,
    std::map<std::string,flagmap::fval>& flag, std::vector<std::string>& file)
  {
    using namespace std;
  
    progname = argp[0];

    if (flag["filenames_expected"].set && argc == 1) {
      if (flag["f"].exists()) {
        usage_message(cerr,progname,flag); 
        return 1;
      }
      else {
        return 0;
      }
    }
  
    int argi = 1;
    while ( (argi < argc) && (argp[argi][0] == '-') ) {
      char* nptr; char* end; char** endptr=&end;
      char* str = &argp[argi][1];
      if (flag[str].exists()) {
        flag[str].set = true;
        switch (flag[str].arg) {
        case '\0':
          break;
        case 'i':
          if (++argi == argc) eoflg(progname,str);
          nptr = argp[argi]; 
          eat_space(nptr);
          flag[str].i = int(strtol(nptr,endptr,10));
          if (**endptr) {badflag(progname,str,nptr,'i'); return 1;}
          break;
        case 'd':
          if (++argi == argc) eoflg(progname,str);
          nptr = argp[argi]; 
          eat_space(nptr);
          flag[str].d = strtod(nptr,endptr);
          if (**endptr) {badflag(progname,str,nptr,'d'); return 1;}
          break;
        case 's':
          if (++argi == argc) eoflg(progname,str);
          flag[str].s = argp[argi];
          break;
        default : 
          cerr << "Error, " << progname 
               << ", only i, d, or s are allowed as argument types.\n";
          return 1;
        }
      }
      else {
        flag.erase(str);
        char s[2] ; s[1]='\0';
        for (char* cptr=str; ( s[0] = *cptr ) ; cptr++) {
          if (flag[s].exists()) {
            flag[s].set = true;
            switch (flag[s].arg) {
            case '\0':
              break;
            case 'i':
              if (++argi == argc) eoflg(progname,str);
              nptr = argp[argi]; 
              eat_space(nptr);
              flag[s].i = int(strtol(nptr,endptr,10));
              if (**endptr) {badflag(progname,s,nptr,'i'); return 1;}
              break;
            case 'd':
              if (++argi == argc) eoflg(progname,str);
              nptr = argp[argi]; 
              eat_space(nptr);
              flag[s].d = strtod(nptr,endptr);
              if (**endptr) {badflag(progname,s,nptr,'d'); return 1;}
              break;
            case 's':
              if (++argi == argc) eoflg(progname,str);
              flag[s].s = argp[argi];
              break;
            default : 
              cerr << "Error, " << progname 
                   << ", only i, d, or s are allowed as argument types.\n";
              return 1;
            }
          }
          else {
            badflag(progname, argp[argi]);
            return 1;
          }
        }
      }
    ++argi;
    }
  
    if (flag["h"].set) {usage_message(cout, progname, flag); exit(0);}
  
    file.clear();
  
    for ( ; argi < argc ; ++argi) {
      file.push_back(argp[argi]);
    }

    return 0;
  }

  int process_file(std::string progname, 
    std::map<std::string,flagmap::fval> flag, 
    std::vector<std::string> file)
  {
    if (flag["filenames_expected"].set) { 

      if (flag["f"].set) {

        if (file.empty()) {
          process_filename(std::cin, progname, flag, file);
          return 0;
        }
	else {
          std::cerr 
            << "Error, " << progname << ", flag -f set but "
            << "filename(s) were on the command line.\n";
          return 1;
        }
      }

      else if (file.empty()) {
      
        if (flag["f"].exists()) {
           std::cerr 
             << "Error, "<< progname << ", filename expected but none found; "
             << "a flag that expects\na string argument may have eaten a "
             << "filename.\n";
          return 1;
        }
        else {
          process_filename(std::cin, progname, flag, file);
          return 0;
        }

      }
      else {
        std::vector<std::string>::size_type idx = 0;
        for(idx = 0; idx < file.size(); ++idx) {

          std::ifstream fin(file[idx].c_str()); 

          if (fin) {
            process_filename(fin, progname, flag, file);
          }
          else {
            std::cerr << "Error, " << progname << ", cannot open " 
                << file[idx] << "\n";
           return 1;
          }
        }
      } 
    }

    return 0;
  }

} 
