/*
// 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.
*/
#ifdef TARGET_HAS_MULTIPLE_DISPLAY
#include <HwcTrace.h>
#include <binder/IServiceManager.h>
#include <Hwcomposer.h>
#include <DisplayAnalyzer.h>
#include <ExternalDevice.h>
#endif

#include <MultiDisplayObserver.h>

namespace android {
namespace intel {

#ifdef TARGET_HAS_MULTIPLE_DISPLAY

////// MultiDisplayCallback

MultiDisplayCallback::MultiDisplayCallback(MultiDisplayObserver *dispObserver)
    : mDispObserver(dispObserver),
      mVideoState(MDS_VIDEO_STATE_UNKNOWN)
{
}

MultiDisplayCallback::~MultiDisplayCallback()
{
    CTRACE();
    mDispObserver = NULL;
}

status_t MultiDisplayCallback::blankSecondaryDisplay(bool blank)
{
    ITRACE("blank: %d", blank);
    mDispObserver->blankSecondaryDisplay(blank);
    return NO_ERROR;
}

status_t MultiDisplayCallback::updateVideoState(int sessionId, MDS_VIDEO_STATE state)
{
    mVideoState = state;
    ITRACE("state: %d", state);
    mDispObserver->updateVideoState(sessionId, state);
    return NO_ERROR;
}

status_t MultiDisplayCallback::setHdmiTiming(const MDSHdmiTiming& timing)
{
    mDispObserver->setHdmiTiming(timing);
    return NO_ERROR;
}

status_t MultiDisplayCallback::updateInputState(bool state)
{
    //ITRACE("input state: %d", state);
    mDispObserver->updateInputState(state);
    return NO_ERROR;
}

status_t MultiDisplayCallback::setHdmiScalingType(MDS_SCALING_TYPE type)
{
    ITRACE("scaling type: %d", type);
    // Merrifield doesn't implement this API
    return INVALID_OPERATION;
}

status_t MultiDisplayCallback::setHdmiOverscan(int hValue, int vValue)
{
    ITRACE("oversacn compensation, h: %d v: %d", hValue, vValue);
    // Merrifield doesn't implement this API
    return INVALID_OPERATION;
}

////// MultiDisplayObserver

MultiDisplayObserver::MultiDisplayObserver()
    : mMDSCbRegistrar(NULL),
      mMDSInfoProvider(NULL),
      mMDSConnObserver(NULL),
      mMDSDecoderConfig(NULL),
      mMDSCallback(NULL),
      mLock(),
      mCondition(),
      mThreadLoopCount(0),
      mDeviceConnected(false),
      mExternalHdmiTiming(false),
      mInitialized(false)
{
    CTRACE();
}

MultiDisplayObserver::~MultiDisplayObserver()
{
    WARN_IF_NOT_DEINIT();
}

bool MultiDisplayObserver::isMDSRunning()
{
    // Check if Multi Display service is running
    sp<IServiceManager> sm = defaultServiceManager();
    if (sm == NULL) {
        ETRACE("fail to get service manager!");
        return false;
    }

    sp<IBinder> service = sm->checkService(String16(INTEL_MDS_SERVICE_NAME));
    if (service == NULL) {
        VTRACE("fail to get MultiDisplay service!");
        return false;
    }

    return true;
}

bool MultiDisplayObserver::initMDSClient()
{
    sp<IServiceManager> sm = defaultServiceManager();
    if (sm == NULL) {
        ETRACE("Fail to get service manager");
        return false;
    }
    sp<IMDService> mds = interface_cast<IMDService>(
            sm->getService(String16(INTEL_MDS_SERVICE_NAME)));
    if (mds == NULL) {
        ETRACE("Fail to get MDS service");
        return false;
    }
    mMDSCbRegistrar = mds->getCallbackRegistrar();
    if (mMDSCbRegistrar.get() == NULL) {
        ETRACE("failed to create mds base Client");
        return false;
    }

    mMDSCallback = new MultiDisplayCallback(this);
    if (mMDSCallback.get() == NULL) {
        ETRACE("failed to create MultiDisplayCallback");
        deinitMDSClient();
        return false;
    }
    mMDSInfoProvider = mds->getInfoProvider();
    if (mMDSInfoProvider.get() == NULL) {
        ETRACE("failed to create mds video Client");
        return false;
    }

    mMDSConnObserver = mds->getConnectionObserver();
    if (mMDSConnObserver.get() == NULL) {
        ETRACE("failed to create mds video Client");
        return false;
    }
    mMDSDecoderConfig = mds->getDecoderConfig();
    if (mMDSDecoderConfig.get() == NULL) {
        ETRACE("failed to create mds decoder Client");
        return false;
    }

    status_t ret = mMDSCbRegistrar->registerCallback(mMDSCallback);
    if (ret != NO_ERROR) {
        ETRACE("failed to register callback");
        deinitMDSClient();
        return false;
    }

    Drm *drm = Hwcomposer::getInstance().getDrm();
    mDeviceConnected = drm->isConnected(IDisplayDevice::DEVICE_EXTERNAL);
    ITRACE("MDS client is initialized");
    return true;
}

void MultiDisplayObserver::deinitMDSClient()
{
    if (mMDSCallback.get() && mMDSCbRegistrar.get()) {
        mMDSCbRegistrar->unregisterCallback(mMDSCallback);
    }

    mDeviceConnected = false;
    mMDSCbRegistrar = NULL;
    mMDSInfoProvider = NULL;
    mMDSCallback = NULL;
    mMDSConnObserver = NULL;
    mMDSDecoderConfig = NULL;
}

bool MultiDisplayObserver::initMDSClientAsync()
{
    if (mThread.get()) {
        WTRACE("working thread has been already created.");
        return true;
    }

    mThread = new MDSClientInitThread(this);
    if (mThread.get() == NULL) {
        ETRACE("failed to create MDS client init thread");
        return false;
    }
    mThreadLoopCount = 0;
    // TODO: check return value
    mThread->run("MDSClientInitThread", PRIORITY_URGENT_DISPLAY);
    return true;
}

bool MultiDisplayObserver::initialize()
{
    bool ret = true;
    Mutex::Autolock _l(mLock);

    if (mInitialized) {
        WTRACE("display observer has been initialized");
        return true;
    }

    // initialize MDS client once. This should succeed if MDS service starts
    // before surfaceflinger service is started.
    // if surface flinger runs first, MDS client will be initialized asynchronously in
    // a working thread
    if (isMDSRunning()) {
        if (!initMDSClient()) {
            ETRACE("failed to initialize MDS client");
            // FIXME: NOT a common case for system server crash.
            // Start a working thread to initialize MDS client if exception happens
            ret = initMDSClientAsync();
        }
    } else {
        ret = initMDSClientAsync();
    }

    mInitialized = true;
    return ret;
}

void MultiDisplayObserver::deinitialize()
{
    sp<MDSClientInitThread> detachedThread;
    do {
        Mutex::Autolock _l(mLock);

        if (mThread.get()) {
            mCondition.signal();
            detachedThread = mThread;
            mThread = NULL;
        }
        mThreadLoopCount = 0;
        deinitMDSClient();
        mInitialized = false;
    } while (0);

    if (detachedThread.get()) {
        detachedThread->requestExitAndWait();
        detachedThread = NULL;
    }
}

bool MultiDisplayObserver::threadLoop()
{
    Mutex::Autolock _l(mLock);

    // try to create MDS client in the working thread
    // multiple delayed attempts are made until MDS service starts.

    // Return false if MDS service is running or loop limit is reached
    // such that thread becomes inactive.
    if (isMDSRunning()) {
        if (!initMDSClient()) {
            ETRACE("failed to initialize MDS client");
        }
        return false;
    }

    if (mThreadLoopCount++ > THREAD_LOOP_BOUND) {
        ETRACE("failed to initialize MDS client, loop limit reached");
        return false;
    }

    status_t err = mCondition.waitRelative(mLock, milliseconds(THREAD_LOOP_DELAY));
    if (err != -ETIMEDOUT) {
        ITRACE("thread is interrupted");
        return false;
    }

    return true; // keep trying
}


status_t MultiDisplayObserver::blankSecondaryDisplay(bool blank)
{
    // blank secondary display
    Hwcomposer::getInstance().getDisplayAnalyzer()->postBlankEvent(blank);
    return 0;
}

status_t MultiDisplayObserver::updateVideoState(int sessionId, MDS_VIDEO_STATE state)
{
    Hwcomposer::getInstance().getDisplayAnalyzer()->postVideoEvent(
        sessionId, (int)state);
    return 0;
}

status_t MultiDisplayObserver::setHdmiTiming(const MDSHdmiTiming& timing)
{
    drmModeModeInfo mode;
    mode.hdisplay = timing.width;
    mode.vdisplay = timing.height;
    mode.vrefresh = timing.refresh;
    mode.flags = timing.flags;
    ITRACE("timing to set: %dx%d@%dHz", timing.width, timing.height, timing.refresh);
    ExternalDevice *dev =
        (ExternalDevice *)Hwcomposer::getInstance().getDisplayDevice(HWC_DISPLAY_EXTERNAL);
    if (dev) {
        dev->setDrmMode(mode);
    }

    mExternalHdmiTiming = true;
    return 0;
}

status_t MultiDisplayObserver::updateInputState(bool active)
{
    Hwcomposer::getInstance().getDisplayAnalyzer()->postInputEvent(active);
    return 0;
}


/// Public interfaces

status_t MultiDisplayObserver::notifyHotPlug( bool connected)
{
    {
        // lock scope
        Mutex::Autolock _l(mLock);
        if (mMDSConnObserver.get() == NULL) {
            return NO_INIT;
        }

        if (connected == mDeviceConnected) {
            WTRACE("hotplug event ignored");
            return NO_ERROR;
        }

        // clear it after externel device is disconnected
        if (!connected) mExternalHdmiTiming = false;

        mDeviceConnected = connected;
    }
    return mMDSConnObserver->updateHdmiConnectionStatus(connected);
}

status_t MultiDisplayObserver::getVideoSourceInfo(int sessionID, VideoSourceInfo* info)
{
    Mutex::Autolock _l(mLock);
    if (mMDSInfoProvider.get() == NULL) {
        return NO_INIT;
    }

    if (info == NULL) {
        ETRACE("invalid parameter");
        return UNKNOWN_ERROR;
    }

    MDSVideoSourceInfo videoInfo;
    memset(&videoInfo, 0, sizeof(MDSVideoSourceInfo));
    status_t ret = mMDSInfoProvider->getVideoSourceInfo(sessionID, &videoInfo);
    if (ret == NO_ERROR) {
        info->width     = videoInfo.displayW;
        info->height    = videoInfo.displayH;
        info->frameRate = videoInfo.frameRate;
        info->isProtected = videoInfo.isProtected;
        VTRACE("Video Session[%d] source info: %dx%d@%d", sessionID,
                info->width, info->height, info->frameRate);
    }
    return ret;
}

int MultiDisplayObserver::getVideoSessionNumber()
{
    Mutex::Autolock _l(mLock);
    if (mMDSInfoProvider.get() == NULL) {
        return 0;
    }

    return mMDSInfoProvider->getVideoSessionNumber();
}

bool MultiDisplayObserver::isExternalDeviceTimingFixed() const
{
    Mutex::Autolock _l(mLock);
    return mExternalHdmiTiming;
}

status_t MultiDisplayObserver::notifyWidiConnectionStatus( bool connected)
{
    Mutex::Autolock _l(mLock);
    if (mMDSConnObserver.get() == NULL) {
        return NO_INIT;
    }
    return mMDSConnObserver->updateWidiConnectionStatus(connected);
}

status_t MultiDisplayObserver::setDecoderOutputResolution(
        int sessionID,
        int32_t width, int32_t height,
        int32_t offX, int32_t offY,
        int32_t bufWidth, int32_t bufHeight)
{
    Mutex::Autolock _l(mLock);
    if (mMDSDecoderConfig.get() == NULL) {
        return NO_INIT;
    }
    if (width <= 0 || height <= 0 ||
            offX < 0 || offY < 0 ||
            bufWidth <= 0 || bufHeight <= 0) {
        ETRACE(" Invalid parameter: %dx%d, %dx%d, %dx%d", width, height, offX, offY, bufWidth, bufHeight);
        return UNKNOWN_ERROR;
    }

    status_t ret = mMDSDecoderConfig->setDecoderOutputResolution(sessionID, width, height, offX, offY, bufWidth, bufHeight);
    if (ret == NO_ERROR) {
        ITRACE("Video Session[%d] output resolution %dx%d ", sessionID, width, height);
    }
    return ret;
}


#endif //TARGET_HAS_MULTIPLE_DISPLAY

} // namespace intel
} // namespace android