/*
* Copyright (C) 2018 Global Graphics Software Ltd. All rights reserved.
*
* This example is provided on an "as is" basis and without
* warranty of any kind. Global Graphics Software Ltd. does not
* warrant or make any representations regarding the use or results
* of use of this example.
*/

#include <stdlib.h>
#include <tiffio.h>
#include <stdint.h>
#include <string>
#include <string.h>
#include <errno.h>
#include "ScreenPro.h"
#include "ScreenProDll.Example.h"

using namespace std;

void* loadSharedLibrary()
{
#ifdef _WIN32
    return static_cast<void*>(LoadLibraryA("ScreenPro.dll"));
#else
    void *f = dlopen("./libScreenPro.so", RTLD_NOW);
    if ( f == NULL )
        fprintf(stderr, "dlopen() failed: %s\n", dlerror());
    return f;
#include <string.h>
#include <wchar.h>
#include <dlfcn.h>
#endif
}

void* getFunction(void* lib, const char* functionName)
{
#ifdef _WIN32
    return static_cast<void*>(GetProcAddress(static_cast<HINSTANCE>(lib), functionName));
#else
    return dlsym(lib, functionName);
#endif
}

BOOL freeSharedLibrary(void* hDLL)
{
#ifdef _WIN32
    return FreeLibrary(static_cast<HINSTANCE>(hDLL));
#else
    return dlclose(hDLL);
#endif
}

#define MAX_PATH 260

ScreenProResult (*fnScreenProStartup)(const char *startupFilename, const ScreenProFileIO *fileIO, const ScreenProPermit *permit);
ScreenProResult (*fnScreenProScreen)(const char * screenFolder, ScreenProRaster *raster, ScreenProLayout *layout);
ScreenProResult (*fnScreenProQueryScreen)(const char * screenFolder, uint64_t channelNumber, ScreenProScreenInfo *info);
ScreenProResult (*fnScreenProQueryLicensing)(ScreenProLicenseInfo *info);
ScreenProResult (*fnScreenProCountDrops)(ScreenProRaster *raster, uint64_t *counts);
ScreenProResult (*fnScreenProShutdown)(void);
ScreenProResult (*fnScreenProGetPrintFlatSubscription)(PrintFlatSubscription *subscription); 

