#include <iostream>

#include <sys/param.h>
#include <getopt.h>

#include <errno.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <sys/types.h>

#include <vector>

#include "CryptoAPI/ICrypto.h"

typedef std::vector<char> buffer_t;

const char* const c_clSmallOptions = "hdei:o:n:k:";
const struct option LongOptions[] =
{
{"help",       0,      NULL,       c_clSmallOptions[0]},
{"decrypt",    0,      NULL,       c_clSmallOptions[1]},
{"encrypt",    0,      NULL,       c_clSmallOptions[2]},
{"input",      0,      NULL,       c_clSmallOptions[3]},
{"output",     0,      NULL,       c_clSmallOptions[4]},
{"nonce",      0,      NULL,       c_clSmallOptions[5]},
{"key",        0,      NULL,       c_clSmallOptions[6]},
{NULL,         0,      NULL,       0}
};

const size_t hexs_cnt = 22;
const char* hexs = "0123456789abcdefABCDEF\0";

using namespace std;

//--------------------------------------------------------------------------------------
void print_usage(FILE* stream, const char* prog_name)
{
    fprintf(stream, "Usage: %s options \n", prog_name);
    fprintf(stream,
        "   -h      		--help                  Display this usage information\n"
        "   -d              --decrypt               Decrypt input file content and store to output file \n"
        "   -e              --encrypt               Encrypt input file content and store to output file \n"
        "   -i path         --input path            Set input file path \n"
        "   -o path         --output path           Set output file path \n"
        "   -n hex value    --nonce hex value       Set nonce in hexademical format \n"
        "   -k hex value    --key hex value         Set key in hexademical format \n"
    );
}
//--------------------------------------------------------------------------------------
void freeArguments(char* input, char* output, char* nonce, char* key)
{
    if (input)
    {
        free(input);
    }
    if (output)
    {
        free(output);
    }
    if (nonce)
    {
        free(nonce);
    }
    if (key)
    {
        free(key);
    }
}
//--------------------------------------------------------------------------------------
bool getArguments(
    int argc, char** argv,
    bool& needDecrypt, char*& input, char*& output, char*& nonce, char*& key)
{
    bool algTypePresent = false;
    bool inputPresent = false;
    bool outputPresent = false;
    bool noncePresent = false;
    bool keyPresent = false;

    int next_option;
	if (argc > 1)
    {
       do
        {
            next_option = getopt_long(argc, argv, c_clSmallOptions, LongOptions, 0);
            switch (next_option)
            {
                case 'd':
                {
                    algTypePresent = true;
                    needDecrypt = true;
                    break;
                }
                case 'e':
                {
                    algTypePresent = true;
                    needDecrypt = false;
                    break;
                }
                case 'i':
                {
                    inputPresent = true;
                    input = (char*)malloc(strlen(optarg) + 1);
                    strcpy(input, optarg);
                    break;
                }
                case 'o':
                {
                    outputPresent = true;
                    output = (char*)malloc(strlen(optarg) + 1);
                    strcpy(output, optarg);
                    break;
                }
                case 'n':
                {
                    noncePresent = true;
                    nonce = (char*)malloc(strlen(optarg) + 1);
                    strcpy(nonce, optarg);
                    break;
                }
                case 'k':
                {
                    keyPresent = true;
                    key = (char*)malloc(strlen(optarg) + 1);
                    strcpy(key, optarg);
                    break;
                }
				case 'h':
				{
                    print_usage(stdout, argv[0]);
                    break;
				}
				default:
                {
                    //fprintf (stdout, "No valid command line argument. Use -h for help\n");
                    break;
                }
            }
        }
        while (next_option != -1);
    }

    // test

    algTypePresent = true;
    inputPresent = true;
    outputPresent = true;
    noncePresent = true;
    keyPresent = true;

    needDecrypt = true;

    input = (char*)malloc(100+1);
    strcpy(input, "/home/ik/svn/OMA-DMClient/build/posix_codeblocks/CryptoAPITest/bin/Debug/BSmessage_enc\0");

    output = (char*)malloc(100+1);
    strcpy(output, "/home/ik/svn/OMA-DMClient/build/posix_codeblocks/CryptoAPITest/bin/Debug/BSmessage_res\0");

    nonce = (char*)malloc(32+1);
    strcpy(nonce, "FF1122AABBEEEEDDEEDDEE\0");

    key = (char*)malloc(64+1);
    strcpy(key, "FF3344AABBEEEEDDEEDDEEAAEE1122DDFF3344AABBEEEEDDEEDDEEAAEE1122DD\0");

    // end test

    if ((!algTypePresent) || (!inputPresent) || (!outputPresent) || (!noncePresent) || (!keyPresent))
    {
        freeArguments(input, output, nonce, key);
        fprintf (stdout, "Not all mandatory arguments are set. Use -h for help\n");
        return false;
    }

    return true;
}
//--------------------------------------------------------------------------------------
bool decode(
    buffer_t& msg,
    unsigned char* nonce, size_t& nonce_size,
    unsigned char* key, size_t& key_size,
    buffer_t& decoded
    )
{
    NS_DM_Client::ICrypto* crypto_if = NS_DM_Client::CreateCryptoImpl();
    bool res = false;
    if (crypto_if)
    {
        size_t outLen = 0;
        decoded.resize(32*1024);
        if (crypto_if->AES_CCM_Decrypt(
            const_cast<unsigned char*>(nonce), nonce_size,
            const_cast<unsigned char*>(key), key_size,
            (reinterpret_cast<unsigned char*>(&msg[0])), msg.size(),
            (unsigned char*)&decoded[0], outLen
        ))
        {
            decoded.resize(outLen);
            res = true;
        }
        crypto_if->Release();
    }
    return res;
}
//--------------------------------------------------------------------------------------
bool encode(
    buffer_t& msg,
    unsigned char* nonce, size_t& nonce_size,
    unsigned char* key, size_t& key_size,
    buffer_t& decoded
)
{
    NS_DM_Client::ICrypto* crypto_if = NS_DM_Client::CreateCryptoImpl();
    bool res = false;
    if (crypto_if)
    {
        size_t outLen = 0;
        decoded.resize(32*1024);
        if (crypto_if->AES_CCM_Encrypt(
            const_cast<unsigned char*>(nonce), nonce_size,
            const_cast<unsigned char*>(key), key_size,
            (reinterpret_cast<unsigned char*>(&msg[0])), msg.size(),
            (unsigned char*)&decoded[0], outLen
        ))
        {
            decoded.resize(outLen);
            res = true;
        }
        crypto_if->Release();
    }
    return res;
}

