/*
 * Copyright 2017 The Android Open Source Project
 *
 * 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.
 */

#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>

#include <getopt.h>

#include <ui/ColorSpace.h>

using namespace android;
using namespace std;

uint32_t gSize = 32;
ColorSpace gColorSpaceSrc = ColorSpace::DisplayP3();
ColorSpace gColorSpaceDst = ColorSpace::extendedSRGB();
string gNameSrc = "DisplayP3";
string gNameDst = "extendedSRGB";

static void printHelp() {
    cout << "lutgen -d SIZE -s SOURCE -t TARGET <lut file>" << endl;
    cout << endl;
    cout << "Generate a 3D LUT to convert between two color spaces." << endl;
    cout << endl;
    cout << "If <lut file> ends in .inc, data is generated without the array declaration." << endl;
    cout << endl;
    cout << "Options:" << endl;
    cout << "  --help, -h" << endl;
    cout << "    print this message" << endl;
    cout << "  --dimension=, -d" << endl;
    cout << "    the dimension of the 3D LUT. Example: 17 for a 17x17x17 LUT. 32 by default" << endl;
    cout << "  --source=COLORSPACE, -s" << endl;
    cout << "    the source color space, see below for available names. DisplayP3 by default" << endl;
    cout << "  --target=COLORSPACE, -t" << endl;
    cout << "    the target color space, see below for available names. extendedSRGB by default" << endl;
    cout << endl;
    cout << "Colorspace names:" << endl;
    cout << "    sRGB" << endl;
    cout << "    linearSRGB" << endl;
    cout << "    extendedSRGB" << endl;
    cout << "    linearExtendedSRGB" << endl;
    cout << "    NTSC" << endl;
    cout << "    BT709" << endl;
    cout << "    BT2020" << endl;
    cout << "    AdobeRGB" << endl;
    cout << "    ProPhotoRGB" << endl;
    cout << "    DisplayP3" << endl;
    cout << "    DCIP3" << endl;
    cout << "    ACES" << endl;
    cout << "    ACEScg" << endl;
}

static const ColorSpace findColorSpace(const string& name) {
    if (name == "linearSRGB") return ColorSpace::linearSRGB();
    if (name == "extendedSRGB") return ColorSpace::extendedSRGB();
    if (name == "linearExtendedSRGB") return ColorSpace::linearExtendedSRGB();
    if (name == "NTSC") return ColorSpace::NTSC();
    if (name == "BT709") return ColorSpace::BT709();
    if (name == "BT2020") return ColorSpace::BT2020();
    if (name == "AdobeRGB") return ColorSpace::AdobeRGB();
    if (name == "ProPhotoRGB") return ColorSpace::ProPhotoRGB();
    if (name == "DisplayP3") return ColorSpace::DisplayP3();
    if (name == "DCIP3") return ColorSpace::DCIP3();
    if (name == "ACES") return ColorSpace::ACES();
    if (name == "ACEScg") return ColorSpace::ACEScg();
    return ColorSpace::sRGB();
}

static int handleCommandLineArgments(int argc, char* argv[]) {
    static constexpr const char* OPTSTR = "h:d:s:t:";
    static const struct option OPTIONS[] = {
            { "help",       no_argument,       nullptr, 'h' },
            { "dimension",  required_argument, nullptr, 'd' },
            { "source",     required_argument, nullptr, 's' },
            { "target",     required_argument, nullptr, 't' },
            { nullptr, 0, nullptr, 0 }  // termination of the option list
    };

    int opt;
    int index = 0;

    while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &index)) >= 0) {
        string arg(optarg ? optarg : "");
        switch (opt) {
            default:
            case 'h':
                printHelp();
                exit(0);
                break;
            case 'd':
                gSize = max(2, min(stoi(arg), 256));
                break;
            case 's':
                gNameSrc = arg;
                gColorSpaceSrc = findColorSpace(arg);
                break;
            case 't':
                gNameDst = arg;
                gColorSpaceDst = findColorSpace(arg);
                break;
        }
    }

    return optind;
}

int main(int argc, char* argv[]) {
    int optionIndex = handleCommandLineArgments(argc, argv);
    int numArgs = argc - optionIndex;

    if (numArgs < 1) {
        printHelp();
        return 1;
    }
    
    bool isInclude = false;

    string filename(argv[optionIndex]);
    size_t index = filename.find_last_of('.');

    if (index != string::npos) {
        string extension(filename.substr(index + 1));
        isInclude = extension == "inc";
    }

    ofstream outputStream(filename, ios::trunc);
    if (outputStream.good()) {
        auto lut = ColorSpace::createLUT(gSize, gColorSpaceSrc, gColorSpaceDst);
        auto data = lut.get();

        outputStream << "// generated with lutgen " << filename.c_str() << endl;
        outputStream << "// 3D LUT stored as an RGB16F texture, in GL order" << endl;
        outputStream << "// Size is " << gSize << "x" << gSize << "x" << gSize << endl;

        string src(gNameSrc);
        string dst(gNameDst);

        if (!isInclude) {
            transform(src.begin(), src.end(), src.begin(), ::toupper);
            transform(dst.begin(), dst.end(), dst.begin(), ::toupper);

            outputStream << "const size_t LUT_" << src << "_TO_" << dst << "_SIZE = " << gSize << endl;
            outputStream << "const uint16_t LUT_" << src << "_TO_" << dst << "[] = {";
        } else {
            outputStream << "// From " << src << " to " << dst << endl;
        }

        for (size_t z = 0; z < gSize; z++) {
            for (size_t y = 0; y < gSize; y++) {
                for (size_t x = 0; x < gSize; x++) {
                    if (x % 4 == 0) outputStream << endl << "    ";

                    half3 rgb = half3(*data++);

                    const uint16_t r = rgb.r.getBits();
                    const uint16_t g = rgb.g.getBits();
                    const uint16_t b = rgb.b.getBits();

                    outputStream << "0x" << setfill('0') << setw(4) << hex << r << ", ";
                    outputStream << "0x" << setfill('0') << setw(4) << hex << g << ", ";
                    outputStream << "0x" << setfill('0') << setw(4) << hex << b << ", ";
                }
            }
        }

        if (!isInclude) {
            outputStream << endl << "}; // end LUT" << endl;
        }

        outputStream << endl;
        outputStream.flush();
        outputStream.close();
    } else {
        cerr << "Could not write to file: " << filename << endl;
        return 1;

    }

    return 0;
}