/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
  
  Derived by beppec56@openoffice.org from various examples
  in SampleICC library, the original copyright retained.
 
  Copyright:  © see below
*/

/*
 * The ICC Software License, Version 0.1
 *
 *
 * Copyright (c) 2003-2006 The International Color Consortium. All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        The International Color Consortium (www.color.org)"
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "ICC" and "The International Color Consortium" must
 *    not be used to imply that the ICC organization endorses or
 *    promotes products derived from this software without prior
 *    written permission. For written permission, please see
 *    <http://www.color.org/>.
 *
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
 * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the The International Color Consortium. 
 *
 *
 * Membership in the ICC is encouraged when this software is used for
 * commercial purposes. 
 *
 *  
 * For more information on The International Color Consortium, please
 * see <http://www.color.org/>.
 *  
 * 
 */

#include <math.h>
#include <iostream>
#include <fstream>
using namespace std;

#include "IccUtil.h"
#include "IccProfile.h"

#include "Vetters.h"
#include "CAT.h"
#include "CLUT.h"

const char * const icc_file_name = "sRGB-IEC61966-2.1.icc";
const char * const hxx_file_name = "sRGB-IEC61966-2.1.hxx";
const char * const this_file_name_and_location =" * icc/source/create_sRGB_profile/create_sRGB_profile.cpp";

const char* const description = "sRGB IEC61966-2.1";
//const char* const devicemanufact = "IEC http://www.iec.ch"; not used, device manufactured by OOo seems funny...
const char* const devicemodel = "IEC 61966-2.1 Default RGB colour space - sRGB";
const char* const copyright = "The Contents of this file are made available subject to the terms of GNU Lesser General Public License version 3";

// the creation date is fixed, corresponds to the last time this file has been changed
// NOTE: change this date values whenever the data inside the profile are changed.
const int  data_last_changed_year =     2011;
const int  data_last_changed_month =    10;
const int  data_last_day =              16;
const int  data_last_changed_hour =     18;
const int  data_last_changed_minute =   55;

// the following string array it's the standard OOo header format
const char * const TheHeader1[] =
{
    "/*************************************************************************",
    " *",
    " *  OpenOffice.org - a multi-platform office productivity suite",
    " *",
    " *  sRGB-IEC61966-2.1.hxx",
    " *",
    " *  creator: create_sRGB_profile",
    NULL
};

const char * const TheHeader2[] =
{
    " *",
    " * Copyright 2011 Apache OpenOffice.org.",
    " *",
    " * Licensed under the Apache License, Version 2.0 (the \"License\");",
    " * you may not use this file except in compliance with the License.",
    " * You may obtain a copy of the License at",
    " *",
    " *     http://www.apache.org/licenses/LICENSE-2.0",
    " *",
    " * Unless required by applicable law or agreed to in writing, software",
    " * distributed under the License is distributed on an \"AS IS\" BASIS,",
    " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.",
    " * See the License for the specific language governing permissions and",
    " * limitations under the License.",
    " *",
    " ************************************************************************/",
    "",
    "#ifndef INCLUDED_ICC_SRGB_IEC61966_2_1_H",
    "#define INCLUDED_ICC_SRGB_IEC61966_2_1_H",
    "",
    "/***********************************************************************",
    " * NOTE:",
    " * this file is automatically generated by running the program",
    " * obtained building:",
    this_file_name_and_location,
    " * contained in module icc",
    " * modify that program if you need to change something.",
    " ***********************************************************************/",
    NULL // last string, a null
};

const char * const TheTail[] =
{
    "#endif /* INCLUDED_ICC_SRGB_IEC61966_2_1_H */",
    NULL
};

icFloatNumber computeIEC_RGBtoXYZ( icFloatNumber indata )
{
    double retval = 0.0;
    if(indata < 0.04045)
        retval = indata/12.92;
    else // apply the other conversion
        retval = pow( (indata + 0.055)/1.055 , 2.4);

    return retval;
}

icFloatNumber computeIEC_XYZtoRGB( icFloatNumber indata )
{
    icFloatNumber retval = 0.0;
    if(indata < 0.0031308)
        retval = indata*12.92;
    else // apply the other conversion
        retval =  1.055*pow( indata , icFloatNumber(1.0/2.4)) - 0.055;

    //  cout << retval << endl;
    return retval;
}