//--------------------------------------------------------------------------------------
void freeBinary(unsigned char* binary)
{
    if (binary)
    {
        free(binary);
    }
}

//--------------------------------------------------------------------------------------
bool convertSeparateDigitToBinary(char* hex, unsigned char& bin)
{
    // 1) convert most significant path
    for (size_t i = 0; i < hexs_cnt; ++i)
    {
        if (hexs[i] == hex[0])
        {
            bin = (i <= 15) ? (16 * i) : (16 * (i - 6));
            break;
        }
        if (i == (hexs_cnt - 1))
        {
            return false;
        }
    }
    // 2) convert less significant path
    for (size_t i = 0; i < hexs_cnt; ++i)
    {
        if (hexs[i] == hex[1])
        {
            bin += (i <= 15) ? i : (i - 6);
            break;
        }
        if (i == (hexs_cnt - 1))
        {
            return false;
        }
    }
    return true;

}
//--------------------------------------------------------------------------------------
bool convertToBinary(char* input, bool nonceConvertion, unsigned char*& output, size_t& outputLength)
{
    size_t input_len = strlen(input);
    if ((input_len % 2) != 0)
    {
        fprintf (stdout, "string representation of %s not valid hexademinal representation.\n", nonceConvertion ? "nonce" : "key");
        return false;
    }

    output = (unsigned char*)malloc((input_len / 2) + 1);
    outputLength = input_len / 2;

    char hexDigit[2 + 1];
    hexDigit[2] = '\0';
    unsigned char binDigit;
    for (size_t i = 0; i < input_len; i += 2)
    {
        strncpy(&hexDigit[0], &input[i], 2);
        if (!convertSeparateDigitToBinary(hexDigit, binDigit))
        {
            freeBinary(output);
            fprintf (stdout, "string representation of %s not valid hexademinal representation.\n", nonceConvertion ? "nonce" : "key");
            return false;
        }
        size_t ddd = i/2;
        output[ddd] = binDigit;
    }
    return true;
}
//--------------------------------------------------------------------------------------
bool readFileSize(const char* path, size_t& size)
{
    bool res = false;
    struct stat st;
    if ((res = (stat(path, &st) == 0)))
    {
        size = st.st_size;
    }
    else
    {
        fprintf (stdout, "Can't get size of file: %s.\n", path);
    }
    return res;
}
//--------------------------------------------------------------------------------------
bool readFromFile(const char* path, buffer_t& content)
{
    size_t size;
    if (readFileSize(path, size))
    {
        if (size > 0)
        {
            FILE* fin = fopen(path, "rb");
            if (fin)
            {
                content.resize(size);
                if (fread(&content[0], 1, size, fin) == size)
                {
                    fclose(fin);
                    return true;
                }
                else
                {
                    fprintf (stdout, "Can't read from file: %s.\n", path);
                    fclose(fin);
                    return false;
                }
            }
            else
            {
                fprintf (stdout, "Can't open file: %s.\n", path);
                return false;
            }
        }
        else
        {
            fprintf (stdout, "size of input file is null: %s.\n", path);
            return false;
        }
    }
    return false;
}
//--------------------------------------------------------------------------------------
bool writeToFile(const char* path, const buffer_t& content)
{
    size_t size = content.size();
    FILE* fin = fopen(path, "wb");
    if (fin)
    {
        if (fwrite(&content[0], 1, size, fin) == size)
        {
            fclose(fin);
            return true;
        }
        else
        {
            fprintf (stdout, "Can't write to file: %s.\n", path);
            fclose(fin);
            return false;
        }
    }
    else
    {
        fprintf (stdout, "Can't open file: %s.\n", path);
        return false;
    }
    return false;
}
//--------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
    bool needDecrypt;
    char* input;
    char* output;
    char* nonce;
    char* key;

    if (getArguments(argc, argv, needDecrypt, input, output, nonce, key))
    {
        unsigned char* bin_nonce;
        size_t bin_nonce_size;
        unsigned char* bin_key;
        size_t bin_key_size;
        if (convertToBinary(nonce, true, bin_nonce, bin_nonce_size) && convertToBinary(key, false, bin_key, bin_key_size))
        {
            buffer_t input_content;
            if (readFromFile(input, input_content))
            {
                buffer_t res;
                if (needDecrypt)
                {
                    if (decode(input_content, bin_nonce, bin_nonce_size, bin_key, bin_key_size, res))
                    {
                        if (!writeToFile(output, res))
                        {
                            fprintf (stdout, "Can't write to file: %s.\n", output);
                        }
                    }
                    else
                    {
                        fprintf (stdout, "Decryption failed.\n");
                    }
                }
                else
                {
                    if (encode(input_content, bin_nonce, bin_nonce_size, bin_key, bin_key_size, res))
                    {
                        if (!writeToFile(output, res))
                        {
                            fprintf (stdout, "Can't write to file: %s.\n", output);
                        }
                    }
                    else
                    {
                        fprintf (stdout, "Encryption failed.\n");
                    }
                }
            }
        }
        freeArguments(input, output, nonce, key);
    }

    return 0;
}
