#pragma once

#include <cstdlib>
#include <string>
#include <vector>

namespace ArgumentParser {

class ArgParser {
public:
  ArgParser(const std::string& name) : name(name) {}

  bool Parse(const std::vector<std::string>& argv);

  ArgParser& AddStringArgument(std::string long_name);

  ArgParser& AddStringArgument(char short_name);

  ArgParser& AddStringArgument(char short_name, std::string argument); 

  std::string GetStringValue(std::string long_name);

  std::string GetStringValue(char short_name);

  ArgParser& StoreValue(std::string& value);

  ArgParser& Default(const char* default_value);

  ArgParser& AddIntArgument(std::string argument);

  ArgParser& AddIntArgument(char short_name, std::string argument);

  ArgParser& AddIntArgument(char short_name);

  int32_t GetIntValue(std::string long_name);

  int32_t GetIntValue(char short_name);

  int32_t GetIntValue(std::string long_name, int32_t index);

  int32_t GetIntValue(char short_name, int32_t index);

  ArgParser& StoreValues(std::vector<int32_t>& values);

  ArgParser& MultiValue();

  ArgParser& MultiValue(size_t min_arguments_count);

  ArgParser& AddFlag(char short_name, std::string long_name);

  ArgParser& AddFlag(char short_name);

  ArgParser& AddFlag(std::string long_name);

  bool GetFlag(char short_name);

  bool GetFlag(std::string long_name);

  ArgParser& Default(bool value);

  ArgParser& StoreValue(bool& value);

  ArgParser& Positional();

  ArgParser& AddHelp(char short_name, std::string long_name, std::string description);

  bool Help();

  std::string HelpDescription();

  ArgParser& AddStringArgumentWithDescription(std::string long_name, std::string description);

  ArgParser& AddStringArgumentWithDescription(char short_name, std::string description);

  ArgParser& AddStringArgumentWithDescription(char short_name, std::string long_name, std::string description);

  ArgParser& AddIntArgumentWithDescription(std::string long_name, std::string description);

  ArgParser& AddIntArgumentWithDescription(char short_name, std::string description);

  ArgParser& AddIntArgumentWithDescription(char short_name, std::string long_name, std::string description);

  ArgParser& AddFlagWithDescription(std::string long_name, std::string description);

  ArgParser& AddFlagWithDescription(char short_name, std::string description);

  ArgParser& AddFlagWithDescription(char short_name, std::string long_name, std::string description);

private:
  std::string name;

  struct Argument {
  std::string long_name;
  char short_name = 0;
  std::string argument_type;
  size_t min_arguments_count = 1;
  std::vector<std::string> value;
  std::vector<int32_t>* int_values_ptr;
  std::string* str_values_ptr;
  bool* bool_values_ptr;
  bool is_multi_value = false;
  bool is_store_value = false;
  bool is_positional = false;
  bool help_requested = false;
  std::string help_message;
  bool is_default;
  };

  std::vector<Argument> arguments;

  int32_t FindByLongName(const std::string& long_name);

  int32_t FindByShortName(char short_name);

};

}  // namespace ArgumentParser


#include "ArgParser.h"

#include <cstdlib>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <string_view>
#include <charconv>

