/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2020  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef USE_WINAPI
#include <windows.h>
#else
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#endif

#include <stoolkit/SIO.h>
#include <stoolkit/SEncoder.h>
#include <stoolkit/sedy/SSedy.h>

static SSedyFunctions functions;
static SString location;
static void* dynHandle = 0;

#if SEDY_BUILT_IN
#include <addon/yudit_sedy/yudit_sedy.h>
#else
#include <stoolkit/sedy/yudit_sedy.h>
static void fixFileName (SString& str)
{
#ifdef USE_WINAPI
  str.replaceAll ("/", "\\");
  if (str.size() > 0 && str[0] == '\\') str.remove (0);
#endif
}
#endif

// Yudit uses external library because of export restrictions.
#define SSEDY_CYPHER "TDES-168-bit"

SString
SSedy::getLocation () 
{
#if SEDY_BUILT_IN
    return SString ("built-in");
#else
    if (dynHandle) {
        if (functions.sedy_create 
            && functions.sedy_destroy 
            && functions.sedy_encrypt 
            && functions.sedy_decrypt 
            && functions.sedy_get_result 
            && functions.sedy_get_result_size 
            && functions.sedy_get_cypher) {

            return SString(location);
        }
    }
    return SString("");
#endif
}

SString
SSedy::loadLibrary (const SStringVector& path) 
{
#if SEDY_BUILT_IN
    dynHandle = 0;
    functions.sedy_create = (*sedy_create);
    functions.sedy_destroy = (*sedy_destroy);
    functions.sedy_encrypt = (*sedy_encrypt);
    functions.sedy_decrypt = (*sedy_decrypt);
    functions.sedy_get_result = (*sedy_get_result);
    functions.sedy_get_result_size  = (*sedy_get_result_size);
    functions.sedy_get_cypher = (*sedy_get_cypher); 
    return SString ("built-in");
#else
    if (dynHandle) {
        if (functions.sedy_create 
            && functions.sedy_destroy 
            && functions.sedy_encrypt 
            && functions.sedy_decrypt 
            && functions.sedy_get_result 
            && functions.sedy_get_result_size 
            && functions.sedy_get_cypher) {

            return SString(location);
        }
        return SString("");
    }
    SString libFile;
#ifdef USE_WINAPI
    libFile = "libsedy.dll";
#else
#ifdef __APPLE__
    libFile = "libsedy.dylib";
#else
    libFile = "libsedy.so";
#endif
#endif
    SString ret = libFile;
    SFile flib (libFile, path);
    if (flib.size() > 0)
    {
      libFile = flib.getName();
      fixFileName (libFile);
    }
    location = libFile; 
    libFile.append ((char)0);

#ifdef USE_WINAPI
    SEncoder u8("utf-8");
    SEncoder u16("utf-16-le");
    SString res = u16.encode (u8.decode (libFile));
    WCHAR* filenameW = (WCHAR *) res.array();
    dynHandle = LoadLibraryW (filenameW);
    if (!dynHandle)
    {
      dynHandle = LoadLibraryA (libFile.array());
      if (!dynHandle) return SString("");
    }
#else
    dynHandle = dlopen (libFile.array(), RTLD_LAZY);
    if (!dynHandle)
    {
      return SString("");
    }
#endif

#ifdef USE_WINAPI
    functions.sedy_create = (void* (*)(char*, char*)) GetProcAddress((HMODULE)dynHandle, "sedy_create");
    functions.sedy_destroy = (void (*)(void*)) GetProcAddress((HMODULE)dynHandle, "sedy_destroy");
    functions.sedy_encrypt = (int (*)(void*, char*, unsigned long)) GetProcAddress((HMODULE)dynHandle, "sedy_encrypt");
    functions.sedy_decrypt = (int (*)(void*, char*, unsigned long)) GetProcAddress((HMODULE)dynHandle, "sedy_decrypt");
    functions.sedy_get_result = (char* (*)(void*)) GetProcAddress((HMODULE)dynHandle, "sedy_get_result");
    functions.sedy_get_result_size = (unsigned long (*)(void*)) GetProcAddress((HMODULE)dynHandle, "sedy_get_result_size");
    functions.sedy_get_cypher = (char* (*)(void*)) GetProcAddress((HMODULE)dynHandle, "sedy_get_cypher");
#else
    functions.sedy_create = (void* (*)(char*, char*)) dlsym(dynHandle, "sedy_create");
    functions.sedy_destroy = (void (*)(void*)) dlsym(dynHandle, "sedy_destroy");
    functions.sedy_encrypt = (int (*)(void*, char*, unsigned long)) dlsym(dynHandle, "sedy_encrypt");
    functions.sedy_decrypt = (int (*)(void*, char*, unsigned long)) dlsym(dynHandle, "sedy_decrypt");
    functions.sedy_get_result = (char* (*)(void*)) dlsym(dynHandle, "sedy_get_result");
    functions.sedy_get_result_size = (unsigned long (*)(void*)) dlsym(dynHandle, "sedy_get_result_size");
    functions.sedy_get_cypher = (char* (*)(void*)) dlsym(dynHandle, "sedy_get_cypher");
#endif
    if (functions.sedy_create 
        && functions.sedy_destroy 
        && functions.sedy_encrypt 
        && functions.sedy_decrypt 
        && functions.sedy_get_result 
        && functions.sedy_get_result_size 
        && functions.sedy_get_cypher) 
    {
        return (SString(location));
    }
    if (!functions.sedy_create) {
        fprintf (stderr, "sedy_create missing\n");
    }
    if (!functions.sedy_destroy) {
        fprintf (stderr, "sedy_destroy missing\n");
    }
    if (!functions.sedy_encrypt) {
        fprintf (stderr, "sedy_encrypt missing\n");
    }
    if (!functions.sedy_decrypt) {
        fprintf (stderr, "sedy_decrypt missing\n");
    }
    if (!functions.sedy_get_result) {
        fprintf (stderr, "sedy_get_result missing\n");
    }
    if (!functions.sedy_get_result_size) {
        fprintf (stderr, "sedy_get_result_size missing\n");
    }
    if (!functions.sedy_get_cypher) {
        fprintf (stderr, "sedy_get_cypher missing\n");
    }
    return (SString(""));
#endif
}

