/*
 * Copyright (C) 2011 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.
 *
 */

/*
 * Hardware Composer Color Equivalence
 *
 * Synopsis
 *   hwc_colorequiv [options] eFmt
 *
 *     options:
         -v - verbose
 *       -s <0.##, 0.##, 0.##> - Start color (default: <0.0, 0.0, 0.0>
 *       -e <0.##, 0.##, 0.##> - Ending color (default: <1.0, 1.0, 1.0>
 *       -r fmt - reference graphic format
 *       -D #.## - End of test delay
 *
 *     graphic formats:
 *       RGBA8888 (reference frame default)
 *       RGBX8888
 *       RGB888
 *       RGB565
 *       BGRA8888
 *       RGBA5551
 *       RGBA4444
 *       YV12
 *
 * Description
 *   Renders a horizontal blend in two frames.  The first frame is rendered
 *   in the upper third of the display and is called the reference frame.
 *   The second frame is displayed in the middle third and is called the
 *   equivalence frame.  The primary purpose of this utility is to verify
 *   that the colors produced in the reference and equivalence frames are
 *   the same.  The colors are the same when the colors are the same
 *   vertically between the reference and equivalence frames.
 *
 *   By default the reference frame is rendered through the use of the
 *   RGBA8888 graphic format.  The -r option can be used to specify a
 *   non-default reference frame graphic format.  The graphic format of
 *   the equivalence frame is determined by a single required positional
 *   parameter.  Intentionally there is no default for the graphic format
 *   of the equivalence frame.
 *
 *   The horizontal blend in the reference frame is produced from a linear
 *   interpolation from a start color (default: <0.0, 0.0, 0.0> on the left
 *   side to an end color (default <1.0, 1.0, 1.0> on the right side.  Where
 *   possible the equivalence frame is rendered with the equivalent color
 *   from the reference frame.  A color of black is used in the equivalence
 *   frame for cases where an equivalent color does not exist.
 */

#include <algorithm>
#include <assert.h>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <libgen.h>
#include <sched.h>
#include <sstream>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <vector>

#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <ui/GraphicBuffer.h>

#define LOG_TAG "hwcColorEquivTest"
#include <utils/Log.h>
#include <testUtil.h>

#include <hardware/hwcomposer.h>

#include "hwcTestLib.h"

using namespace std;
using namespace android;

// Defaults for command-line options
const bool defaultVerbose = false;
const ColorFract defaultStartColor(0.0, 0.0, 0.0);
const ColorFract defaultEndColor(1.0, 1.0, 1.0);
const char *defaultRefFormat = "RGBA8888";
const float defaultEndDelay = 2.0; // Default delay after rendering graphics

// Defines
#define MAXSTR               100
#define MAXCMD               200
#define BITSPERBYTE            8 // TODO: Obtain from <values.h>, once
                                 // it has been added

#define CMD_STOP_FRAMEWORK   "stop 2>&1"
#define CMD_START_FRAMEWORK  "start 2>&1"

// Macros
#define NUMA(a) (sizeof(a) / sizeof(a [0])) // Num elements in an array
#define MEMCLR(addr, size) do { \
        memset((addr), 0, (size)); \
    } while (0)

// Globals
static const int texUsage = GraphicBuffer::USAGE_HW_TEXTURE |
        GraphicBuffer::USAGE_SW_WRITE_RARELY;
static hwc_composer_device_1_t *hwcDevice;
static EGLDisplay dpy;
static EGLSurface surface;
static EGLint width, height;

// Functions prototypes
void init(void);
void printSyntax(const char *cmd);

// Command-line option settings
static bool verbose = defaultVerbose;
static ColorFract startRefColor = defaultStartColor;
static ColorFract endRefColor = defaultEndColor;
static float endDelay = defaultEndDelay;
static const struct hwcTestGraphicFormat *refFormat
    = hwcTestGraphicFormatLookup(defaultRefFormat);
static const struct hwcTestGraphicFormat *equivFormat;

/*
 * Main
 *
 * Performs the following high-level sequence of operations:
 *
 *   1. Command-line parsing
 *
 *   2. Stop framework
 *
 *   3. Initialization
 *
 *   4. Create Hardware Composer description of reference and equivalence frames
 *
 *   5. Have Hardware Composer render the reference and equivalence frames
 *
 *   6. Delay for amount of time given by endDelay
 *
 *   7. Start framework
 */
