/*
+-------------------------------- DISCLAIMER ---------------------------------+
|                                                                             |
| This application program is provided to you free of charge as an example.   |
| Despite the considerable efforts of Euresys personnel to create a usable    |
| example, you should not assume that this program is error-free or suitable  |
| for any purpose whatsoever.                                                 |
|                                                                             |
| EURESYS does not give any representation, warranty or undertaking that this |
| program is free of any defect or error or suitable for any purpose. EURESYS |
| shall not be liable, in contract, in torts or otherwise, for any damages,   |
| loss, costs, expenses or other claims for compensation, including those     |
| asserted by third parties, arising out of or in connection with the use of  |
| this program.                                                               |
|                                                                             |
+-----------------------------------------------------------------------------+
*/

// clserialLibrary.h : header file
//

#pragma once

#include <stdexcept>
#include <string>
#include <sstream>
#include <utility>

#include <Windows.h>

namespace Euresys {
namespace clseregl {
namespace Internal {

class dynlib_loading_error: public std::runtime_error {
public:
    explicit dynlib_loading_error(const std::string &path)
        : std::runtime_error(std::string("Cannot load the following dynamic library:\n") + path)
    {}
};

class missing_dynlib_symbol: public std::runtime_error {
public:
    explicit missing_dynlib_symbol(const std::string &path, const std::string &symbol)
        : std::runtime_error(std::string("Missing ") + symbol + " symbol in the following dynamic library:\n" + path)
    {}
};

class DynamicLibrary {
public:
    DynamicLibrary(const std::string &path)
        : path(path)
    {
        dynlib = LoadLibraryA(path.c_str());
        if (!dynlib) {
            throw dynlib_loading_error(path);
        }
    }
    ~DynamicLibrary() {
        try {
            FreeLibrary(dynlib);
        } catch (...) {
        }
    }
    void *getSymbol(const std::string &symbol) {
        void *s = reinterpret_cast<void *>(GetProcAddress(dynlib, symbol.c_str()));
        if (!s) {
            throw missing_dynlib_symbol(path, symbol);
        }
        return s;
    }
    const std::string &getPath() const {
        return path;
    }
private:
    std::string path;
    HMODULE dynlib;
};

}

typedef PVOID hSerRef;

#define CL_DLL_VERSION_NO_VERSION               1   // Not a CL dll
#define CL_DLL_VERSION_1_0                      2   // Oct 2000 compliant
#define CL_DLL_VERSION_1_1                      3   // Oct 2001 compliant
#define CL_DLL_VERSION_2_0                      4   // Feb 2012 compliant
#define CL_DLL_VERSION_2_1                      5   // Apr 2018 compliant

#define CL_BAUDRATE_9600                        1
#define CL_BAUDRATE_19200                       2
#define CL_BAUDRATE_38400                       4
#define CL_BAUDRATE_57600                       8
#define CL_BAUDRATE_115200                      16
#define CL_BAUDRATE_230400                      32
#define CL_BAUDRATE_460800                      64
#define CL_BAUDRATE_921600                      128

const std::pair<UINT32, CString> CL_BAUDRATES[] = {
    std::make_pair(CL_BAUDRATE_9600, L"9600"),
    std::make_pair(CL_BAUDRATE_19200, L"19200"),
    std::make_pair(CL_BAUDRATE_38400, L"38400"),
    std::make_pair(CL_BAUDRATE_57600, L"57600"),
    std::make_pair(CL_BAUDRATE_115200, L"115200"),
    std::make_pair(CL_BAUDRATE_230400, L"230400"),
    std::make_pair(CL_BAUDRATE_460800, L"460800"),
    std::make_pair(CL_BAUDRATE_921600, L"921600"),
};

const UINT32 CL_BAUDRATES_SIZE = sizeof(CL_BAUDRATES) / sizeof(CL_BAUDRATES[0]);

//------------------------------------------------------------------------------
//  Error Codes
//------------------------------------------------------------------------------
#define CL_ERR_NO_ERR                               0
#define CL_ERR_BUFFER_TOO_SMALL                     -10001
#define CL_ERR_MANU_DOES_NOT_EXIST                  -10002
#define CL_ERR_PORT_IN_USE                          -10003
#define CL_ERR_TIMEOUT                              -10004
#define CL_ERR_INVALID_INDEX                        -10005
#define CL_ERR_INVALID_REFERENCE                    -10006
#define CL_ERR_ERROR_NOT_FOUND                      -10007
#define CL_ERR_BAUD_RATE_NOT_SUPPORTED              -10008
#define CL_ERR_OUT_OF_MEMORY                        -10009
#define CL_ERR_REGISTRY_KEY_NOT_FOUND               -10010
#define CL_ERR_INVALID_PTR                          -10011
#define CL_ERR_UNABLE_TO_LOAD_DLL                   -10098
#define CL_ERR_FUNCTION_NOT_FOUND                   -10099

class ClSerialError : public std::exception {
public:
    ClSerialError(INT32 status, const std::string &description) 
        : status(status)
        , description(description) {}