void dumpTag(FILE *outfile, CIccProfile *pIcc, icTagSignature sig)
{
    CIccTag *pTag = pIcc->FindTag(sig);
    char buf[64];
    CIccInfo Fmt;

    std::string contents;

    if (pTag)
    {
        fprintf(outfile, "\nContents of %s tag (%s)\n", Fmt.GetTagSigName(sig), icGetSig(buf, sig)); 
        fprintf(outfile,"Type:   ");
        
        if (pTag->IsArrayType())  fprintf(outfile, "Array of ");

        fprintf(outfile, "%s\n", Fmt.GetTagTypeSigName(pTag->GetType()));
        pTag->Describe(contents);
        fwrite(contents.c_str(), contents.length(), 1, outfile);
    }
    else
        fprintf(outfile, "Tag (%s) not found in profile\n", icGetSig(buf, sig));
}

void dumpProfile(FILE *outfile, const char * profileName)
{
    CIccProfile *pIcc;
    icValidateStatus nStatus;

    pIcc = OpenIccProfile(profileName);

    if (!pIcc)
        printf("Unable to open '%s'\n", profileName);
    else
    {
        icHeader *pHdr = &pIcc->m_Header;
        CIccInfo Fmt;
        char buf[64];

        fprintf(outfile,"Profile:          '%s'\n", profileName);
        if(Fmt.IsProfileIDCalculated(&pHdr->profileID))
            fprintf(outfile,"Profile ID:        %s\n", Fmt.GetProfileID(&pHdr->profileID));
        else
            fprintf(outfile,"Profile ID:       Profile ID not calculated.\n");
        fprintf(outfile,"Size:             %ld(0x%lx) bytes\n", pHdr->size, pHdr->size);

        fprintf(outfile,"\nHeader\n");
        fprintf(outfile,"------\n");
        fprintf(outfile,"Attributes:       %s\n", Fmt.GetDeviceAttrName(pHdr->attributes));
        fprintf(outfile,"Cmm:              %s\n", Fmt.GetCmmSigName((icCmmSignature)(pHdr->cmmId)));
        fprintf(outfile,"Creation Date:    %d/%d/%d  %02u:%02u:%02u\n",
                pHdr->date.month, pHdr->date.day, pHdr->date.year,
                pHdr->date.hours, pHdr->date.minutes, pHdr->date.seconds);
        fprintf(outfile,"Creator:          %s\n", icGetSig(buf, pHdr->creator));
        fprintf(outfile,"Data Color Space: %s\n", Fmt.GetColorSpaceSigName(pHdr->colorSpace));
        fprintf(outfile,"Flags             %s\n", Fmt.GetProfileFlagsName(pHdr->flags));
        fprintf(outfile,"PCS Color Space:  %s\n", Fmt.GetColorSpaceSigName(pHdr->pcs));
        fprintf(outfile,"Platform:         %s\n", Fmt.GetPlatformSigName(pHdr->platform));
        fprintf(outfile,"Rendering Intent: %s\n", Fmt.GetRenderingIntentName((icRenderingIntent)(pHdr->renderingIntent)));
        fprintf(outfile,"Type:             %s\n", Fmt.GetProfileClassSigName(pHdr->deviceClass));
        fprintf(outfile,"Version:          %s\n", Fmt.GetVersionName(pHdr->version));
        fprintf(outfile,"Illuminant:       X=%.4lf, Y=%.4lf, Z=%.4lf\n",
                icFtoD(pHdr->illuminant.X),
                icFtoD(pHdr->illuminant.Y),
                icFtoD(pHdr->illuminant.Z));

        fprintf(outfile,"\nProfile Tags\n");
        fprintf(outfile,"------------\n");

        fprintf(outfile,"%25s    ID    %8s\t%8s\n", "Tag", "Offset", "Size");
        fprintf(outfile,"%25s  ------  %8s\t%8s\n", "----", "------", "----");

        int n;
        TagEntryList::iterator i;

        for (n=0, i=pIcc->m_Tags->begin(); i!=pIcc->m_Tags->end(); i++, n++)
        {
            fprintf(outfile,"%25s  %s  %8ld\t%8ld\n", Fmt.GetTagSigName(i->TagInfo.sig),
                    icGetSig(buf, i->TagInfo.sig, false), 
                    i->TagInfo.offset, i->TagInfo.size);
        }

        for (n=0, i=pIcc->m_Tags->begin(); i!=pIcc->m_Tags->end(); i++, n++)
            dumpTag(outfile, pIcc, i->TagInfo.sig);
    }
    delete pIcc;
}