SSedy::SSedy () 
{
    prepareLastError();
}


SSedy::~SSedy () 
{
}

void
SSedy::prepareLastError () 
{
    lastError = SString("");
    if (functions.sedy_create==0) 
    {
        lastError = "No handle for sedy_create";
    }
    if (functions.sedy_destroy==0) 
    {
        lastError = "No handle for sedy_destroy";
    }
    if (functions.sedy_encrypt==0) 
    {
        lastError = "No handle for sedy_encrypt";
    }
    if (functions.sedy_encrypt==0) 
    {
        lastError = "No handle for sedy_decrypt";
    }
    if (functions.sedy_get_result==0) 
    {
        lastError = "No handle for sedy_get_result";
    }
    if (functions.sedy_get_result_size==0) 
    {
        lastError = "No handle for sedy_get_result_size";
    }
    if (functions.sedy_get_cypher==0) 
    {
        lastError = "No handle for sedy_get_cypher";
    }
#if SEDY_BUILT_IN
    lastError = SString("");
#else
    if (!dynHandle) 
    {
        lastError = "Could not load sedy dynamic library";
    }
#endif
}

SString
SSedy::decrypt (const SString& text, const SString& passWord) 
{
    prepareLastError();
    if (lastError.size()) 
    {
        return SString("");
    }
    SString arg = text;
    SString pw = passWord;
    pw.append ((char)0);

    void* sedy = functions.sedy_create((char*)pw.array(), (char*)SSEDY_CYPHER);
    int ret = (*functions.sedy_decrypt)(sedy, (char*)arg.array(), arg.size());
    switch (ret) {
    case SEDY_NO_ERROR:
        break; // NO_ERROR
    case SEDY_UNSOPPORTED_CYPHER:
        lastError = SString("UNSOPPORTED_CYPHER");
        functions.sedy_destroy(sedy);
        return SString();
    case SEDY_BAD_PASSWORD:
        lastError = SString("BAD_PASSWORD");
        functions.sedy_destroy(sedy);
        return SString();
    case SEDY_OUT_OF_MEMORY:
        lastError = SString("OUT_OF_MEMORY");
        functions.sedy_destroy(sedy);
        return SString();
    default:
        lastError = SString("UNKOWN_ERROR");
        functions.sedy_destroy(sedy);
        return SString();
    }
    lastError = SString("");
    SString cypher = SString (functions.sedy_get_cypher(sedy)); 
    if (cypher != SString((char*)SSEDY_CYPHER)) {
        lastError = SString("Cypher Changed to: ");
        lastError.append (cypher);
        functions.sedy_destroy(sedy);
        return SString();
    }
    SString nonfree = SString(functions.sedy_get_result(sedy), functions.sedy_get_result_size(sedy));
    functions.sedy_destroy (sedy);
    return SString(nonfree);
}

SString
SSedy::encrypt (const SString& text, const SString& passWord) 
{
    prepareLastError();
    if (lastError.size()) 
    {
        return SString("");
    }
    SString arg = text;
    SString pw = passWord;
    pw.append ((char)0);

    void* sedy = functions.sedy_create((char*)pw.array(), (char*)SSEDY_CYPHER);
    int ret = (*functions.sedy_encrypt)(sedy, (char*)arg.array(), arg.size());
    switch (ret) {
    case SEDY_NO_ERROR:
        break; // NO_ERROR
    case SEDY_UNSOPPORTED_CYPHER:
        lastError = SString("UNSOPPORTED_CYPHER");
        functions.sedy_destroy(sedy);
        return SString();
    case SEDY_BAD_PASSWORD:
        lastError = SString("BAD_PASSWORD");
        functions.sedy_destroy(sedy);
        return SString();
    case SEDY_OUT_OF_MEMORY:
        lastError = SString("OUT_OF_MEMORY");
        functions.sedy_destroy(sedy);
        return SString();
    default:
        lastError = SString("UNKOWN_ERROR");
        functions.sedy_destroy(sedy);
        return SString();
    }
    lastError = SString("");
    SString cypher = SString (functions.sedy_get_cypher(sedy)); 
    if (cypher != SString(SSEDY_CYPHER)) {
        lastError = SString("Cypher Changed to: ");
        lastError.append (cypher);
        functions.sedy_destroy(sedy);
        return SString();
    }
    SString nonfree = SString(functions.sedy_get_result(sedy), functions.sedy_get_result_size(sedy));
    functions.sedy_destroy (sedy);
    return SString(nonfree);
}

SString
SSedy::getLastError () 
{
    return SString(lastError);
}