namespace ArgumentParser {

bool IsValue(const std::string& buff) {
  return buff.size() == 1 || std::isdigit(buff[1]);
}

int32_t ArgParser::FindByLongName(const std::string& long_name) {
  for (int i = 0; i < arguments.size(); ++i) {
    if (arguments[i].long_name == long_name) {
      return i;
    }
  }
  return -1;
}

int32_t ArgParser::FindByShortName(char short_name) {
  for (int i = 0; i < arguments.size(); ++i) {
    if (arguments[i].short_name == short_name) {
      return i;
    }
  }
  return -1;
}

bool ArgParser::Parse(const std::vector<std::string>& argv) {
  if (arguments.empty()) {
    return true;
  }

  for (int i = 1; i < argv.size(); ++i) {

    if (argv[i][0] != '-' || arguments[FindByLongName(argv[i].substr(1))].is_positional || 
      arguments[FindByShortName(argv[i][1])].is_positional) {
      int positional_index = -1;
      for (int j = 0; j < arguments.size(); ++j) {
        if (arguments[j].is_positional) {
          positional_index = j;
          break;
        }
      }

      if (positional_index != -1) {
        arguments[positional_index].value.push_back(argv[i]);
        if (arguments[positional_index].is_store_value) {
          if (arguments[positional_index].argument_type == "int")
            arguments[positional_index].int_values_ptr->push_back(stoi(argv[i]));
          else if (arguments[positional_index].argument_type == "string")
            *arguments[positional_index].str_values_ptr = argv[i];
          else if(arguments[positional_index].argument_type == "bool")
            *arguments[positional_index].bool_values_ptr = argv[i]=="true"; 
        }
      } 
    } else {
      if (argv[i][1] != '-' && argv[i].find("=") == std::string::npos) { 
        for (size_t j = 1; j < argv[i].size(); ++j) { 
          std::string arg(1, argv[i][j]);
          int index_of_argument = FindByShortName(arg[0]);
          if (arguments[index_of_argument].argument_type == "bool") {
            arguments[index_of_argument].value.push_back("1");
            if (arguments[index_of_argument].is_store_value) {
              *arguments[index_of_argument].bool_values_ptr = true;
            }
          }
        }
      } else {
        std::string arg;
        std::string value;
        size_t equal_pos = argv[i].find("=");
        if(equal_pos != std::string_view::npos) {
        if(argv[i][1] == '-'){
            arg = std::string_view(argv[i]).substr(2, equal_pos - 2);
        } else{
            arg = std::string_view(argv[i]).substr(1, equal_pos - 1);
        }
        value = std::string_view(argv[i]).substr(equal_pos + 1);
        } else {
        arg = std::string_view(argv[i]).substr(2);
        }
        int index_of_argument = FindByLongName(arg);
        if (index_of_argument == -1) {
          index_of_argument = FindByShortName(arg[0]);
        }

        if (arguments[index_of_argument].argument_type == "int") {
          if (arguments[index_of_argument].is_multi_value) {
            arguments[index_of_argument].value.push_back(value);
            if (arguments[index_of_argument].is_store_value) {
              arguments[index_of_argument].int_values_ptr->push_back(stoi(value));
            }
          } else {
            arguments[index_of_argument].value.clear();
            arguments[index_of_argument].value.push_back(value);
            if (arguments[index_of_argument].is_store_value) {
              arguments[index_of_argument].int_values_ptr->clear();
              arguments[index_of_argument].int_values_ptr->push_back(stoi(value));
            }
          }
        } else if (arguments[index_of_argument].argument_type == "string") {
          arguments[index_of_argument].value.push_back(value);
          if (arguments[index_of_argument].is_store_value) {
              *arguments[index_of_argument].str_values_ptr = value;
          } 
          if (arguments[index_of_argument].value.size() > 0 && arguments[index_of_argument].is_multi_value) {
              arguments[index_of_argument].value.push_back(value);
          }
        } else if (arguments[index_of_argument].argument_type == "bool") {
          arguments[index_of_argument].value.push_back("1");
          if(arguments[index_of_argument].is_store_value) {
            *arguments[index_of_argument].bool_values_ptr = true;
          }
        
      }
      }
    }
  }

  for(const auto& argument : arguments) {
    if((argument.value.empty() || argument.value.size() < argument.min_arguments_count) && !argument.help_requested) {
      return false;
    }
  }

  return true;
}

ArgParser& ArgParser::AddStringArgument(std::string long_name) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.argument_type = "string";
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddStringArgument(char short_name) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.argument_type = "string";
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddStringArgument(char short_name, std::string long_name) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.short_name = short_name;
  new_argument.argument_type = "string";
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::StoreValue(std::string& value) {
  arguments.back().value.push_back(value);
  arguments.back().str_values_ptr = &value;
  arguments.back().is_store_value = true;
  return *this;
}

std::string ArgParser::GetStringValue(std::string long_name) {
  int32_t index_of_argument = FindByLongName(long_name);
  std::string value = arguments[index_of_argument].value[0];
  return value;
}

std::string ArgParser::GetStringValue(char short_name) {
  int32_t index_of_argument = FindByShortName(short_name);
  std::string value = arguments[index_of_argument].value[0];
  return value;
}

ArgParser& ArgParser::Default(const char* default_value) {
  if (!(arguments.back().argument_type == "string")) {
    exit(EXIT_FAILURE);
  }
  if (arguments.back().value.empty()) {
    arguments.back().value.push_back(default_value);
  }
  arguments.back().is_default = true;
  return *this;
}

ArgParser& ArgParser::AddIntArgument(std::string long_name) {
  Argument new_argument;
  new_argument.long_name = std::move(long_name);
  new_argument.argument_type = "int";
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddIntArgument(char short_name) {
  Argument new_argument;
  new_argument.short_name = std::move(short_name);
  new_argument.argument_type = "int";
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddIntArgument(char short_name, std::string long_name) {
  Argument new_argument;
  new_argument.long_name = std::move(long_name);
  new_argument.short_name = std::move(short_name);
  new_argument.argument_type = "int";
  arguments.push_back(new_argument);
  return *this;
}

int32_t ArgParser::GetIntValue(std::string long_name) {
  int32_t index_of_argument = FindByLongName(long_name);
  int32_t value = stoi(arguments[index_of_argument].value[0]);
  return value;
}

int32_t ArgParser::GetIntValue(char short_name) {
  int32_t index_of_argument = FindByShortName(short_name);
  int32_t value = stoi(arguments[index_of_argument].value[0]);
  return value;
}

int32_t ArgParser::GetIntValue(std::string long_name, int32_t index) {
    int32_t index_of_argument = FindByLongName(long_name);
    return stoi(arguments[index_of_argument].value[index]);
    
}

ArgParser& ArgParser::StoreValues(std::vector<int32_t>& values) {
  arguments.back().int_values_ptr = &values;
  arguments.back().is_store_value = true;
  return *this;
}

ArgParser& ArgParser::MultiValue() {
  arguments.back().is_multi_value = true;
  return *this;
}

ArgParser& ArgParser::MultiValue(size_t min_arguments_count) {
  arguments.back().is_multi_value = true;
  arguments.back().min_arguments_count = min_arguments_count;

  return *this;
}

ArgParser& ArgParser::AddFlag(char short_name, std::string long_name) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.long_name = long_name;
  new_argument.argument_type = "bool";
  arguments.push_back(new_argument);

  return *this;
}

ArgParser& ArgParser::AddFlag(char short_name) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.argument_type = "bool";
  arguments.push_back(new_argument);

  return *this;
}

ArgParser& ArgParser::AddFlag(std::string long_name) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.argument_type = "bool";
  arguments.push_back(new_argument);