int main(int argc, char* argv[])
{
    const char* myName = path_tail(argv[0]);

    try
    {
        const char* const out_file_pathname = icc_file_name;

        CIccProfile profile;
        profile.InitHeader();

        profile.m_Header.date.year = data_last_changed_year;
        profile.m_Header.date.month = data_last_changed_month;
        profile.m_Header.date.day = data_last_day;
        profile.m_Header.date.hours = data_last_changed_hour;
        profile.m_Header.date.minutes = data_last_changed_minute;
        profile.m_Header.date.seconds = 0;

        profile.m_Header.deviceClass = icSigDisplayClass;
        profile.m_Header.colorSpace = icSigRgbData;
        profile.m_Header.pcs = icSigXYZData;
        profile.m_Header.platform = icSigUnkownPlatform;
        profile.m_Header.attributes = static_cast<icUInt64Number>(icReflective);
        profile.m_Header.renderingIntent = icPerceptual;

        profile.m_Header.cmmId = 0x6E6F6E65; /* 'none' */
        profile.m_Header.model = 0x73524742;//sRGB

        profile.m_Header.version=icVersionNumberV2_1;

        // Required tags for a three-component matrix-based display profile, as laid
        // out by specification ICC.1:1998-09 (clause 6.3)  are:
        //   
        //   copyrightTag
        //   profileDescriptionTag
        //   redMatrixColumnTag
        //   greenMatrixColumnTag
        //   blueMatrixColumnTag
        //   redTRCTag
        //   greenTRCTag
        //   blueTRCTag
        //   mediaWhitePointTag

        // the other tags:
        //
        // technologyTag
        // deviceModelTag
        // deviceMfgDescTag
        // mediaBlackPointTag
        // viewingCondDescTag
        // viewingConditionsTag
        // luminanceTag
        // measurementTag
        // 
        // are optionals, added for completeness

        // the element below are sorted in the same order as
        // the list above, but the LUT table,
        // embedded at the end of the profile

        // copyrightTag
        CIccTagText* copyrightTag = new CIccTagText;
        copyrightTag->SetText(copyright);
        profile.AttachTag(icSigCopyrightTag, copyrightTag);

        // profileDescriptionTag
        CIccTagTextDescription* descriptionTag = new CIccTagTextDescription;
        descriptionTag->SetText(description);
        profile.AttachTag(icSigProfileDescriptionTag, descriptionTag);

        CIccTagXYZ* redMatrixColumnTag = new CIccTagXYZ;
        //values from raccomandation of ICC for sRGB, D50 referenced characterisation data
        //should be: 0.4361, 0.2225, 0.0139 according to application notes,
        // the 'X' value below is the one commonly in use on a very
        // diffused sRGB profile
        (*redMatrixColumnTag)[0].X = icDtoF(0.4361);
        (*redMatrixColumnTag)[0].Y = icDtoF(0.2225);
        (*redMatrixColumnTag)[0].Z = icDtoF(0.0139);
        profile.AttachTag(icSigRedMatrixColumnTag, redMatrixColumnTag);

        CIccTagXYZ* greenMatrixColumnTag = new CIccTagXYZ;
        //values from raccomandation of ICC for sRGB, D50 referenced characterisation data
        (*greenMatrixColumnTag)[0].X = icDtoF(0.3851);
        (*greenMatrixColumnTag)[0].Y = icDtoF(0.7169);
        (*greenMatrixColumnTag)[0].Z = icDtoF(0.0971);
        profile.AttachTag(icSigGreenMatrixColumnTag, greenMatrixColumnTag);

        CIccTagXYZ* blueMatrixColumnTag = new CIccTagXYZ;
        //values from raccomandation of ICC for sRGB, D50 referenced characterisation data
        //should be: 0.1431, 0.0606, 0.7139 according to application notes,
        // the 'Z' value below is the one commonly in use on a very
        // diffused sRGB profile
        (*blueMatrixColumnTag)[0].X = icDtoF(0.1431);
        (*blueMatrixColumnTag)[0].Y = icDtoF(0.0606);
        (*blueMatrixColumnTag)[0].Z = icDtoF(0.7141);
        profile.AttachTag(icSigBlueMatrixColumnTag, blueMatrixColumnTag);

        // mediaWhitePointTag
        CIccTagXYZ* whitePointTag = new CIccTagXYZ;
        (*whitePointTag)[0].X = icDtoF(0.9505);
        (*whitePointTag)[0].Y = icDtoF(1.0);
        (*whitePointTag)[0].Z = icDtoF(1.0891);
        profile.AttachTag(icSigMediaWhitePointTag, whitePointTag);        

        //device signature (technologytag)
        CIccTagSignature* deviceSign = new CIccTagSignature;
        deviceSign->SetValue( icSigCRTDisplay );
        profile.AttachTag( icSigTechnologyTag, deviceSign );

        //device model tag
        CIccTagTextDescription* deviceModelTag = new CIccTagTextDescription;
        deviceModelTag->SetText("IEC 61966-2.1 Default RGB colour space - sRGB");
        profile.AttachTag( icSigDeviceModelDescTag, deviceModelTag);

        // deviceMfgDescTag
        CIccTagTextDescription* deviceMfgTag = new CIccTagTextDescription;
        deviceMfgTag->SetText("IEC http://www.iec.ch");
        profile.AttachTag( icSigDeviceMfgDescTag, deviceMfgTag);

        // mediaBlackPointTag
        CIccTagXYZ* blackPointTag = new CIccTagXYZ;
        (*blackPointTag)[0].X =
        (*blackPointTag)[0].Y =
        (*blackPointTag)[0].Z = icDtoF(0.0);
        profile.AttachTag(icSigMediaBlackPointTag, blackPointTag);

        // viewingCondDescTag
        CIccTagTextDescription* viewingCondDescTag = new CIccTagTextDescription;
        viewingCondDescTag->SetText("Reference viewing condition according to IEC 61966-2.1");
        profile.AttachTag( icSigViewingCondDescTag, viewingCondDescTag );

        // viewingConditionsTag
        CIccTagViewingConditions* viewingConditionsTag = new  CIccTagViewingConditions;
        // Illuminant tristimulus value        
        (*viewingConditionsTag).m_XYZIllum.X = icDtoF(19.6445);
        (*viewingConditionsTag).m_XYZIllum.Y = icDtoF(20.3718);
        (*viewingConditionsTag).m_XYZIllum.Z = icDtoF(16.8089);
        // surround tristimulus value        
        (*viewingConditionsTag).m_XYZSurround.X = icDtoF(3.9289);
        (*viewingConditionsTag).m_XYZSurround.Y = icDtoF(4.0744);
        (*viewingConditionsTag).m_XYZSurround.Z = icDtoF(3.3618);
        (*viewingConditionsTag).m_illumType = icIlluminantD50;
        profile.AttachTag( icSigViewingConditionsType, viewingConditionsTag );

        // luminanceTag
        CIccTagXYZ* luminanceTag = new CIccTagXYZ;
        (*luminanceTag)[0].X = icDtoF(76.0365);
        (*luminanceTag)[0].Y = icDtoF(80.0);
        (*luminanceTag)[0].Z = icDtoF(87.1246);
        profile.AttachTag(icSigLuminanceTag, luminanceTag);

        // measurementTag
        CIccTagMeasurement* measurementTag = new  CIccTagMeasurement;
        (*measurementTag).m_Data.stdObserver = icStdObs1931TwoDegrees;
        (*measurementTag).m_Data.backing.X =
        (*measurementTag).m_Data.backing.Y =
        (*measurementTag).m_Data.backing.Z = icDtoF(0.0);
        (*measurementTag).m_Data.geometry = icGeometryUnknown;
        // the flare is 1%, but the library doesn't seem all right with this
        // see specification ICC.1:1998-09, clause 6.5.8, table 55 fot the right
        // format of the data value
        (*measurementTag).m_Data.flare = static_cast< icMeasurementFlare > ( icDtoUF( 0.01 ) );//means 1%
        (*measurementTag).m_Data.illuminant = icIlluminantD65;
        profile.AttachTag(icSigMeasurementTag, measurementTag );

        // compute the LUT curves, they are equal for all three colors
        // so only one LUT is computed and stored
        int N = 1024; // number of points in LUTs            
        CIccTagCurve* colorTRCTag = new CIccTagCurve(N);
        // apply conversion from RGB to XYZ, stepping the RGB value linearly from 0 to 100%
        // 1024 steps are computed            
        for (int i = 0; i < N; ++i)
            (*colorTRCTag)[i] = computeIEC_RGBtoXYZ( (icFloatNumber)i/(N-1));

        profile.AttachTag(icSigRedTRCTag, colorTRCTag);
        profile.AttachTag(icSigGreenTRCTag, colorTRCTag);
        profile.AttachTag(icSigBlueTRCTag, colorTRCTag);

        //Verify things
        string validationReport;
        icValidateStatus validationStatus = profile.Validate(validationReport);

        switch (validationStatus)
        {
        case icValidateOK:
            break;
      
        case icValidateWarning:
            clog << "Profile validation warning" << endl
                 << validationReport;
            break;
      
        case icValidateNonCompliant:
            clog << "Profile non compliancy" << endl
                 << validationReport;
            break;
      
        case icValidateCriticalError:
        default:
            clog << "Profile Error" << endl
                 << validationReport;
        }

        // Out it goes
        CIccFileIO out;
        out.Open(out_file_pathname, "wb+");
        profile.Write(&out);
        out.Close();

        FILE *headerfile = fopen(hxx_file_name,"w");

        //print OpenOffice standard file header
        const char *the_string;

        int idx = 0;

        while((the_string = TheHeader1[idx++]) != NULL )
            fprintf(headerfile,"%s\n",the_string);
// print the creation date (today)
// print the date of sRGB ICC profile data
        fprintf(headerfile," *  the date of last change to sRGB ICC profile data is:\n *  %4d/%02d/%02d - %02d:%02d\n",
                data_last_changed_year, data_last_changed_month,
                data_last_day, data_last_changed_hour,data_last_changed_minute );

        idx = 0;

        while((the_string = TheHeader2[idx++]) != NULL )
            fprintf(headerfile,"%s\n",the_string);

        {
// spit out the data structure (an array of unsigned char)            
            FILE *infile;

            int achar, number = 1;

            infile = fopen(out_file_pathname,"rb");

            fseek(infile,0,SEEK_END);
            long int thesize= ftell(infile);
            fseek(infile,0,SEEK_SET);

            fprintf(headerfile,"\nsal_uInt8 nsRGB_ICC_profile[%d]=\n{\n    ",thesize);

            do
            {
                achar = fgetc(infile);
                if(achar == EOF)
                    break;
                fprintf(headerfile,"0x%02x",achar);
                if(number % 12 == 0)
                    fprintf(headerfile,",\n    ");
                else
                    fprintf(headerfile,", ");
                number++;
            } while(achar != EOF );
            fprintf(headerfile,"\n};\n\n");

            fclose(infile);      
        }
        // append the file contents, in human readable form, as comment in the header
        // get the functions from iccDump

        fprintf(headerfile,"/*****************\n\n");

        fprintf(headerfile,"This ICC profile contains the following data:\n\n");

        dumpProfile(headerfile, out_file_pathname );

        fprintf(headerfile,"\n*****************/\n");
        //now append the tail      
        idx = 0;
        while((the_string = TheTail[idx++]) != NULL )
            fprintf(headerfile,"%s\n",the_string);

        fclose(headerfile);

        return EXIT_SUCCESS;
    }
    catch (const std::exception& e)
    {
        cout << myName << ": error: " << e.what() << endl;
        return EXIT_FAILURE;
    }
}
