/*
 * Copyright (C) 2013-2020 Global Graphics Software Ltd. All rights reserved
 *
 *  Simple sample application for EDS renderring using the JawsMako APIs.
 */

#include <algorithm>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <wctype.h>
#include <jawsmako/jawsmako.h>
#include <jawsmako/xpsoutput.h>
#include <jawsmako/pdfinput.h>
#include <edl/edlversion.h>
#include <edl/iabort.h>
#include <edl/platform_utils.h>
#include <edl/edlsimplebuffer.h>

using namespace JawsMako;
using namespace EDL;

// Print usage
static void usage(const String& argv0)
{
    std::wcerr << L"Usage: " << argv0 << L" <source file> <output stem for PNG images>" << std::endl; // required arguments
}

// Determine the associated format for a given path from the file extension
static eFileFormat formatFromPath(const String& path)
{
    // Get the extension in lower case
    if (path.size() < 3)
    {
        // Cannot determine the extension if there isn't one!
        std::string message("Cannot determine file extension for path ");
        message += StringToU8String(path).c_str();
        throw std::length_error(message);
    }

    size_t extensionPosition = path.find_last_of('.');
    if (extensionPosition != String::npos)
    {
        String extension = path.substr(extensionPosition);
        std::transform(extension.begin(), extension.end(), extension.begin(), towlower);

        if (extension == L".xps")
            return eFFXPS;
        else if (extension == L".pdf")
            return eFFPDF;
        else if (extension == L".svg")
            return eFFSVG;
        else if (extension == L".ps")
            return eFFPS;
        else if (extension == L".eps")
            return eFFEPS;
        else if (extension == L".pcl")
            return eFFPCL5;
        else if (extension == L".pxl")
            return eFFPCLXL;
    }

    std::string message("Unsupported file type for path ");
    message += StringToU8String(path).c_str();
    throw std::invalid_argument(message);
}