    const char *what() {
        return description.c_str();
    }

    INT32 getStatus() {
        return status;
    }

private:
    INT32 status;
    std::string description;
};

class ClSerialLibrary {
public:
    ClSerialLibrary(const std::string &path = getDefaultPath())
        : dynlib(path)
    {
        clSerialClose = reinterpret_cast<INT32(__cdecl *)(void *)>(dynlib.getSymbol("clSerialClose"));
        clSerialInit = reinterpret_cast<INT32(__cdecl *)(UINT32, void **)>(dynlib.getSymbol("clSerialInit"));
        clSerialRead = reinterpret_cast<INT32(__cdecl *)(void *, char *, UINT32 *, UINT32)>(dynlib.getSymbol("clSerialRead"));
        clSerialWrite = reinterpret_cast<INT32(__cdecl *)(void *, char *, UINT32 *, UINT32)>(dynlib.getSymbol("clSerialWrite"));
        clFlushPort = reinterpret_cast<INT32(__cdecl *)(void *)>(dynlib.getSymbol("clFlushPort"));
        clGetSerialPortIdentifier = reinterpret_cast<INT32(__cdecl *)(UINT32, char *, UINT32 *)>(dynlib.getSymbol("clGetSerialPortIdentifier"));
        clGetSupportedBaudRates = reinterpret_cast<INT32(__cdecl *)(void *, UINT32 *)>(dynlib.getSymbol("clGetSupportedBaudRates"));
        clSetBaudRate = reinterpret_cast<INT32(__cdecl *)(void *, UINT32)>(dynlib.getSymbol("clSetBaudRate"));
        clGetNumBytesAvail = reinterpret_cast<INT32(__cdecl *)(void *, UINT32 *)>(dynlib.getSymbol("clGetNumBytesAvail"));
        clGetErrorText = reinterpret_cast<INT32(__cdecl *)(INT32, char *, UINT32 *)>(dynlib.getSymbol("clGetErrorText"));
        clGetManufacturerInfo = reinterpret_cast<INT32(__cdecl *)(char *, UINT32 *, INT32 *)>(dynlib.getSymbol("clGetManufacturerInfo"));
        clGetNumSerialPorts = reinterpret_cast<INT32(__cdecl *)(UINT32 *)>(dynlib.getSymbol("clGetNumSerialPorts"));
    }

    static std::string getDefaultPath() {
        std::string path;
        DWORD pathSize = 0;
        LONG retCode = ::RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\cameralink",
            "CLSERIALPATH", RRF_RT_REG_SZ, nullptr, nullptr, &pathSize);
        if (retCode != 0) {
            throw std::runtime_error("Cannot retrieve CLSERIALPATH registry value");
        }
        path.resize(pathSize);
        retCode = RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\cameralink",
            "CLSERIALPATH", RRF_RT_REG_SZ, nullptr, &path[0], &pathSize);
        if (retCode != 0) {
            throw std::runtime_error("Cannot retrieve CLSERIALPATH registry value");
        }
        path.erase(std::find(path.begin(), path.end(), '\0'), path.end());
        if (path.empty()) {
            throw std::runtime_error("No default clseregl library path");
        }
        path.append("clseregl.dll");
        return path;
    }

