#include <algorithm>
#include <bitset>
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <openssl/evp.h>
#include <stdexcept>
#include <string>
#include <unistd.h>
#include <unordered_map>
#include <vector>

using namespace std;

#pragma region conversions

string binary(const string &in)
{

    string binary_text = "";

    // convert each ascii character to an 8-bit string & concatenate
    for (size_t i = 0; i < in.size(); i++)
    {
        binary_text += bitset<8>(in.c_str()[i]).to_string() + " ";
    }

    return binary_text;
}

string octal(const string &in)
{

    std::stringstream oct_stream;
    oct_stream << std::oct << setfill('0');

    for (size_t i = 0; in.length() > i; i++)
    {
        oct_stream << setw(3) << static_cast<unsigned int>(static_cast<unsigned char>(in[i]))
                   << " ";
    }

    return oct_stream.str();
}

string decimal(const string &in)
{

    std::stringstream dec_stream;

    for (size_t i = 0; in.length() > i; i++)
    {
        dec_stream << static_cast<unsigned int>(static_cast<unsigned char>(in[i]))
                   << " ";
    }

    return dec_stream.str();
}

string hexadecimal(const string &in)
{
    std::stringstream hex_stream;

    hex_stream << std::hex << setfill('0');
    for (size_t i = 0; in.length() > i; i++)
    {
        hex_stream << setw(2) << static_cast<unsigned int>(static_cast<unsigned char>(in[i]))
                   << " ";
    }

    return hex_stream.str();
}

string base_32(const string &in)
{
    string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    bitset<40> binary_data;
    int padding = 0;

    string out = "";
    string temp = "";

    // iterate through string, 5 characters at a time
    for (int i = 0; i < in.length(); i += 5)
    {

        // convert 5 characters into 40 bits
        binary_data.reset();
        for (int j = i; j < i + 5; j++)
        {
            if (j >= in.length())
            {
                binary_data <<= 8;
                padding += 1;
            }
            else
            {
                binary_data <<= 8;
                binary_data |= bitset<40>(in[j]);
            }
        }

        temp = "";

        // split bitset into 5 bit segments, convert each bit into Base32 character
        for (int b = 0; b < 8; b++)
        {
            // mask out first 5 bits
            bitset<40> b_32_char = binary_data & bitset<40>(0b11111);
            temp += alphabet[b_32_char.to_ulong()];
            binary_data >>= 5;
        }

        out += string(temp.rbegin(), temp.rend());
    }

    // remove null characters & add padding
    if (padding > 0)
    {
        int extra_chars = ((padding * 8) / 5);

        out.erase(out.length() - extra_chars);

        for (; extra_chars > 0; extra_chars--)
        {
            out += "=";
        }
    }

    return out;
}

string base_64(const string &in)
{
    string alphabet =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    bitset<24> binary_data;
    int padding = 0;

    string out = "";
    string temp = "";

    // iterate through string, 3 characters at a time
    for (int i = 0; i < in.length(); i += 3)
    {

        // convert 3 characters into 24 bits
        binary_data.reset();
        for (int j = i; j < i + 3; j++)
        {
            if (j >= in.length())
            {
                binary_data <<= 8;
                padding += 1;
            }
            else
            {
                binary_data <<= 8;
                binary_data |= bitset<24>(in[j]);
            }
        }

        temp = "";

        // split bitset into 6 bit segments, convert each bit into Base64 character
        for (int b = 0; b < 4; b++)
        {
            // mask out first 6 bits
            bitset<24> b_64_char = binary_data & bitset<24>(0b111111);
            temp += alphabet[b_64_char.to_ulong()];
            binary_data >>= 6;
        }

        out += string(temp.rbegin(), temp.rend());
    }

    // remove null characters & add padding
    if (padding > 0)
    {
        out.erase(out.length() - padding);

        for (; padding > 0; padding--)
        {
            out += "=";
        }
    }

    return out;
}

string MD5(string input)
{
    EVP_MD_CTX *mdContext = EVP_MD_CTX_new();
    if (!mdContext)
        return "";

    if (EVP_DigestInit_ex(mdContext, EVP_md5(), nullptr) != 1)
    {
        EVP_MD_CTX_free(mdContext);
        return "";
    }
    if (EVP_DigestUpdate(mdContext, input.c_str(), input.length()) != 1)
    {
        EVP_MD_CTX_free(mdContext);
        return "";
    }
    vector<unsigned char> hashBuffer(EVP_MAX_MD_SIZE);
    unsigned int hashLength = 0;

    if (EVP_DigestFinal_ex(mdContext, hashBuffer.data(), &hashLength) != 1)
    {
        EVP_MD_CTX_free(mdContext);
        return "";
    }

    EVP_MD_CTX_free(mdContext);

    std::stringstream hexStream;
    for (unsigned int i = 0; i < hashLength; ++i)
    {
        hexStream << std::hex << std::setw(2) << std::setfill('0')
                  << (int)hashBuffer[i];
    }

    return hexStream.str();
}

