//---------------------------------------------------------------------------------
//
//  Little Color Management System
//  Copyright (c) 1998-2022 Marti Maria Saguer
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//---------------------------------------------------------------------------------
//

#include "utils.h"


int Verbose = 0;

static char ProgramName[256] = "";

void FatalError(const char *frm, ...)
{
    va_list args;

    va_start(args, frm);
    fprintf(stderr, "[%s fatal error]: ", ProgramName);
    vfprintf(stderr, frm, args);
    fprintf(stderr, "\n");
    va_end(args);

    exit(1);
}

// Show errors to the end user (unless quiet option)
static
void MyErrorLogHandler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text)
{
    if (Verbose >= 0)
        fprintf(stderr, "[%s]: %s\n", ProgramName, Text);

    UTILS_UNUSED_PARAMETER(ErrorCode);
    UTILS_UNUSED_PARAMETER(ContextID);
}


void InitUtils(const char* PName)
{
      strncpy(ProgramName, PName, sizeof(ProgramName));
      ProgramName[sizeof(ProgramName)-1] = 0;

      cmsSetLogErrorHandler(MyErrorLogHandler);
}


// Virtual profiles are handled here.
cmsHPROFILE OpenStockProfile(cmsContext ContextID, const char* File)
{
       if (!File)
            return cmsCreate_sRGBProfileTHR(ContextID);

       if (cmsstrcasecmp(File, "*Lab2") == 0)
                return cmsCreateLab2ProfileTHR(ContextID, NULL);

       if (cmsstrcasecmp(File, "*Lab4") == 0)
                return cmsCreateLab4ProfileTHR(ContextID, NULL);

       if (cmsstrcasecmp(File, "*Lab") == 0)
                return cmsCreateLab4ProfileTHR(ContextID, NULL);

       if (cmsstrcasecmp(File, "*LabD65") == 0) {

           cmsCIExyY D65xyY;

           cmsWhitePointFromTemp( &D65xyY, 6504);
           return cmsCreateLab4ProfileTHR(ContextID, &D65xyY);
       }

       if (cmsstrcasecmp(File, "*XYZ") == 0)
                return cmsCreateXYZProfileTHR(ContextID);

       if (cmsstrcasecmp(File, "*Gray22") == 0) {

           cmsToneCurve* Curve = cmsBuildGamma(ContextID, 2.2);
           cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
           cmsFreeToneCurve(Curve);
           return hProfile;
       }

        if (cmsstrcasecmp(File, "*Gray30") == 0) {

           cmsToneCurve* Curve = cmsBuildGamma(ContextID, 3.0);
           cmsHPROFILE hProfile = cmsCreateGrayProfileTHR(ContextID, cmsD50_xyY(), Curve);
           cmsFreeToneCurve(Curve);
           return hProfile;
       }

       if (cmsstrcasecmp(File, "*srgb") == 0)
                return cmsCreate_sRGBProfileTHR(ContextID);

       if (cmsstrcasecmp(File, "*null") == 0)
                return cmsCreateNULLProfileTHR(ContextID);


       if (cmsstrcasecmp(File, "*Lin2222") == 0) {

            cmsToneCurve*  Gamma = cmsBuildGamma(0, 2.2);
            cmsToneCurve*  Gamma4[4];
            cmsHPROFILE hProfile;

            Gamma4[0] = Gamma4[1] = Gamma4[2] = Gamma4[3] = Gamma;
            hProfile = cmsCreateLinearizationDeviceLink(cmsSigCmykData, Gamma4);
            cmsFreeToneCurve(Gamma);
            return hProfile;
       }


        return cmsOpenProfileFromFileTHR(ContextID, File, "r");
}

// Help on available built-ins
void PrintBuiltins(void)
{
     fprintf(stderr, "\nBuilt-in profiles:\n\n");
     fprintf(stderr, "\t*Lab2  -- D50-based v2 CIEL*a*b\n"
                     "\t*Lab4  -- D50-based v4 CIEL*a*b\n"
                     "\t*Lab   -- D50-based v4 CIEL*a*b\n"
                     "\t*XYZ   -- CIE XYZ (PCS)\n"
                     "\t*sRGB  -- sRGB color space\n"
                     "\t*Gray22 - Monochrome of Gamma 2.2\n"
                     "\t*Gray30 - Monochrome of Gamma 3.0\n"
                     "\t*null   - Monochrome black for all input\n"
                     "\t*Lin2222- CMYK linearization of gamma 2.2 on each channel\n\n");
}


// Auxiliary for printing information on profile
static
void PrintInfo(cmsHPROFILE h, cmsInfoType Info)
{
    char* text;
    int len;

    len = cmsGetProfileInfoASCII(h, Info, "en", "US", NULL, 0);
    if (len == 0) return;

    text = (char*) malloc(len * sizeof(char));
    if (text == NULL) return;

    cmsGetProfileInfoASCII(h, Info, "en", "US", text, len);

    if (strlen(text) > 0)
        printf("%s\n", text);

    free(text);
}