    void SerialClose() {
        std::ostringstream ss;
        ss << "Cannot close port (serialRef = 0x" << std::hex << serialRef << ")";
        ThrowOnSerialError(clSerialClose(serialRef), ss.str());
    }

    void SerialInit(UINT32 serialIndex) {
        std::ostringstream ss;
        ss << "Cannot initialize port (index = " << serialIndex << ")";
        ThrowOnSerialError(clSerialInit(serialIndex, &serialRef), ss.str());
    }

    void SerialRead(char *buffer, UINT32 *numBytes, UINT32 serialTimeout) {
        ThrowOnSerialError(clSerialRead(serialRef, buffer, numBytes, serialTimeout), "Cannot read port");
    }

    void SerialWrite(char *buffer, UINT32* bufferSize, UINT32 serialTimeout) {
        ThrowOnSerialError(clSerialWrite(serialRef, buffer, bufferSize, serialTimeout), "Cannot write port");
    }

    void FlushPort() {
        ThrowOnSerialError(clFlushPort(serialRef), "Cannot flush port");
    }

    void GetSerialPortIdentifier(UINT32 serialIndex, char *portID, UINT32 *bufferSize) {
        ThrowOnSerialError(clGetSerialPortIdentifier(serialIndex, portID, bufferSize), "Cannot retrieve port identifier");
    }

    void GetSupportedBaudRates(UINT32 *baudRates) {
        ThrowOnSerialError(clGetSupportedBaudRates(serialRef, baudRates), "Cannot retrieve supported baud rates");
    }

    void SetBaudRate(UINT32 baudRate) {
        ThrowOnSerialError(clSetBaudRate(serialRef, baudRate), "Cannot set baud rate");
    }

    void GetNumBytesAvail(UINT32 *numBytes) {
        ThrowOnSerialError(clGetNumBytesAvail(serialRef, numBytes), "Cannot retrieve number of bytes available");
    }

    void GetErrorText(INT32 errorCode, char *errorText, UINT32 *errorTextSize) {
        ThrowOnSerialError(clGetErrorText(errorCode, errorText, errorTextSize), "clGetErrorText error");
    }

    void GetManufacturerInfo(char *ManufacturerName, UINT32 *bufferSize, INT32 *version) {
        ThrowOnSerialError(clGetManufacturerInfo(ManufacturerName, bufferSize, version), "Cannot retrieve manufacturer info");
    }

    void GetNumSerialPorts(UINT32 *numSerialPorts) {
        ThrowOnSerialError(clGetNumSerialPorts(numSerialPorts), "Cannot retrieve number of ports");
    }

protected:
    INT32(__cdecl *clSerialClose)(hSerRef serialRef);
    INT32(__cdecl *clSerialInit)(UINT32 serialIndex, hSerRef *serialRefPtr);
    INT32(__cdecl *clSerialRead)(hSerRef serialRef, char *buffer, UINT32 *numBytes, UINT32 serialTimeout);
    INT32(__cdecl *clSerialWrite)(hSerRef serialRef, char *buffer, UINT32* bufferSize, UINT32 serialTimeout);
    INT32(__cdecl *clFlushPort)(hSerRef serialRef);
    INT32(__cdecl *clGetSerialPortIdentifier)(UINT32 serialIndex, char *portID, UINT32 *bufferSize);
    INT32(__cdecl *clGetSupportedBaudRates)(hSerRef serialRef, UINT32 *baudRates);
    INT32(__cdecl *clSetBaudRate)(hSerRef serialRef, UINT32 baudRate);
    INT32(__cdecl *clGetNumBytesAvail)(hSerRef serialRef, UINT32 *numBytes);
    INT32(__cdecl *clGetErrorText)(INT32 errorCode, char *errorText, UINT32 *errorTextSize);
    INT32(__cdecl *clGetManufacturerInfo)(char *ManufacturerName, UINT32 *bufferSize, INT32 *version);
    INT32(__cdecl *clGetNumSerialPorts)(UINT32 *numSerialPorts);

private:
    void ThrowOnSerialError(INT32 status, const std::string &action)
    {
        if (status != CL_ERR_NO_ERR)
        {
            throw ClSerialError(status, action);
        }
    }

    Internal::DynamicLibrary dynlib;
    hSerRef serialRef;
};

}
}