#ifdef _WIN32
int wmain(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif
{
    try
    {
        // Create our JawsMako instance.
        // Currently only one instance is allowed at any one time.
        IJawsMakoPtr jawsMako = IJawsMako::create(".");
        IJawsMako::enableAllFeatures(jawsMako);


        if (argc != 3)
        {
#ifdef _WIN32
            usage(argv[0]);
#else
            usage(U8StringToString(U8String(argv[0])));
#endif
        }

        String inputFilePath;
        String outputFileStem;
#ifdef _WIN32
        inputFilePath = argv[1];
        outputFileStem = argv[2];
#else
        inputFilePath = U8StringToString(U8String(argv[1]));
        outputFileStem = U8StringToString(U8String(argv[2]));
#endif

        IInputPtr  input;

        // Work out the input file format from the file extension of the input file path
        input = IInput::create(jawsMako, formatFromPath(inputFilePath));
        // Get the assembly from the input.
        IDocumentAssemblyPtr assembly = input->open(inputFilePath);

        // The halftone - this example is Floyd Steinberg with drop sizes of 3,
        // serpentine enabled and no perturbation
        IJawsRenderer::CEDSHalftone halftone;
        halftone.dropSizes = 3;
        halftone.rows = 2;
        halftone.columns = 3;
        halftone.pixelColumn = 1;
        halftone.denominator = 16;
        halftone.weights[0] = 0;  halftone.weights[1] = 0;  halftone.weights[2] = 7; 
    	halftone.weights[3] = 0;  halftone.weights[4] = 0;  halftone.weights[5] = 3; 
    	halftone.weights[6] = 5;  halftone.weights[7] = 1;  halftone.weights[8] = 0; 
    	halftone.weights[9] = 0;  halftone.weights[10] = 0; halftone.weights[11] = 0; 
    	halftone.weights[12] = 0; halftone.weights[13] = 0; halftone.weights[14] = 0;
        halftone.weights[15] = 0; halftone.weights[16] = 0; halftone.weights[17] = 0; 
    	halftone.weights[18] = 0; halftone.weights[19] = 0;
        halftone.useSerpentine = true;
        halftone.perturbation = 0.0;

        uint32 pageIndex = 0;

        // For each document
        IJawsRendererPtr renderer = IJawsRenderer::create(jawsMako);
        for (uint32 docNum = 0; docNum < assembly->getNumDocuments(); docNum++)
        {
            IDocumentPtr document = assembly->getDocument(docNum);
            for (uint32 pageNum = 0; pageNum < document->getNumPages(); pageNum++)
            {
                IPagePtr page = document->getPage(pageNum);

                // Another page encountered
                pageIndex++;
                printf("Rendering page %u\n", pageIndex);

                IDOMImagePtr image;

                const double dpi = 150.0; // Adjust to suit

                // Here we'll use CMYK, but you can test RGB and Gray also.
                IDOMColorSpacePtr colourSpace = IDOMColorSpaceDeviceCMYK::create(jawsMako);
#if 1
                // Regular rendering version
                CEDLVector<IDOMImagePtr> images = renderer->renderScreened(page->getContent(), dpi, &halftone, colourSpace);

                // Combine the separations into a single image
                {
                    IDOMRecombineImage::Data params;
                    params.colorSpace = colourSpace;
                    params.componentImages = images;
                    image = createInstance<IDOMRecombineImage>(jawsMako);
                    if (!image || !image->init(&params))
                    {
                        printf("Could not create combined image!?\n");
                        throwEDLError(EDL_ERR_INVALID_IMAGE);
                    }
                }

#else
                // Frmae buffer rendering version
                CEDLVector<CEDLSimpleBuffer> frameBuffers;
                CEDLVector<void*>           bufferList;
                CEDLVector<uint32>           strideList;
                bufferList.resize(colourSpace->getNumComponents());
                frameBuffers.resize(colourSpace->getNumComponents());
                strideList.resize(colourSpace->getNumComponents());

                IDOMFixedPagePtr content = page->getContent();

                uint32 width = (uint32)(content->getWidth() / 96.0 * dpi);
                uint32 height = (uint32)(content->getHeight() / 96.0 * dpi);
                uint32 depth = (halftone.dropSizes == 1) ? 1 : 4;
                uint32 stride = (uint32)(width * depth + 7) / 8;

                for (uint32 i = 0; i < colourSpace->getNumComponents(); i++)
                {
                    frameBuffers[i].resize(stride * height);
                    strideList[i] = stride;
                    bufferList[i] = &(frameBuffers[i])[0];
                }

                // Render
                renderer->renderScreenedToFrameBuffers(page->getContent(), &bufferList[0], &strideList[0], width, height, &halftone, colourSpace);

                // Create images from the frame buffers
                CEDLVector<IDOMImagePtr> images(colourSpace->getNumComponents());

                for (uint32 k = 0; k < colourSpace->getNumComponents(); k++)
                {
                    uint8* frameBuffer = &frameBuffers[k][0];

                    IImageFrameWriterPtr    frameWriter;
                    IDOMImagePtr            frameImage;

                    // PNG can cope with 4bit images
                    frameImage = IDOMPNGImage::createWriterAndImage(jawsMako,
                        frameWriter,
                        IDOMColorSpaceDeviceGray::create(jawsMako),
                        width, height,
                        depth,
                        dpi, dpi,
                        eIECNone);


                    for (int32 y = 0; y < height; y++)
                    {
                        uint8* frameRow = &frameBuffer[y * stride];
                        frameWriter->writeScanLine(frameRow);
                    }
                    frameWriter->flushData();

                    images[k] = frameImage;
                }

                // Combine the separations into a single image
                {
                    IDOMRecombineImage::Data params;
                    params.colorSpace = colourSpace;
                    params.componentImages = images;
                    image = createInstance<IDOMRecombineImage>(jawsMako);
                    if (!image || !image->init(&params))
                    {
                        printf("Could not create combined image!?\n");
                        throwEDLError(EDL_ERR_INVALID_IMAGE);
                    }
                }


#endif
                page->release();

                // Create a PNG with the result. This will get converted to RGB
                char fileName[64];
                edlSnprintfE(fileName, sizeof(fileName), "%u.tif", pageIndex);
                String fullPath = outputFileStem;
                fullPath += U8StringToString(U8String(fileName));
                printf("Writing to %s\n", StringToU8String(fullPath).c_str());
                IDOMTIFFImage::encode(jawsMako, image, IOutputStream::createToFile(jawsMako, fullPath));
            }
        }
    }
    catch (IError & e)
    {
        String errorFormatString = getEDLErrorString(e.getErrorCode());
        std::wcerr << L"Exception thrown: " << e.getErrorDescription(errorFormatString) << std::endl;
#ifdef _WIN32
        // On windows, the return code allows larger numbers, and we can return the error code
        return e.getErrorCode();
#else
        // On other platforms, the exit code is masked to the low 8 bits. So here we just return
        // a fixed value.
        return 1;
#endif
    }
    catch (std::exception & e)
    {
        std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}