// Displays the colorant table
static
void PrintColorantTable(cmsHPROFILE hInput, cmsTagSignature Sig, const char* Title)
{
    cmsNAMEDCOLORLIST* list;
    int i, n;

    if (cmsIsTag(hInput, Sig)) {

        printf("%s:\n", Title);

        list = (cmsNAMEDCOLORLIST*) cmsReadTag(hInput, Sig);
        if (list == NULL) {
            printf("(Unavailable)\n");
            return;
        }

        n = cmsNamedColorCount(list);
        for (i=0; i < n; i++) {

            char Name[cmsMAX_PATH];

            cmsNamedColorInfo(list, i, Name, NULL, NULL, NULL, NULL);
            printf("\t%s\n", Name);
        }

        printf("\n");
    }

}


void PrintProfileInformation(cmsHPROFILE hInput)
{
    if (hInput == NULL) {
			fprintf(stderr, "*Wrong or corrupted profile*\n");
            return;
    }

    PrintInfo(hInput, cmsInfoDescription);
    PrintInfo(hInput, cmsInfoManufacturer);
    PrintInfo(hInput, cmsInfoModel);
    PrintInfo(hInput, cmsInfoCopyright);

    if (Verbose > 2) {

        PrintColorantTable(hInput, cmsSigColorantTableTag,    "Input colorant table");
        PrintColorantTable(hInput, cmsSigColorantTableOutTag, "Input colorant out table");
    }

    printf("\n");
}

// -----------------------------------------------------------------------------


void PrintRenderingIntents(void)
{
    cmsUInt32Number Codes[200];
    char* Descriptions[200];
    cmsUInt32Number n, i;

    fprintf(stderr, "-t<n> rendering intent:\n\n");

    n = cmsGetSupportedIntents(200, Codes, Descriptions);

    for (i=0; i < n; i++) {
        fprintf(stderr, "\t%u - %s\n", Codes[i], Descriptions[i]);
    }
    fprintf(stderr, "\n");
}



// ------------------------------------------------------------------------------

cmsBool SaveMemoryBlock(const cmsUInt8Number* Buffer, cmsUInt32Number dwLen, const char* Filename)
{
    FILE* out = fopen(Filename, "wb");
    if (out == NULL) {
        FatalError("Cannot create '%s'", Filename);
        return FALSE;
    }

    if (fwrite(Buffer, 1, dwLen, out) != dwLen) {
        FatalError("Cannot write %ld bytes to %s", (long) dwLen, Filename);
        return FALSE;
    }

    if (fclose(out) != 0) {
        FatalError("Error flushing file '%s'", Filename);
        return FALSE;
    }

    return TRUE;
}

// ------------------------------------------------------------------------------

// Return a pixel type on depending on the number of channels
int PixelTypeFromChanCount(int ColorChannels)
{
    switch (ColorChannels) {

        case 1: return PT_GRAY;
        case 2: return PT_MCH2;
        case 3: return PT_MCH3;
        case 4: return PT_CMYK;
        case 5: return PT_MCH5;
        case 6: return PT_MCH6;
        case 7: return PT_MCH7;
        case 8: return PT_MCH8;
        case 9: return PT_MCH9;
        case 10: return PT_MCH10;
        case 11: return PT_MCH11;
        case 12: return PT_MCH12;
        case 13: return PT_MCH13;
        case 14: return PT_MCH14;
        case 15: return PT_MCH15;

        default:

            FatalError("What a weird separation of %d channels?!?!", ColorChannels);
            return -1;
    }
}


// ------------------------------------------------------------------------------

// Return number of channels of pixel type
int ChanCountFromPixelType(int ColorChannels)
{
    switch (ColorChannels) {

      case PT_GRAY: return 1;

      case PT_RGB:
      case PT_CMY:
      case PT_Lab:
      case PT_YUV:
      case PT_YCbCr: return 3;

      case PT_CMYK: return 4 ;
      case PT_MCH2: return 2 ;
      case PT_MCH3: return 3 ;
      case PT_MCH4: return 4 ;
      case PT_MCH5: return 5 ;
      case PT_MCH6: return 6 ;
      case PT_MCH7: return 7 ;
      case PT_MCH8: return 8 ;
      case PT_MCH9: return 9 ;
      case PT_MCH10: return 10;
      case PT_MCH11: return 11;
      case PT_MCH12: return 12;
      case PT_MCH13: return 12;
      case PT_MCH14: return 14;
      case PT_MCH15: return 15;

      default:

          FatalError("Unsupported color space of %d channels", ColorChannels);
          return -1;
    }
}