  return *this;
}

bool ArgParser::GetFlag(char short_name) {
  int32_t index_of_argument = FindByShortName(short_name);
  if (arguments[index_of_argument].value.back() == "1") {
    return true;
  }
  return false;
}

bool ArgParser::GetFlag(std::string long_name) {
  int32_t index_of_argument = FindByLongName(long_name);
  if (arguments[index_of_argument].value.back() == "1") {
    return true;
  }
  return false;
}

ArgParser& ArgParser::Default(bool default_value) {
  if (!(arguments.back().argument_type == "bool")) {
    exit(EXIT_FAILURE);
  }
  if (arguments.back().value.empty()) {
    arguments.back().value.push_back(default_value ? "1" : "0");
  }
  arguments.back().is_default = true;
  return *this;
}

ArgParser& ArgParser::StoreValue(bool& value) {
  arguments.back().bool_values_ptr = &value;
  arguments.back().is_store_value = true;
  return *this;
}

ArgParser& ArgParser::Positional() {
  arguments.back().is_positional = true;
  return *this;
}

ArgParser& ArgParser::AddHelp(char short_name, std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.long_name = long_name;
  new_argument.help_message = description;
  new_argument.help_requested = true;
  new_argument.argument_type = "bool";
  arguments.push_back(new_argument);

  return *this;
}