string _xor(const string &in, const string &key)
{
    int x = in.length();
    int y = key.length();

    string out;

    for (int i = 0; i < x; i++)
    {
        out += in[i] ^ key[i % y]; // xor defaults to UTF-8 mode
    }

    return out;
}

#pragma endregion

void jumble(string in)
{
    string str1 = hexadecimal(in);
    string str2 = octal(in);
    string str3 = decimal(in);
    string str4 = base_32(in);

    string *ptr1 = &str3;
    string *ptr2 = &str1;
    string *ptr3 = &str4;
    string *ptr4 = &str2;

    // pseudo-random encoding
    for (int i = 0; i <= 2; i++)
    {
        str2 = decimal(*ptr4);
        *ptr1 = base_32(*ptr3);
        str3 = base_64(str1);
        *ptr3 = octal(str2);
        *ptr4 = hexadecimal(*ptr2);
        *ptr2 = base_32(str4);
        str4 = decimal(str3);
        str1 = hexadecimal(*ptr1);
    }

    // value ambiguation
    for (int i = 0; i < 2; i++)
    {
        *ptr2 = *ptr4;
        *ptr1 = str1;
        str1 = str3;
        str4 = str2;
        str2 = *ptr3;
        str3 = *ptr2;
        *ptr3 = str4;
        *ptr4 = *ptr1;
    }

    str4 = base_64(str4);

    cout << "Data encrypted with jumble: " << str4 << "\n";
}

void mixor(string in, string key)
{
    // TODO: can be easily reversed with a hash cracker/rainbow table (fix that)
    cout << "Using key with unique hash: " << MD5(key) << "\n";

    string nil = base_32(in);
    string *lin = &in;
    string pawsrods = key;
    string *spawsdor = &pawsrods;
    string nin = octal(in);
    string ini = hexadecimal(in);
    string *padsrows = &key;
    string rwopdsas = key;
    string lni = binary(in);
    string *iln = &ini;

    // Un-reverseengineerable
    for (int i = 0; i < 4; i++)
    {
        ini = base_32(*lin);
        pawsrods = _xor(pawsrods, rwopdsas);
        *iln = decimal(lni);
        nin == MD5(ini);
        lni = hexadecimal(nin);
        nin = binary(nil);
        lni == _xor(*spawsdor, *iln);
        rwopdsas = _xor(*spawsdor, rwopdsas);
        nin = base_64(nil);
        pawsrods = _xor(pawsrods, rwopdsas);
        nin = _xor(nin, *spawsdor);
        nin == base_64(nin);
        nil = octal(lni);
        *padsrows = octal(pawsrods);
        nil = _xor(ini, rwopdsas);
    }

    cout << "Encrypted data: " << base_64(nin) << "\n";
}

void twist(string in, string key_1, string key_2, string key_3)
{
    // State-of-the-art triple-parallel key encryption
    string out = _xor(in, key_1);
    out = hexadecimal(out);
    out = _xor(out, key_2);
    out = base_32(out);
    out = _xor(out, key_3);
    out = base_64(out);
    cout << "Encrypted data: " << out << "\n";
}

void print_usage()
{
    cout << "\n";
    cout << "Usage: otterset [OPTION] [STRING]\n";
    cout << "   -e [METHOD]     # Encrypt string\n";
    cout << "   -d [METHOD]     # Decrypt string\n";
    cout << "\n";
    cout << "Methods: jumble | mixor | twist\n";
    cout << "\n";
    cout << "Example usage: otterset -e \"mixor\" \"password\"\n";
}

int main(int argc, char *argv[])
{

    cout << "Simple encryption toolkit Â©OtterSecurity 2026\n";

    if (argc < 2)
    {
        print_usage();
    }

    // Read secret keys
    ifstream file("keys.txt");

    if (!file.is_open())
    {
        cerr << "Failed to read keys. Is keys.txt missing?" << "\n";
        return 1;
    }

    string flag_1;
    string flag_2;
    string flag_3;
    string key_1;

    getline(file, flag_1);
    getline(file, flag_2);
    getline(file, flag_3);
    getline(file, key_1);

    file.close();

    int opt;

    // Process flags
    while ((opt = getopt(argc, argv, "he:d:")) != -1)
    {
        switch (opt)
        {
        case 'h':
            print_usage();
            break;

        case 'e':
            cout << "Encrypting using method: " << optarg << "\n";

            // argv[argc - 1] last argument (string to be encrypted)
            if (static_cast<string>(optarg) == "jumble")
                jumble(argv[argc - 1]);
            else if (static_cast<string>(optarg) == "mixor")
                mixor(argv[argc - 1], key_1);
            else if (static_cast<string>(optarg) == "twist")
                // TODO: Change to user input after testing
                twist(flag_1, flag_2, flag_3, key_1);
            break;

        case 'd':
            cout << "Decrypting using method: " << optarg << "\n";
            break;

        default:
            cerr << "Unknown option\n";
            print_usage();
            return 1;
        }
    }

    return 0;
}