/*
// Copyright (c) 2014 Intel Corporation 
//
// 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 <HwcTrace.h>
#include <Drm.h>
#include <DrmConfig.h>
#include <Hwcomposer.h>
#include <ExternalDevice.h>

namespace android {
namespace intel {

ExternalDevice::ExternalDevice(Hwcomposer& hwc, DeviceControlFactory* controlFactory)
    : PhysicalDevice(DEVICE_EXTERNAL, hwc, controlFactory),
      mHdcpControl(NULL),
      mAbortModeSettingCond(),
      mPendingDrmMode(),
      mHotplugEventPending(false),
      mExpectedRefreshRate(0)
{
    CTRACE();
}

ExternalDevice::~ExternalDevice()
{
    CTRACE();
}

bool ExternalDevice::initialize()
{
    if (!PhysicalDevice::initialize()) {
        DEINIT_AND_RETURN_FALSE("failed to initialize physical device");
    }

    mHdcpControl = mControlFactory->createHdcpControl();
    if (!mHdcpControl) {
        DEINIT_AND_RETURN_FALSE("failed to create HDCP control");
    }

    mHotplugEventPending = false;
    if (mConnected) {
        mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
    }

    UeventObserver *observer = Hwcomposer::getInstance().getUeventObserver();
    if (observer) {
        observer->registerListener(
            DrmConfig::getHotplugString(),
            hotplugEventListener,
            this);
    } else {
        ETRACE("Uevent observer is NULL");
    }
    return true;
}

void ExternalDevice::deinitialize()
{
    // abort mode settings if it is in the middle
    mAbortModeSettingCond.signal();
    if (mThread.get()) {
        mThread->join();
        mThread = NULL;
    }

    if (mHdcpControl) {
        mHdcpControl->stopHdcp();
        delete mHdcpControl;
        mHdcpControl = 0;
    }

    mHotplugEventPending = false;
    PhysicalDevice::deinitialize();
}

bool ExternalDevice::setDrmMode(drmModeModeInfo& value)
{
    if (!mConnected) {
        WTRACE("external device is not connected");
        return false;
    }

    if (mThread.get()) {
        mThread->join();
        mThread = NULL;
    }

    Drm *drm = Hwcomposer::getInstance().getDrm();
    drmModeModeInfo mode;
    drm->getModeInfo(mType, mode);
    if (drm->isSameDrmMode(&value, &mode))
        return true;

    // any issue here by faking connection status?
    mConnected = false;
    mPendingDrmMode = value;

    // setting mode in a working thread
    mThread = new ModeSettingThread(this);
    if (!mThread.get()) {
        ETRACE("failed to create mode settings thread");
        return false;
    }

    mThread->run("ModeSettingsThread", PRIORITY_URGENT_DISPLAY);
    return true;
}

bool ExternalDevice::threadLoop()
{
    // one-time execution
    setDrmMode();
    return false;
}

void ExternalDevice::setDrmMode()
{
    ITRACE("start mode setting...");

    Drm *drm = Hwcomposer::getInstance().getDrm();

    mConnected = false;
    mHwc.hotplug(mType, false);

    {
        Mutex::Autolock lock(mLock);
        // TODO: make timeout value flexible, or wait until surface flinger
        // acknowledges hot unplug event.
        status_t err = mAbortModeSettingCond.waitRelative(mLock, milliseconds(20));
        if (err != -ETIMEDOUT) {
            ITRACE("Mode settings is interrupted");
            mHwc.hotplug(mType, true);
            return;
        }
    }

    // TODO: potential threading issue with onHotplug callback
    mHdcpControl->stopHdcp();
    if (!drm->setDrmMode(mType, mPendingDrmMode)) {
        ETRACE("failed to set Drm mode");
        mHwc.hotplug(mType, true);
        return;
    }

    if (!PhysicalDevice::updateDisplayConfigs()) {
        ETRACE("failed to update display configs");
        mHwc.hotplug(mType, true);
        return;
    }
    mConnected = true;
    mHotplugEventPending = true;
    // delay sending hotplug event until HDCP is authenticated
    if (mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this) == false) {
        ETRACE("startHdcpAsync() failed; HDCP is not enabled");
        mHotplugEventPending = false;
        mHwc.hotplug(mType, true);
    }
    mExpectedRefreshRate = 0;
}


void ExternalDevice::HdcpLinkStatusListener(bool success, void *userData)
{
    if (userData == NULL) {
        return;
    }

    ExternalDevice *p = (ExternalDevice*)userData;
    p->HdcpLinkStatusListener(success);
}

void ExternalDevice::HdcpLinkStatusListener(bool success)
{
    if (!success) {
        ETRACE("HDCP is not authenticated, disabling dynamic vsync");
        mHwc.getVsyncManager()->enableDynamicVsync(false);
    }

    if (mHotplugEventPending) {
        DTRACE("HDCP authentication status %d, sending hotplug event...", success);
        mHwc.hotplug(mType, mConnected);
        mHotplugEventPending = false;
    }

    if (success) {
        ITRACE("HDCP authenticated, enabling dynamic vsync");
        mHwc.getVsyncManager()->enableDynamicVsync(true);
    }
}

void ExternalDevice::hotplugEventListener(void *data)
{
    ExternalDevice *pThis = (ExternalDevice*)data;
    if (pThis) {
        pThis->hotplugListener();
    }
}

void ExternalDevice::hotplugListener()
{
    bool ret;

    CTRACE();

    // abort mode settings if it is in the middle
    mAbortModeSettingCond.signal();

    // remember the current connection status before detection
    bool connected = mConnected;

    // detect display configs
    ret = detectDisplayConfigs();
    if (ret == false) {
        ETRACE("failed to detect display config");
        return;
    }

    ITRACE("hotpug event: %d", mConnected);

    if (connected == mConnected) {
        WTRACE("same connection status detected, hotplug event ignored");
        return;
    }

    if (mConnected == false) {
        mHotplugEventPending = false;
        mHwc.getVsyncManager()->resetVsyncSource();
        mHdcpControl->stopHdcp();
        mHwc.hotplug(mType, mConnected);
    } else {
        DTRACE("start HDCP asynchronously...");
         // delay sending hotplug event till HDCP is authenticated.
        mHotplugEventPending = true;
        ret = mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
        if (ret == false) {
            ETRACE("failed to start HDCP");
            mHotplugEventPending = false;
            mHwc.hotplug(mType, mConnected);
        }
    }
    mActiveDisplayConfig = 0;
}

int ExternalDevice::getRefreshRate()
{
    Drm *drm = Hwcomposer::getInstance().getDrm();
    drmModeModeInfo mode;
    if (!drm->getModeInfo(IDisplayDevice::DEVICE_EXTERNAL, mode))
        return 0;
    return mode.vrefresh;
}

void ExternalDevice::setRefreshRate(int hz)
{
    RETURN_VOID_IF_NOT_INIT();

    ITRACE("setting refresh rate to %d", hz);

    if (mBlank) {
        WTRACE("external device is blank");
        return;
    }

    Drm *drm = Hwcomposer::getInstance().getDrm();
    drmModeModeInfo mode;
    if (!drm->getModeInfo(IDisplayDevice::DEVICE_EXTERNAL, mode))
        return;

    if (hz == 0 && (mode.type & DRM_MODE_TYPE_PREFERRED))
        return;

    if (hz == (int)mode.vrefresh)
        return;

    if (mExpectedRefreshRate != 0 &&
            mExpectedRefreshRate == hz && mHotplugEventPending) {
        ITRACE("Ignore a new refresh setting event because there is a same event is handling");
        return;
    }
    mExpectedRefreshRate = hz;

    ITRACE("changing refresh rate from %d to %d", mode.vrefresh, hz);

    mHwc.getVsyncManager()->enableDynamicVsync(false);

    mHdcpControl->stopHdcp();

    drm->setRefreshRate(IDisplayDevice::DEVICE_EXTERNAL, hz);

    mHotplugEventPending = false;
    mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
    mHwc.getVsyncManager()->enableDynamicVsync(true);
}

int ExternalDevice::getActiveConfig()
{
    if (!mConnected) {
        return 0;
    }
    return mActiveDisplayConfig;
}

bool ExternalDevice::setActiveConfig(int index)
{
    if (!mConnected) {
        if (index == 0)
            return true;
        else
            return false;
    }

    // for now we will only permit the frequency change.  In the future
    // we may need to set mode as well.
    if (index >= 0 && index < static_cast<int>(mDisplayConfigs.size())) {
        DisplayConfig *config = mDisplayConfigs.itemAt(index);
        setRefreshRate(config->getRefreshRate());
        mActiveDisplayConfig = index;
        return true;
    } else {
        return false;
    }
    return true;
}

} // namespace intel
} // namespace android