void* getFunctionsPointers()
{
    void* libHandle = loadSharedLibrary();

    if (libHandle == nullptr)
    {
        fprintf(stderr, "Failed to open shared obj\n");
        fflush(stderr);
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProStartup) = getFunction(libHandle, "ScreenProStartup");
    if (fnScreenProStartup == nullptr)
    {
        fprintf(stderr, "Failed to get spStartup function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProGetPrintFlatSubscription) = getFunction(libHandle, "ScreenProGetPrintFlatSubscription");
    if (fnScreenProGetPrintFlatSubscription == nullptr)
    {
        fprintf(stderr, "Failed to get ScreenProGetPrintFlatSubscription function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProScreen) = getFunction(libHandle, "ScreenProScreen");
    if (fnScreenProScreen == nullptr)
    {
        fprintf(stderr, "Failed to get spScreen function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProQueryScreen) = getFunction(libHandle, "ScreenProQueryScreen");
    if (fnScreenProQueryScreen == nullptr)
    {
        fprintf(stderr, "Failed to get spQueryScreen function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProQueryLicensing) = getFunction(libHandle, "ScreenProQueryLicensing");
    if (fnScreenProQueryLicensing == nullptr)
    {
        fprintf(stderr, "Failed to get spQueryLicensing function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProCountDrops) = getFunction(libHandle, "ScreenProCountDrops");
    if (fnScreenProCountDrops == nullptr)
    {
        fprintf(stderr, "Failed to get spCountDrops function ptr.\n");
        exit(1);
    }

    *reinterpret_cast<void**>(&fnScreenProShutdown) = getFunction(libHandle, "ScreenProShutdown");
    if (fnScreenProShutdown == nullptr)
    {
        fprintf(stderr, "Failed to get spShutdown function ptr.\n");
        exit(1);
    }
    
    return libHandle;
}

void addColormap(TIFF* tifOut, int dropCount)
{
    uint16_t red[65536];
    uint16_t green[65536];
    uint16_t blue[65536];

    // Initialize red, blue and green arrays to 65535
    memset(red, 0xff, sizeof(red));
    memset(green, 0xff, sizeof(green));
    memset(blue, 0xff, sizeof(blue));

    uint16_t colorCalc;

    if (dropCount > 0)
    {
        for (uint16_t i = 0; i <= dropCount; ++i)
        {
            colorCalc = 65535 * (dropCount - i) / dropCount;
            red[i] = colorCalc;
            green[i] = colorCalc;
            blue[i] = colorCalc;
        }
    }

    TIFFSetField(tifOut, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE);
    TIFFSetField(tifOut, TIFFTAG_INDEXED, 1);
    TIFFSetField(tifOut, TIFFTAG_COLORMAP, red, green, blue);
}

int write8bppRaster(TIFF* tifOut, ScreenProRaster* screenRaster, int dropCount)
{
    TIFFSetField(tifOut, TIFFTAG_BITSPERSAMPLE, 8);
    
    uint8_t* pDst = (uint8_t *)screenRaster->buffer;

    for (unsigned int y = 0; y < screenRaster->height; ++y)
    {
        TIFFWriteScanline(tifOut, pDst, y, 0);
        pDst += screenRaster->stride;
    }

    TIFFClose(tifOut);

    return 0;
}

int Write16bppOutput(TIFF* tifOut, ScreenProRaster* screenRaster, int dropCount)
{
    TIFFSetField(tifOut, TIFFTAG_BITSPERSAMPLE, 8);

    uint16_t* pDst = (uint16_t *)screenRaster->buffer;
    for (uint32_t row = 0; row < screenRaster->height; row++)
    {
        uint8_t* p8 = (uint8_t *)pDst;
        for (uint32_t i = 0; i < screenRaster->width; i++)
            p8[i] = (uint8)(pDst[i] & 0xff);

        if (TIFFWriteScanline(tifOut, pDst, row, 0) == -1)
        {
            printf("Error: TIFFWriteScanline() failed\n");
            return -43204;
        }
        pDst += screenRaster->stride / sizeof(uint16_t);
    }

    TIFFClose(tifOut);

    return 0;
}

// Need a different version of Write16bppOutput when number of Drops is High (greater than 8) 
// which is used with Passthrough
int Write16bppOutput_HighDropCount(TIFF* tifOut, ScreenProRaster* screenRaster, int dropCount)
{
    TIFFSetField(tifOut, TIFFTAG_BITSPERSAMPLE, screenRaster->bitsPerPixel);

    uint16_t* pDst = static_cast<uint16_t *>(screenRaster->buffer);
    for (uint32_t row = 0; row < screenRaster->height; row++)
    {
        if (TIFFWriteScanline(tifOut, pDst, row, 0) == -1)
        {
            printf("Error: TIFFWriteScanline() failed\n");
            return -43204;
        }
        pDst += screenRaster->stride / sizeof(uint16_t);
    }

    TIFFClose(tifOut);

    return 0;
}

int WriteOutput(char* pFilename, ScreenProRaster* screenRaster, int dropCount, TiffExtraOption * tiffExtraOption)
{
    TIFF* tifOut = TIFFOpen(pFilename, "wb");

    if (!tifOut)
    {
        printf("Failed to open \"%s\".\n", pFilename);
        return -43201;
    }

    // point to Raster data we get from Screener
    if (!screenRaster->buffer)
    {
        printf("Error - pointer to output raster data (from Screener) is NULL.\n");
        return -43203;
    }

    TIFFSetField(tifOut, TIFFTAG_IMAGELENGTH, screenRaster->height);
    TIFFSetField(tifOut, TIFFTAG_IMAGEWIDTH, screenRaster->width);

    TIFFSetField(tifOut, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField(tifOut, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);

    uint8_t bps = (uint8_t)screenRaster->bitsPerPixel;
    if (dropCount <= 16)
    {
        if (bps == 16)
            bps = 8;
        TIFFSetField(tifOut, TIFFTAG_BITSPERSAMPLE, bps);
        TIFFSetField(tifOut, TIFFTAG_SAMPLESPERPIXEL, 1);

        if (screenRaster->bitsPerPixel > 1)
        {
            addColormap(tifOut, dropCount);
        }
        else
        {
            TIFFSetField(tifOut, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
        }
    }
    else
    {
        // High number of Drops (normally with PassThrough)
        TIFFSetField(tifOut, TIFFTAG_BITSPERSAMPLE, bps);
        TIFFSetField(tifOut, TIFFTAG_SAMPLESPERPIXEL, 1);
        TIFFSetField(tifOut, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
    }

    // Resolutions Tags (only if we have values in Input Image)
    if (tiffExtraOption->resolutionX > 0.0)
    {
        TIFFSetField(tifOut, TIFFTAG_XRESOLUTION, tiffExtraOption->resolutionX);
    }
    if (tiffExtraOption->resolutionY > 0.0)
    {
        TIFFSetField(tifOut, TIFFTAG_YRESOLUTION, tiffExtraOption->resolutionY);
    }
    if (tiffExtraOption->resolutionUnit > 0)
    {
        TIFFSetField(tifOut, TIFFTAG_RESOLUTIONUNIT, tiffExtraOption->resolutionUnit);
    }

    TIFFSetField(tifOut, TIFFTAG_ROWSPERSTRIP, 1);
    TIFFSetField(tifOut, TIFFTAG_COMPRESSION, COMPRESSION_LZW);

    if (screenRaster->bitsPerPixel == 8)
    {
        return write8bppRaster(tifOut, screenRaster, dropCount);
    }
    if (screenRaster->bitsPerPixel == 16)
    {
        if (dropCount <= 16)
        {
            return Write16bppOutput(tifOut, screenRaster, dropCount);
        }
        return Write16bppOutput_HighDropCount(tifOut, screenRaster, dropCount);
    }

    TIFFClose(tifOut);
    return 0;
}

int ReadTifFile(char* psz, ScreenProRaster* screenRaster, TiffExtraOption * tiffExtraOption)
{
    TIFF * tif = TIFFOpen(psz, "r");
    if (!tif)
    {
        printf("Failed to open \"%s\".\n", psz);
        return -1;
    }

    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &screenRaster->height);
    printf("TIFFTAG_IMAGELENGTH %d\n", (int)screenRaster->height);

    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &screenRaster->width);
    printf("TIFFTAG_IMAGEWIDTH %d\n", (int)screenRaster->width);

    TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &screenRaster->bitsPerPixel);
    printf("TIFFTAG_BITSPERSAMPLE %d\n", (int)screenRaster->bitsPerPixel);

    uint16_t photometric;
    TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
    printf("TIFFTAG_PHOTOMETRIC %d\n", (int)photometric);
    
    TIFFGetField(tif, TIFFTAG_XRESOLUTION, &tiffExtraOption->resolutionX);
    TIFFGetField(tif, TIFFTAG_YRESOLUTION, &tiffExtraOption->resolutionY);
    TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &tiffExtraOption->resolutionUnit);

    screenRaster->stride = static_cast<uint32_t>(TIFFScanlineSize(tif));
    // Round up to the nearest byte
    screenRaster->stride = (screenRaster->stride + 7) & ~7;

    if (screenRaster->stride > static_cast<unsigned int>(-1))
    {
        printf("TIFF stride requires variable to 64 bit (%d bytes).\n",
                (int)screenRaster->stride);
        TIFFClose(tif);
        return -1;
    }

    size_t rasterSize = (size_t)(screenRaster->stride) * (size_t)(screenRaster->height + 1);

    if (screenRaster->bitsPerPixel == 8)
    {
        screenRaster->buffer = _TIFFmalloc(rasterSize * sizeof(uint8_t));
    }
    else /* bitsPerSample == 16 */
    {
        screenRaster->buffer = _TIFFmalloc(rasterSize * sizeof(uint16_t));
    }
    if (!screenRaster->buffer)
    {
        printf("Error: _TIFFmalloc() failed for CTiffReaderObject raster data\n");
        TIFFClose(tif);
        return -1;
    }

    void* pSrc = screenRaster->buffer;
    for (uint32_t row = 0; row < screenRaster->height; row++)
    {
        if (TIFFReadScanline(tif, pSrc, row) == -1)
        {
            printf("Error: TIFFReadScanline() failed\n");
            TIFFClose(tif);
            return -1;
        }
        if (photometric == PHOTOMETRIC_MINISBLACK)
        {
            if (screenRaster->bitsPerPixel == 8)
            {
                uint8_t* p8 = (uint8_t *)pSrc;
                for (uint32_t i = 0; i < screenRaster->stride; i++)
                    p8[i] = 255 - p8[i];
            }
            else /* bitsPerSample == 16 */
            {
                uint16_t* p16 = (uint16_t *)pSrc;
                for (uint32_t i = 0; i < screenRaster->stride; i++)
                    p16[i] = 65535 - p16[i];
            }
        }

        pSrc = (void *)((uint8_t *)pSrc + screenRaster->stride);
    }

    TIFFClose(tif);

    return 0;
}

void charToWchar(char* source, wchar_t* destination)
{
    size_t length = strlen(source);
    for (size_t i = 0; i < length; ++i)
    {
        destination[i] = static_cast<wchar_t>(source[i]);
    }
}

int main(int argc, char** argv)
{
    char szPathnameIn[MAX_PATH] = {0};
    char szPathnameOut[MAX_PATH] = {0};
    TiffExtraOption tiffExtraOption;

    if (argc != 6)
    {
        printf("%s <ScreenPath> <Channel> <XOffset> <YOffset> <TIFF input file>\n", argv[0]);
        printf("\twhere: <XOffset> and <YOffset> are only supported for chameleon or mirrorpearl \n");
        return -1;
    }

    char* szScreenPath = argv[1];
    int channel = atoi(argv[2]);
    unsigned int xOffset = atoi(argv[3]);
    unsigned int yOffset = atoi(argv[4]);

    sprintf_s(szPathnameIn, "%s", argv[5]);
    sprintf_s(szPathnameOut, "%s_output.tif", szPathnameIn);
    
    // Output what we're going to do
    fwprintf(stderr, L"ScreenPro: \n");
    printf("Screening file: \"%s\"\n", szPathnameIn);
    printf("Using screen: \"%s\"\n", szScreenPath);
    printf("Using X/Y offsets: %d %d\n", xOffset, yOffset);
    printf("Outputting to: \"%s\"\n", szPathnameOut);

    // Load ScreenPro library and retrieve all exports
    void* libHandle = getFunctionsPointers();
            printf("It gets here before there");
     ScreenProPermit permit =
     {  const_cast<char*>("ebW+X7d8kfdGCt5a5OjzTHyKigjymWTUbOKolA9T8yCKZQuZQBOKyVNTw4lSLjKPOJo3XLk6eEU9trz7Pc/wcjJN/9d0wmKZyRjr5q3gIb420NZUqZfQmQrVO9kPrwCEy6vr9uIaCGUcpSG2fmMtJJ3DYy+3UfpmaF2MrsM5gD+GhywpGzpWterahSsCXkoNByScJlAFWU7cj+eLLz3uWbhRWJRv95WIVRRuI6v9nQKqrx+abyu5a18M/Y/evt5jObD2BQ5wuu5KCA9MTI3hRxj4846Pm1osNnQd2r7hNVtyY4+Wt971M5T5LJKOJGgRcnf+ypEsP9C/rbe4aBQjKyk/UI49nOGcW6X3C/R04LgGoUiMvPf0l5OMWFysQjw1IXOn8ivROt+3LbY/fuoUDKEzTUTNchihIszbKaXkcyo="),  // Replace with license key if you have one.
         const_cast<char*>("f5da61d2-3291-40ed-82a0-481d2f2d026a"),  // Replace with password if you have one.
         1,        // Replace with number of output boards if you wish.
         nullptr,  // Replace with OEM Id for PrintFlat if you have one.
         nullptr   // Replace with expiry date for PrintFlat if you have one. The format is "yyyymmdd" format.
     };
        printf("It gets here");
    ScreenProResult result = fnScreenProStartup("ScreenProStartup.txt", getInlineDataIOHandler(), &permit);
    if (result != SP_SUCCESS)
    {
        printf("ScreenPro start up failed. Error code: %d \n", result);
        return -1;
    }    

    // Get PrintFlat subscription. The subscription is needed in PrintFlat to create calibrations.
    // OEM Id and expiry date fields are needed for subscription.
    PrintFlatSubscription printFlatSubscription;
    result = fnScreenProGetPrintFlatSubscription(&printFlatSubscription);
    if (result != SP_SUCCESS)
    {
        printf("ScreenPro failed to create PrintFlat subscription. Error code: %d \n", result);
    }
    else
    {
        printf("ScreenPro successfully generated PrintFlat subscription: %s \n", printFlatSubscription.subscription);
    }

    ScreenProRaster raster = { sizeof(ScreenProRaster) };
    ScreenProLayout layout = { sizeof(ScreenProLayout), (uint64_t)channel, { (uint64_t)xOffset , (uint64_t)yOffset }, false, { CALIBRATION_LEFT, 0, false } };

    // Get screen info
    ScreenProScreenInfo screenInfo;
    if ((result = fnScreenProQueryScreen(szScreenPath, layout.channelNumber, &screenInfo)) != SP_SUCCESS)
    {
        printf("ScreenPro failed to retrieve screen info. Error code: %d \n", result);
        (void)fnScreenProShutdown();
        freeSharedLibrary((void*)libHandle);
        return -1;
    }

    // x/y offsets only applicable for Chameleon or MirrorPearl
    if ((xOffset != 0 || yOffset != 0) && screenInfo.screenType != SP_CHAMELEON && screenInfo.screenType != SP_MIRROR && screenInfo.screenType != SP_PEARL)
    {
        printf("Error: X/Y offsets are not supported for the given screen %s (currently only supported for chameleon or mirrorpearl).\n", szScreenPath);
        (void)fnScreenProShutdown();
        freeSharedLibrary((void*)libHandle);
        return -1;
    }

    // Read the input Tiff file
    if (ReadTifFile(szPathnameIn, &raster, &tiffExtraOption) < 0)
    {
        printf("Failed to read file.\n");
        (void)fnScreenProShutdown();
        freeSharedLibrary((void*)libHandle);
        return -1;
    }

    // Screen the input raster
    if ((result = fnScreenProScreen(szScreenPath, &raster, &layout)) != SP_SUCCESS)
    {
        printf("ScreenPro failed to screen file. Error code: %d \n", result);
        (void)fnScreenProShutdown();
        freeSharedLibrary((void*)libHandle);
        return -1;
    }
    
    // Shutdown ScreenPro.
    if ((result = fnScreenProShutdown()) != SP_SUCCESS)
    {
        printf("ScreenPro failed to shut down. Error code: %d \n", result);
        freeSharedLibrary((void*)libHandle);
        return -1;
    }

    //Write the output to tif
    WriteOutput(szPathnameOut, &raster, (int) screenInfo.maxDrops, &tiffExtraOption);

    // Free ScreenPro library before exiting
    freeSharedLibrary( (void*)libHandle);
printf("ScreenPro has completed screening \n");
    return 0;
}

