#include "excel_addin_utils.h"
#include <sstream>

//////////////////////////////////////////////////////////////////////////////
//
// excel_string_to_utf8
//
//    Converts an Excel string, which is a UTF16-encoded string with
//    the string length in the first character, to a UT8-encoded
//    NULL-terminated string stored in a std::string
//
std::string excel_string_to_utf8(XCHAR const* excel_string)
{
    // Return empty string if the input pointer is NULL.
    if (excel_string == 0)
    {
        return std::string();
    }

    // The first wide character of an Excel string contains
    // the length of the string.
    uint16_t num_wide_chars = static_cast<uint16_t>(excel_string[0]);
    if (num_wide_chars == 0)
    {
        return std::string();
    }

    // The actual string data starts at the second character.
    LPCWSTR start_of_utf16_str = &excel_string[1];

    // Call WideCharToMultiByte with no output buffer to get
    // the required size of buffer.
    int num_utf8_chars = WideCharToMultiByte(
        CP_UTF8, 0,
        start_of_utf16_str, num_wide_chars,
        NULL, 0, NULL, NULL);

    if (num_utf8_chars == 0)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: excel_string_to_utf8 first call to WideCharToMultiByte failed with error: " << GetLastError();
        throw std::runtime_error(err_msg.str());
    }

    // Create a string with enough space.
    std::string utf8_string(static_cast<std::string::size_type>(num_utf8_chars), '\0');

    // Now convert the string from UTF16 to UTF8
    int utf8_chars_written = WideCharToMultiByte(
        CP_UTF8, 0,
        start_of_utf16_str, num_wide_chars,
        &utf8_string[0], num_utf8_chars, NULL, NULL);

    if (utf8_chars_written != num_utf8_chars)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: excel_string_to_utf8 second call to WideCharToMultiByte failed with error: " << GetLastError();
        throw std::runtime_error(err_msg.str());
    }

    return utf8_string;
}

//////////////////////////////////////////////////////////////////////////////
//
// utf8_string_to_excel
//
//    Converts  a UT8-encoded NULL-terminated string stored in a
//    std::string to a UTF16-encoded string with length stored
//    in the first character.
//
//    NOTE: the caller is responsible for calling delete [] ptr on
//          the pointer returned by the function when it is no
//          longer required.
//
XCHAR* utf8_string_to_excel(char const* utf8_string_ptr)
{
    // Return empty string if passed a NULL pointer.
    if (utf8_string_ptr == 0)
    {
        XCHAR* excel_string = new XCHAR[2];
        excel_string[0] = 0;
        excel_string[1] = L'\0';
        return excel_string;
    }

    // MultiByteToWideChar takes the string size as an int. If the
    // number of characters in the string is bigger than can be
    // fitted in an int then throw an exception.
    std::string utf8_string(utf8_string_ptr);
    if (utf8_string.size() > INT32_MAX)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: utf8_string_to_excel string too big to be converted by MultiByteToWideChar. Size = " << utf8_string.size();
        throw std::runtime_error(err_msg.str());
    }

    // Cast size to the type that MultiByteToWideChar wants.
    int utf8_string_size = static_cast<int>(utf8_string.size());

    if (utf8_string_size == 0)
    {
        // Return an empty string.
        XCHAR* excel_string = new XCHAR[2];
        excel_string[0] = 0;
        excel_string[1] = L'\0';
        return excel_string;
    }

    int num_wide_chars = MultiByteToWideChar(
        CP_UTF8, 0,
        utf8_string.c_str(), utf8_string_size,
        NULL, 0);

    if (num_wide_chars == 0)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: utf8_string_to_excel first call to MultiByteToWideChar failed with error: " << GetLastError();
        throw std::runtime_error(err_msg.str());
    }

    // Excel strings can only contain 32K characters.
    if (num_wide_chars > MAX_XL12_STR_LEN)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: utf8_string_to_excel string too big to be returned to Excel. Number of characters, " << num_wide_chars << ", more than maximum allowed: " << MAX_XL12_STR_LEN;
        throw std::runtime_error(err_msg.str());
    }

    // Allocate enough memory for length at the beginning and
    // NULL terminator.
    XCHAR* excel_string = new XCHAR[num_wide_chars + 2];

    if (excel_string == 0)
    {
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: utf8_string_to_excel failed to allocate memory for Excel string.";
        throw std::runtime_error(err_msg.str());
    }

    int wide_chars_written = MultiByteToWideChar(
        CP_UTF8, 0,
        utf8_string.c_str(), utf8_string_size,
        &excel_string[1], num_wide_chars);

    if (wide_chars_written != num_wide_chars)
    {
        delete[] excel_string;
        std::ostringstream err_msg;
        err_msg << "Excel addin internal error: utf8_string_to_excel second call to MultiByteToWideChar failed with error: " << GetLastError();
        throw std::runtime_error(err_msg.str());
    }

    // Set first character to the string length.
    excel_string[0] = static_cast<XCHAR>(wide_chars_written);
    excel_string[wide_chars_written + 1] = L'\0';

    return excel_string;
}