int
main(int argc, char *argv[])
{
    int rv, opt;
    bool error;
    char *chptr;
    unsigned int pass;
    char cmd[MAXCMD];
    string str;

    testSetLogCatTag(LOG_TAG);

    assert(refFormat != NULL);

    testSetLogCatTag(LOG_TAG);

    // Parse command line arguments
    while ((opt = getopt(argc, argv, "vs:e:r:D:?h")) != -1) {
        switch (opt) {
          case 'D': // End of test delay
                    // Delay between completion of final pass and restart
                    // of framework
            endDelay = strtod(optarg, &chptr);
            if ((*chptr != '\0') || (endDelay < 0.0)) {
                testPrintE("Invalid command-line specified end of test delay "
                           "of: %s", optarg);
                exit(1);
            }
            break;

          case 's': // Starting reference color
            str = optarg;
            while (optind < argc) {
                if (*argv[optind] == '-') { break; }
                char endChar = (str.length() > 1) ? str[str.length() - 1] : 0;
                if ((endChar == '>') || (endChar == ']')) { break; }
                str += " " + string(argv[optind++]);
            }
            {
                istringstream in(str);
                startRefColor = hwcTestParseColor(in, error);
                // Any parse error or characters not used by parser
                if (error
                    || (((unsigned int) in.tellg() != in.str().length())
                        && (in.tellg() != (streampos) -1))) {
                    testPrintE("Invalid command-line specified start "
                               "reference color of: %s", str.c_str());
                    exit(2);
                }
            }
            break;

          case 'e': // Ending reference color
            str = optarg;
            while (optind < argc) {
                if (*argv[optind] == '-') { break; }
                char endChar = (str.length() > 1) ? str[str.length() - 1] : 0;
                if ((endChar == '>') || (endChar == ']')) { break; }
                str += " " + string(argv[optind++]);
            }
            {
                istringstream in(str);
                endRefColor = hwcTestParseColor(in, error);
                // Any parse error or characters not used by parser
                if (error
                    || (((unsigned int) in.tellg() != in.str().length())
                        && (in.tellg() != (streampos) -1))) {
                    testPrintE("Invalid command-line specified end "
                               "reference color of: %s", str.c_str());
                    exit(3);
                }
            }
            break;

          case 'r': // Reference graphic format
            refFormat = hwcTestGraphicFormatLookup(optarg);
            if (refFormat == NULL) {
                testPrintE("Unkown command-line specified reference graphic "
                           "format of: %s", optarg);
                printSyntax(basename(argv[0]));
                exit(4);
            }
            break;

          case 'v': // Verbose
            verbose = true;
            break;

          case 'h': // Help
          case '?':
          default:
            printSyntax(basename(argv[0]));
            exit(((optopt == 0) || (optopt == '?')) ? 0 : 5);
        }
    }

    // Expect a single positional parameter, which specifies the
    // equivalence graphic format.
    if (argc != (optind + 1)) {
        testPrintE("Expected a single command-line postional parameter");
        printSyntax(basename(argv[0]));
        exit(6);
    }
    equivFormat = hwcTestGraphicFormatLookup(argv[optind]);
    if (equivFormat == NULL) {
        testPrintE("Unkown command-line specified equivalence graphic "
                   "format of: %s", argv[optind]);
        printSyntax(basename(argv[0]));
        exit(7);
    }

    testPrintI("refFormat: %u %s", refFormat->format, refFormat->desc);
    testPrintI("equivFormat: %u %s", equivFormat->format, equivFormat->desc);
    testPrintI("startRefColor: %s", ((string) startRefColor).c_str());
    testPrintI("endRefColor: %s", ((string) endRefColor).c_str());
    testPrintI("endDelay: %f", endDelay);

    // Stop framework
    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STOP_FRAMEWORK);
    if (rv >= (signed) sizeof(cmd) - 1) {
        testPrintE("Command too long for: %s", CMD_STOP_FRAMEWORK);
        exit(8);
    }
    testExecCmd(cmd);
    testDelay(1.0); // TODO - needs means to query whether asynchronous stop
                    // framework operation has completed.  For now, just wait
                    // a long time.

    init();

    // Use the upper third of the display for the reference frame and
    // the middle third for the equivalence frame.
    unsigned int refHeight = height / 3;
    unsigned int refPosY = 0; // Reference frame Y position
    unsigned int refPosX = 0; // Reference frame X position
    unsigned int refWidth = width - refPosX;
    if ((refWidth & refFormat->wMod) != 0) {
        refWidth += refFormat->wMod - (refWidth % refFormat->wMod);
    }
    unsigned int equivHeight = height / 3;
    unsigned int equivPosY = refHeight; // Equivalence frame Y position
    unsigned int equivPosX = 0;         // Equivalence frame X position
    unsigned int equivWidth = width - equivPosX;
    if ((equivWidth & equivFormat->wMod) != 0) {
        equivWidth += equivFormat->wMod - (equivWidth % equivFormat->wMod);
    }

    // Create reference and equivalence graphic buffers
    const unsigned int numFrames = 2;
    sp<GraphicBuffer> refFrame;
    refFrame = new GraphicBuffer(refWidth, refHeight,
                                 refFormat->format, texUsage);
    if ((rv = refFrame->initCheck()) != NO_ERROR) {
        testPrintE("refFrame initCheck failed, rv: %i", rv);
        testPrintE("  width %u height: %u format: %u %s", refWidth, refHeight,
                   refFormat->format,
                   hwcTestGraphicFormat2str(refFormat->format));
        exit(9);
    }
    testPrintI("refFrame width: %u height: %u format: %u %s",
               refWidth, refHeight, refFormat->format,
               hwcTestGraphicFormat2str(refFormat->format));

    sp<GraphicBuffer> equivFrame;
    equivFrame = new GraphicBuffer(equivWidth, equivHeight,
                                   equivFormat->format, texUsage);
    if ((rv = refFrame->initCheck()) != NO_ERROR) {
        testPrintE("refFrame initCheck failed, rv: %i", rv);
        testPrintE("  width %u height: %u format: %u %s", refWidth, refHeight,
                   refFormat->format,
                   hwcTestGraphicFormat2str(refFormat->format));
        exit(10);
    }
    testPrintI("equivFrame width: %u height: %u format: %u %s",
               equivWidth, equivHeight, equivFormat->format,
               hwcTestGraphicFormat2str(equivFormat->format));

    // Fill the frames with a horizontal blend
    hwcTestFillColorHBlend(refFrame.get(), refFormat->format,
                           startRefColor, endRefColor);
    hwcTestFillColorHBlend(equivFrame.get(), refFormat->format,
                           startRefColor, endRefColor);

    hwc_display_contents_1_t *list;
    size_t size = sizeof(hwc_display_contents_1_t) + numFrames * sizeof(hwc_layer_1_t);
    if ((list = (hwc_display_contents_1_t *) calloc(1, size)) == NULL) {
        testPrintE("Allocate list failed");
        exit(11);
    }
    list->flags = HWC_GEOMETRY_CHANGED;
    list->numHwLayers = numFrames;

    hwc_layer_1_t *layer = &list->hwLayers[0];
    layer->handle = refFrame->handle;
    layer->blending = HWC_BLENDING_NONE;
    layer->sourceCrop.left = 0;
    layer->sourceCrop.top = 0;
    layer->sourceCrop.right = width;
    layer->sourceCrop.bottom = refHeight;
    layer->displayFrame.left = 0;
    layer->displayFrame.top = 0;
    layer->displayFrame.right = width;
    layer->displayFrame.bottom = refHeight;
    layer->visibleRegionScreen.numRects = 1;
    layer->visibleRegionScreen.rects = &layer->displayFrame;

    layer++;
    layer->handle = equivFrame->handle;
    layer->blending = HWC_BLENDING_NONE;
    layer->sourceCrop.left = 0;
    layer->sourceCrop.top = 0;
    layer->sourceCrop.right = width;
    layer->sourceCrop.bottom = equivHeight;
    layer->displayFrame.left = 0;
    layer->displayFrame.top = refHeight;
    layer->displayFrame.right = width;
    layer->displayFrame.bottom = layer->displayFrame.top + equivHeight;
    layer->visibleRegionScreen.numRects = 1;
    layer->visibleRegionScreen.rects = &layer->displayFrame;

    // Perform prepare operation
    if (verbose) { testPrintI("Prepare:"); hwcTestDisplayList(list); }
    hwcDevice->prepare(hwcDevice, 1, &list);
    if (verbose) {
        testPrintI("Post Prepare:");
        hwcTestDisplayListPrepareModifiable(list);
    }

    // Turn off the geometry changed flag
    list->flags &= ~HWC_GEOMETRY_CHANGED;

    if (verbose) {hwcTestDisplayListHandles(list); }
    list->dpy = dpy;
    list->sur = surface;
    hwcDevice->set(hwcDevice, 1, &list);

    testDelay(endDelay);

    // Start framework
    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_START_FRAMEWORK);
    if (rv >= (signed) sizeof(cmd) - 1) {
        testPrintE("Command too long for: %s", CMD_START_FRAMEWORK);
        exit(12);
    }
    testExecCmd(cmd);

    return 0;
}

void init(void)
{
    // Seed pseudo random number generator
    // Seeding causes fill horizontal blend to fill the pad area with
    // a deterministic set of values.
    srand48(0);

    hwcTestInitDisplay(verbose, &dpy, &surface, &width, &height);

    hwcTestOpenHwc(&hwcDevice);
}

void printSyntax(const char *cmd)
{
    testPrintE("  %s [options] graphicFormat", cmd);
    testPrintE("    options:");
    testPrintE("      -s <0.##, 0.##, 0.##> - Starting reference color");
    testPrintE("      -e <0.##, 0.##, 0.##> - Ending reference color");
    testPrintE("      -r format - Reference graphic format");
    testPrintE("      -D #.## - End of test delay");
    testPrintE("      -v Verbose");
    testPrintE("");
    testPrintE("    graphic formats:");
    for (unsigned int n1 = 0; n1 < NUMA(hwcTestGraphicFormat); n1++) {
        testPrintE("      %s", hwcTestGraphicFormat[n1].desc);
    }
}
