/*
 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * *    * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of The Linux Foundation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * 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.
 */
#define LOG_NDEBUG 1

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <stdlib.h>

#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
#define LOG_TAG "QCOM PowerHAL"
#include <utils/Log.h>
#include <cutils/trace.h>
#include <hardware/hardware.h>
#include <hardware/power.h>

#include "utils.h"
#include "metadata-defs.h"
#include "hint-data.h"
#include "performance.h"
#include "power-common.h"

static int display_hint_sent;
int launch_handle = -1;
int launch_mode;

#ifdef EXTRA_POWERHAL_HINTS
static int process_cam_preview_hint(void *metadata)
{
    char governor[80];
    struct cam_preview_metadata_t cam_preview_metadata;

    if (get_scaling_governor(governor, sizeof(governor)) == -1) {
        ALOGE("Can't obtain scaling governor.");

        return HINT_NONE;
    }

    /* Initialize encode metadata struct fields */
    memset(&cam_preview_metadata, 0, sizeof(struct cam_preview_metadata_t));
    cam_preview_metadata.state = -1;
    cam_preview_metadata.hint_id = CAM_PREVIEW_HINT_ID;

    if (metadata) {
        if (parse_cam_preview_metadata((char *)metadata, &cam_preview_metadata) ==
            -1) {
            ALOGE("Error occurred while parsing metadata.");
            return HINT_NONE;
        }
    } else {
        return HINT_NONE;
    }

    if (cam_preview_metadata.state == 1) {
        if ((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) {
            /* sched and cpufreq params
             * above_hispeed_delay for LVT - 40ms
             * go hispeed load for LVT - 95
             * hispeed freq for LVT - 556 MHz
             * target load for LVT - 90
             * above hispeed delay for sLVT - 40ms
             * go hispeed load for sLVT - 95
             * hispeed freq for sLVT - 556 MHz
             * target load for sLVT - 90
             * bus DCVS set to V2 config:
             *  low power ceil mpbs - 2500
             *  low power io percent - 50
             */
            int resource_values[] = {0x41400000, 0x4, 0x41410000, 0x5F, 0x41414000, 0x22C,
                0x41420000, 0x5A, 0x41400100, 0x4, 0x41410100, 0x5F, 0x41414100, 0x22C,
                0x41420100, 0x5A, 0x41810000, 0x9C4, 0x41814000, 0x32};

            perform_hint_action(cam_preview_metadata.hint_id,
                    resource_values, sizeof(resource_values)/sizeof(resource_values[0]));
            ALOGI("Cam Preview hint start");
            return HINT_HANDLED;
        } else if ((strncmp(governor, SCHED_GOVERNOR, strlen(SCHED_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(SCHED_GOVERNOR))) {
            /*
             * lower bus BW to save power
             *   0x41810000: low power ceil mpbs = 2500
             *   0x41814000: low power io percent = 50
             */
            int resource_values[] = {0x41810000, 0x9C4, 0x41814000, 0x32};

            perform_hint_action(
                cam_preview_metadata.hint_id, resource_values,
                sizeof(resource_values) / sizeof(resource_values[0]));
            ALOGI("Cam Preview hint start");
            return HINT_HANDLED;
        }
    } else if (cam_preview_metadata.state == 0) {
        if (((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) ||
            ((strncmp(governor, SCHED_GOVERNOR, strlen(SCHED_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(SCHED_GOVERNOR)))) {
            undo_hint_action(cam_preview_metadata.hint_id);
            ALOGI("Cam Preview hint stop");
            return HINT_HANDLED;
        }
    }
    return HINT_NONE;
}
#endif

static int process_boost(int boost_handle, int duration)
{
    char governor[80];
    int eas_launch_resources[] = {0x40804000, 0xFFF, 0x40804100, 0xFFF,
                                         0x40800000, 0xFFF, 0x40800100, 0xFFF,
                                         0x41800000, 140,   0x40400000, 0x1};
    int hmp_launch_resources[] = {0x40C00000, 0x1,   0x40804000, 0xFFF,
                                         0x40804100, 0xFFF, 0x40800000, 0xFFF,
                                         0x40800100, 0xFFF, 0x41800000, 140,
                                         0x40400000, 0x1};
    int* launch_resources;
    size_t launch_resources_size;

    if (get_scaling_governor(governor, sizeof(governor)) == -1) {
        ALOGE("Can't obtain scaling governor.");
        return -1;
    }
    if (strncmp(governor, SCHED_GOVERNOR, strlen(SCHED_GOVERNOR)) == 0) {
        launch_resources = eas_launch_resources;
        launch_resources_size = sizeof(eas_launch_resources) / sizeof(eas_launch_resources[0]);
    } else if (strncmp(governor, INTERACTIVE_GOVERNOR,
                       strlen(INTERACTIVE_GOVERNOR)) == 0) { /*HMP boost*/
        launch_resources = hmp_launch_resources;
        launch_resources_size = sizeof(hmp_launch_resources) / sizeof(hmp_launch_resources[0]);
    } else {
        ALOGE("Unsupported governor.");
        return -1;
    }
    boost_handle = interaction_with_handle(
        boost_handle, duration, launch_resources_size, launch_resources);
    return boost_handle;
}

static int process_video_encode_hint(void *metadata)
{
    char governor[80];
    static int boost_handle = -1;

    if (get_scaling_governor(governor, sizeof(governor)) == -1) {
        ALOGE("Can't obtain scaling governor.");

        return HINT_NONE;
    }

    if (metadata) {
        int duration = 2000; // boosts 2s for starting encoding
        boost_handle = process_boost(boost_handle, duration);
        ALOGD("LAUNCH ENCODER-ON: %d MS", duration);
        if ((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) {
            /* 1. cpufreq params
             *    -above_hispeed_delay for LVT - 40ms
             *    -go hispeed load for LVT - 95
             *    -hispeed freq for LVT - 556 MHz
             *    -target load for LVT - 90
             *    -above hispeed delay for sLVT - 40ms
             *    -go hispeed load for sLVT - 95
             *    -hispeed freq for sLVT - 806 MHz
             *    -target load for sLVT - 90
             * 2. bus DCVS set to V2 config:
             *    -low power ceil mpbs - 2500
             *    -low power io percent - 50
             * 3. hysteresis optimization
             *    -bus dcvs hysteresis tuning
             *    -sample_ms of 10 ms
             *    -disable ignore_hispeed_notif
             *    -sLVT hispeed freq to 806MHz
             */
            int resource_values[] = {
                0x41810000, 0x9C4, 0x41814000, 0x32, 0x4180C000, 0x0, 0x41820000, 0xA,
                0x41438100, 0x1,  0x41438000, 0x1 };

            perform_hint_action(DEFAULT_VIDEO_ENCODE_HINT_ID,
                    resource_values, sizeof(resource_values)/sizeof(resource_values[0]));
            ALOGD("Video Encode hint start");
            return HINT_HANDLED;
        } else if ((strncmp(governor, SCHED_GOVERNOR, strlen(SCHED_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(SCHED_GOVERNOR))) {

            /* 1. bus DCVS set to V2 config:
             *    0x41810000: low power ceil mpbs - 2500
             *    0x41814000: low power io percent - 50
             * 2. hysteresis optimization
             *    0x4180C000: bus dcvs hysteresis tuning
             *    0x41820000: sample_ms of 10 ms
             */
            int resource_values[] = {0x41810000, 0x9C4, 0x41814000, 0x32,
                                     0x4180C000, 0x0,   0x41820000, 0xA};

            perform_hint_action(DEFAULT_VIDEO_ENCODE_HINT_ID,
                    resource_values, sizeof(resource_values)/sizeof(resource_values[0]));
            ALOGD("Video Encode hint start");
            return HINT_HANDLED;
        }
    } else {
        // boost handle is intentionally not released, release_request(boost_handle);
        if (((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) ||
            ((strncmp(governor, SCHED_GOVERNOR, strlen(SCHED_GOVERNOR)) == 0) &&
                (strlen(governor) == strlen(SCHED_GOVERNOR)))) {
            undo_hint_action(DEFAULT_VIDEO_ENCODE_HINT_ID);
            ALOGD("Video Encode hint stop");
            return HINT_HANDLED;
        }
    }
    return HINT_NONE;
}

static int process_activity_launch_hint(void *data)
{
    // boost will timeout in 5s
    int duration = 5000;
    ATRACE_BEGIN("launch");
    if (sustained_performance_mode || vr_mode) {
        ATRACE_END();
        return HINT_HANDLED;
    }

    ALOGD("LAUNCH HINT: %s", data ? "ON" : "OFF");
    if (data && launch_mode == 0) {
        launch_handle = process_boost(launch_handle, duration);
        if (launch_handle > 0) {
            launch_mode = 1;
            ALOGD("Activity launch hint handled");
            ATRACE_INT("launch_lock", 1);
            ATRACE_END();
            return HINT_HANDLED;
        } else {
            ATRACE_END();
            return HINT_NONE;
        }
    } else if (data == NULL  && launch_mode == 1) {
        release_request(launch_handle);
        ATRACE_INT("launch_lock", 0);
        launch_mode = 0;
        ATRACE_END();
        return HINT_HANDLED;
    }
    ATRACE_END();
    return HINT_NONE;
}

int power_hint_override(power_hint_t hint, void *data)
{
    int ret_val = HINT_NONE;
    switch(hint) {
#ifdef EXTRA_POWERHAL_HINTS
        case POWER_HINT_CAM_PREVIEW:
            ret_val = process_cam_preview_hint(data);
            break;
#endif
        case POWER_HINT_VIDEO_ENCODE:
            ret_val = process_video_encode_hint(data);
            break;
        case POWER_HINT_LAUNCH:
            ret_val = process_activity_launch_hint(data);
            break;
        default:
            break;
    }
    return ret_val;
}

int set_interactive_override(int on)
{
    return HINT_HANDLED; /* Don't excecute this code path, not in use */
    char governor[80];

    if (get_scaling_governor(governor, sizeof(governor)) == -1) {
        ALOGE("Can't obtain scaling governor.");

        return HINT_NONE;
    }

    if (!on) {
        /* Display off */
        if ((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
            (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) {
            int resource_values[] = {}; /* dummy node */
            if (!display_hint_sent) {
                perform_hint_action(DISPLAY_STATE_HINT_ID,
                resource_values, sizeof(resource_values)/sizeof(resource_values[0]));
                display_hint_sent = 1;
                ALOGV("Display Off hint start");
                return HINT_HANDLED;
            }
        }
    } else {
        /* Display on */
        if ((strncmp(governor, INTERACTIVE_GOVERNOR, strlen(INTERACTIVE_GOVERNOR)) == 0) &&
            (strlen(governor) == strlen(INTERACTIVE_GOVERNOR))) {
            undo_hint_action(DISPLAY_STATE_HINT_ID);
            display_hint_sent = 0;
            ALOGV("Display Off hint stop");
            return HINT_HANDLED;
        }
    }
    return HINT_NONE;
}