//////////////////////////////////////////////////////////////////////////////
//
// caller_location
//
//    Returns a string created from the book name, sheet name, row and
//    column of the cell from which the current Excel function is being
///   being called.
//
std::string caller_location()
{
    static int g_unique_number = 0;

    std::ostringstream caller_loc;

    XLOPER12 xl_caller;
    xloper_scope_guard xl_caller_scope_guard(&xl_caller);
    Excel12(xlfCaller, &xl_caller, 0);

    if (xl_caller.xltype == xltypeSRef)
    {
        int caller_row = xl_caller.val.sref.ref.rwFirst + 1;
        int caller_col = xl_caller.val.sref.ref.colFirst + 1;

        XLOPER12 xl_sheet_name;
        xloper_scope_guard xl_sheet_name_scope_guard(&xl_sheet_name);
        Excel12(xlSheetNm, &xl_sheet_name, 1, &xl_caller);

        std::string sheet_name = excel_string_to_utf8(xl_sheet_name.val.str);
        caller_loc << sheet_name << "!R" << caller_row << "C" << caller_col;
    }
    else
    {
        // Failed to retrieve call location information for some reason.
        //
        // We append a unique number in order to disambiguate calls from
        // unknown locations.
        caller_loc << "UnknownExcelLocation_" <<g_unique_number++;
    }

    return caller_loc.str();
}


//////////////////////////////////////////////////////////////////////////
//
// free_xloper_memory
//
//    Frees allocated strings that were returned to Excel from
//    calls.
//
void free_xloper_memory(XLOPER12* xl_oper)
{
    if (xl_oper->xltype & xltypeMulti)
    {
        // This is a multi. Loop over the elements looking for strings to free.
        int size = xl_oper->val.array.rows * xl_oper->val.array.columns;
        XLOPER12* p = xl_oper->val.array.lparray;

        // Loop over array deleting any string elements.
        while (size > 0)
        {
            if (p->xltype == (xltypeStr | xlbitDLLFree))
            {
                // We allocated this string so we delete it.
                delete[] p->val.str;
            }
            else if (p->xltype == (xltypeStr | xlbitXLFree))
            {
                // Excel allocated this string so let Excel delete it.
                Excel12(xlFree, 0, 1, p);
            }
            --size;
            ++p;
        }

        // Now delete the array itself.
        delete[] xl_oper->val.array.lparray;
    }
    else if (xl_oper->xltype == (xltypeStr | xlbitDLLFree))
    {
        // This is a string we allocated so we delete it.
        delete[] xl_oper->val.str;
    }
    else if (xl_oper->xltype == (xltypeStr | xlbitXLFree))
    {
        // This is a string Excel allocated so get Excel to delete it.
        Excel12(xlFree, 0, 1, xl_oper);
    }

    // Now delete the XLOPER itself
    delete xl_oper;
}

//////////////////////////////////////////////////////////////////////////////
//
// create_error_message
//
//
void create_error_message(XLOPER12* xl_result, char const* error_message)
{
    std::string message;
    std::string const hash_err("#Err: ");
    std::string error_message_str(error_message);

    // Prefix the message with #Err
    message += hash_err;

    message += error_message_str;

    // utf8_string_to_excel allocates memory. We set xlbitDLLFree
    // to tell Excel to call xlAutoFree12 to free this XLOPER when
    // it is done with it.
    xl_result->xltype = xltypeStr | xlbitDLLFree;
    xl_result->val.str = utf8_string_to_excel(message.c_str());
}

DWORD xloper_type(XLOPER12* xl_oper)
{
    return (xl_oper->xltype & ~(xlbitDLLFree | xlbitXLFree));
}