bool ArgParser::Help() {
  for(int i = 0; i < arguments.size(); ++i) {
    if (arguments[i].help_requested) {
      return true;
    }
  }
  return false;
}

std::string ArgParser::HelpDescription() {
  std::stringstream help_string;
  help_string << name << "\n";
  for (const auto &arg : arguments) {
    if (arg.help_requested) {
      help_string << arg.help_message << "\n\n";
      continue;
    }
      help_string << (arg.short_name != 0 ? "-" : "") 
              << (arg.short_name != 0 ? std::string(1, arg.short_name) + ", " : "")
              << (arg.long_name.empty() ? "" : "--")
              << arg.long_name
              << ", <" << arg.argument_type << ">, " 
              << arg.help_message;


    if (arg.is_multi_value) {
      help_string << " [repeated";
      if (arg.min_arguments_count > 0) {
        help_string << ", min args = " << arg.min_arguments_count;
      }
      help_string << "]";
    }
    
    if (arg.is_default) {
      if(arg.argument_type == "string"){
        if (arg.is_default) {
          help_string << " [default = " << arg.value[0] << "]";
        }
      }else if(arg.argument_type == "int" && arg.is_default && !arg.value.empty()){
        help_string << " [default = " << arg.value[0] << "]";
      }else if(arg.argument_type == "bool"){
        if (arg.is_default) {
          help_string << " [default = " << (arg.value[0] == "1" ? "true" : "false") << "]";
        }
      }
    }
    help_string << "\n\n";
  }
  help_string << "\n\n" << (arguments[0].short_name != 0 ? "-" : "") 
            << (arguments[0].short_name != 0 ? std::string(1, arguments[0].short_name) + ", " : "")
            << "--help Display this help and exit\n";

  return help_string.str();
}



ArgParser& ArgParser::AddStringArgumentWithDescription(std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.argument_type = "string";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddStringArgumentWithDescription(char short_name, std::string description) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.argument_type = "string";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddStringArgumentWithDescription(char short_name, std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.short_name = short_name;
  new_argument.argument_type = "string";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddIntArgumentWithDescription(std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = std::move(long_name);
  new_argument.argument_type = "int";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddIntArgumentWithDescription(char short_name, std::string description) {
  Argument new_argument;
  new_argument.short_name = std::move(short_name);
  new_argument.argument_type = "int";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddIntArgumentWithDescription(char short_name, std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = std::move(long_name);
  new_argument.short_name = std::move(short_name);
  new_argument.argument_type = "int";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddFlagWithDescription(std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.argument_type = "bool";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddFlagWithDescription(char short_name, std::string description) {
  Argument new_argument;
  new_argument.short_name = short_name;
  new_argument.argument_type = "bool";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}

ArgParser& ArgParser::AddFlagWithDescription(char short_name, std::string long_name, std::string description) {
  Argument new_argument;
  new_argument.long_name = long_name;
  new_argument.short_name = short_name;
  new_argument.argument_type = "bool";
  new_argument.help_message = description;
  new_argument.help_requested = true;
  arguments.push_back(new_argument);
  return *this;
}


} // namespace ArgumentParser


перепиши структуры на наследования(никаких вируалов лямбдв функций std::function и тп) только дефолтные