//////////////////////////////////////////////////////////////////////////////
//
// register_functions
//
//    This function iterates over a registration_info array and registers
//    the function described with Excel.
//
unsigned int register_functions(
    unsigned int num_functions_to_register,
    registration_info reg_info[]
)
{
    // Get the full path to the xll Excel is currently loading,
    // wrapping the XLOPER12 in a scope guard so it gets freed
    // whatever happens.
    XLOPER12 xl_xll_path;
    xloper_scope_guard xl_xll_path_guard(&xl_xll_path);

    int ret = Excel12(xlGetName, &xl_xll_path, 0);

    if (ret)
    {
        std::ostringstream err_msg;
        err_msg << "xlGetName call failed with error: " << ret;
        xloper_string error_message(err_msg.str());
        Excel12(xlcAlert, 0, 1, &error_message);
        return 0;
    }

    unsigned int num_functions_registered = 0;

    for (unsigned int i = 0; i < num_functions_to_register; ++i)
    {
        xloper_string xl_dll_export(reg_info[i].dll_export);
        xloper_string xl_type_string(reg_info[i].return_and_parameter_types);
        xloper_string xl_func_name(reg_info[i].function_name_in_excel);
        xloper_string xl_param_names(reg_info[i].parameter_names);
        xloper_string xl_category(reg_info[i].function_wizard_category);
        xloper_string xl_help_topic("");
        xloper_string xl_func_help(reg_info[i].function_description);

        // This is a user-defined function
        XLOPER12 xl_macro_type;
        xl_macro_type.xltype = xltypeNum;
        xl_macro_type.val.num = 1;

        // Short-cut key. Not used for functions.
        XLOPER12 xl_shortcut;
        xl_shortcut.xltype = xltypeStr;
        xl_shortcut.val.str = utf8_string_to_excel("\000");

        xloper_string xl_argument_help[MAX_XL_PARAMS];

        for (unsigned int p = 0; p < reg_info[i].num_parameters; ++p)
        {
            xl_argument_help[p].set_string(reg_info[i].parameter_description[p]);
        }

        XLOPER12 xl_registration_result;

        ret = Excel12(
            xlfRegister, &xl_registration_result,
            10 + reg_info[i].num_parameters,
            &xl_xll_path, &xl_dll_export,
            &xl_type_string, &xl_func_name, &xl_param_names,
            &xl_macro_type, &xl_category, &xl_shortcut, &xl_help_topic, &xl_func_help,
            &xl_argument_help[0], &xl_argument_help[1], &xl_argument_help[2],
            &xl_argument_help[3], &xl_argument_help[4], &xl_argument_help[5],
            &xl_argument_help[6], &xl_argument_help[7], &xl_argument_help[8],
            &xl_argument_help[9], &xl_argument_help[10], &xl_argument_help[11],
            &xl_argument_help[12], &xl_argument_help[13], &xl_argument_help[14],
            &xl_argument_help[15], &xl_argument_help[16], &xl_argument_help[17],
            &xl_argument_help[18], &xl_argument_help[19], &xl_argument_help[20],
            &xl_argument_help[21], &xl_argument_help[22], &xl_argument_help[23],
            &xl_argument_help[24], &xl_argument_help[25], &xl_argument_help[26],
            &xl_argument_help[27], &xl_argument_help[28], &xl_argument_help[29],
            &xl_argument_help[30], &xl_argument_help[31], &xl_argument_help[32],
            &xl_argument_help[33], &xl_argument_help[34], &xl_argument_help[35],
            &xl_argument_help[36], &xl_argument_help[37], &xl_argument_help[38],
            &xl_argument_help[39], &xl_argument_help[40], &xl_argument_help[41],
            &xl_argument_help[42], &xl_argument_help[43], &xl_argument_help[44],
            &xl_argument_help[45], &xl_argument_help[46], &xl_argument_help[47],
            &xl_argument_help[48], &xl_argument_help[49], &xl_argument_help[50],
            &xl_argument_help[51], &xl_argument_help[52], &xl_argument_help[53],
            &xl_argument_help[54], &xl_argument_help[55], &xl_argument_help[56],
            &xl_argument_help[57], &xl_argument_help[58], &xl_argument_help[59],
            &xl_argument_help[60], &xl_argument_help[61], &xl_argument_help[62],
            &xl_argument_help[63], &xl_argument_help[64], &xl_argument_help[65]
        );
        
        // The registration result should contain Excel's function id
        // for the function if the function was registered correctly.
        if (xl_registration_result.xltype == xltypeNum)
        {
            // Registration successful.
            num_functions_registered++;
        }
        else
        {
            std::ostringstream err_msg;
            err_msg << "Excel addin: Failed to register function: " << reg_info[i].function_name_in_excel << ". xlfRegister return value: " << ret << ", result type: " << xl_registration_result.xltype;
            xloper_string error_message(err_msg.str());
            Excel12(xlcAlert, 0, 1, &error_message);
            break;
        }
    }

    return num_functions_registered;
}
