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

#define LOG_TAG "ProCamera2Client"
#define ATRACE_TAG ATRACE_TAG_CAMERA
//#define LOG_NDEBUG 0

#include <utils/Log.h>
#include <utils/Trace.h>

#include <cutils/properties.h>
#include <gui/Surface.h>
#include <gui/Surface.h>
#include "camera2/Parameters.h"
#include "ProCamera2Client.h"
#include "camera2/ProFrameProcessor.h"
#include "CameraDeviceBase.h"

namespace android {
using namespace camera2;

// Interface used by CameraService

ProCamera2Client::ProCamera2Client(const sp<CameraService>& cameraService,
                                   const sp<IProCameraCallbacks>& remoteCallback,
                                   const String16& clientPackageName,
                                   int cameraId,
                                   int cameraFacing,
                                   int clientPid,
                                   uid_t clientUid,
                                   int servicePid) :
    Camera2ClientBase(cameraService, remoteCallback, clientPackageName,
                cameraId, cameraFacing, clientPid, clientUid, servicePid)
{
    ATRACE_CALL();
    ALOGI("ProCamera %d: Opened", cameraId);

    mExclusiveLock = false;
}

status_t ProCamera2Client::initialize(camera_module_t *module)
{
    ATRACE_CALL();
    status_t res;

    res = Camera2ClientBase::initialize(module);
    if (res != OK) {
        return res;
    }

    String8 threadName;
    mFrameProcessor = new ProFrameProcessor(mDevice);
    threadName = String8::format("PC2-%d-FrameProc", mCameraId);
    mFrameProcessor->run(threadName.string());

    mFrameProcessor->registerListener(FRAME_PROCESSOR_LISTENER_MIN_ID,
                                      FRAME_PROCESSOR_LISTENER_MAX_ID,
                                      /*listener*/this);

    return OK;
}

ProCamera2Client::~ProCamera2Client() {
}

status_t ProCamera2Client::exclusiveTryLock() {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);
    SharedCameraCallbacks::Lock l(mSharedCameraCallbacks);

    if (!mDevice.get()) return PERMISSION_DENIED;

    if (!mExclusiveLock) {
        mExclusiveLock = true;

        if (mRemoteCallback != NULL) {
            mRemoteCallback->onLockStatusChanged(
                              IProCameraCallbacks::LOCK_ACQUIRED);
        }

        ALOGV("%s: exclusive lock acquired", __FUNCTION__);

        return OK;
    }

    // TODO: have a PERMISSION_DENIED case for when someone else owns the lock

    // don't allow recursive locking
    ALOGW("%s: exclusive lock already exists - recursive locking is not"
          "allowed", __FUNCTION__);

    return ALREADY_EXISTS;
}

status_t ProCamera2Client::exclusiveLock() {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);
    SharedCameraCallbacks::Lock l(mSharedCameraCallbacks);

    if (!mDevice.get()) return PERMISSION_DENIED;

    /**
     * TODO: this should asynchronously 'wait' until the lock becomes available
     * if another client already has an exclusive lock.
     *
     * once we have proper sharing support this will need to do
     * more than just return immediately
     */
    if (!mExclusiveLock) {
        mExclusiveLock = true;

        if (mRemoteCallback != NULL) {
            mRemoteCallback->onLockStatusChanged(IProCameraCallbacks::LOCK_ACQUIRED);
        }

        ALOGV("%s: exclusive lock acquired", __FUNCTION__);

        return OK;
    }

    // don't allow recursive locking
    ALOGW("%s: exclusive lock already exists - recursive locking is not allowed"
                                                                , __FUNCTION__);
    return ALREADY_EXISTS;
}

status_t ProCamera2Client::exclusiveUnlock() {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);
    SharedCameraCallbacks::Lock l(mSharedCameraCallbacks);

    // don't allow unlocking if we have no lock
    if (!mExclusiveLock) {
        ALOGW("%s: cannot unlock, no lock was held in the first place",
              __FUNCTION__);
        return BAD_VALUE;
    }

    mExclusiveLock = false;
    if (mRemoteCallback != NULL ) {
        mRemoteCallback->onLockStatusChanged(
                                       IProCameraCallbacks::LOCK_RELEASED);
    }
    ALOGV("%s: exclusive lock released", __FUNCTION__);

    return OK;
}

bool ProCamera2Client::hasExclusiveLock() {
    Mutex::Autolock icl(mBinderSerializationLock);
    return mExclusiveLock;
}

void ProCamera2Client::onExclusiveLockStolen() {
    ALOGV("%s: ProClient lost exclusivity (id %d)",
          __FUNCTION__, mCameraId);

    Mutex::Autolock icl(mBinderSerializationLock);
    SharedCameraCallbacks::Lock l(mSharedCameraCallbacks);

    if (mExclusiveLock && mRemoteCallback.get() != NULL) {
        mRemoteCallback->onLockStatusChanged(
                                       IProCameraCallbacks::LOCK_STOLEN);
    }

    mExclusiveLock = false;

    //TODO: we should not need to detach the device, merely reset it.
    detachDevice();
}

status_t ProCamera2Client::submitRequest(camera_metadata_t* request,
                                         bool streaming) {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;

    if (!mExclusiveLock) {
        return PERMISSION_DENIED;
    }

    CameraMetadata metadata(request);

    if (streaming) {
        return mDevice->setStreamingRequest(metadata);
    } else {
        return mDevice->capture(metadata);
    }

    // unreachable. thx gcc for a useless warning
    return OK;
}

status_t ProCamera2Client::cancelRequest(int requestId) {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;

    if (!mExclusiveLock) {
        return PERMISSION_DENIED;
    }

    // TODO: implement
    ALOGE("%s: not fully implemented yet", __FUNCTION__);
    return INVALID_OPERATION;
}

status_t ProCamera2Client::deleteStream(int streamId) {
    ATRACE_CALL();
    ALOGV("%s (streamId = 0x%x)", __FUNCTION__, streamId);

    status_t res;
    if ( (res = checkPid(__FUNCTION__) ) != OK) return res;

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;
    mDevice->clearStreamingRequest();

    status_t code;
    if ((code = mDevice->waitUntilDrained()) != OK) {
        ALOGE("%s: waitUntilDrained failed with code 0x%x", __FUNCTION__, code);
    }

    return mDevice->deleteStream(streamId);
}

status_t ProCamera2Client::createStream(int width, int height, int format,
                      const sp<IGraphicBufferProducer>& bufferProducer,
                      /*out*/
                      int* streamId)
{
    if (streamId) {
        *streamId = -1;
    }

    ATRACE_CALL();
    ALOGV("%s (w = %d, h = %d, f = 0x%x)", __FUNCTION__, width, height, format);

    status_t res;
    if ( (res = checkPid(__FUNCTION__) ) != OK) return res;

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;

    sp<IBinder> binder;
    sp<ANativeWindow> window;
    if (bufferProducer != 0) {
        binder = bufferProducer->asBinder();
        window = new Surface(bufferProducer);
    }

    return mDevice->createStream(window, width, height, format, /*size*/1,
                                 streamId);
}

// Create a request object from a template.
// -- Caller owns the newly allocated metadata
status_t ProCamera2Client::createDefaultRequest(int templateId,
                             /*out*/
                              camera_metadata** request)
{
    ATRACE_CALL();
    ALOGV("%s (templateId = 0x%x)", __FUNCTION__, templateId);

    if (request) {
        *request = NULL;
    }

    status_t res;
    if ( (res = checkPid(__FUNCTION__) ) != OK) return res;

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;

    CameraMetadata metadata;
    if ( (res = mDevice->createDefaultRequest(templateId, &metadata) ) == OK) {
        *request = metadata.release();
    }

    return res;
}

status_t ProCamera2Client::getCameraInfo(int cameraId,
                                         /*out*/
                                         camera_metadata** info)
{
    if (cameraId != mCameraId) {
        return INVALID_OPERATION;
    }

    Mutex::Autolock icl(mBinderSerializationLock);

    if (!mDevice.get()) return DEAD_OBJECT;

    CameraMetadata deviceInfo = mDevice->info();
    *info = deviceInfo.release();

    return OK;
}

status_t ProCamera2Client::dump(int fd, const Vector<String16>& args) {
    String8 result;
    result.appendFormat("ProCamera2Client[%d] (%p) PID: %d, dump:\n",
            mCameraId,
            getRemoteCallback()->asBinder().get(),
            mClientPid);
    result.append("  State: ");

    // TODO: print dynamic/request section from most recent requests
    mFrameProcessor->dump(fd, args);

    return dumpDevice(fd, args);
}

// IProCameraUser interface

void ProCamera2Client::detachDevice() {
    if (mDevice == 0) return;

    ALOGV("Camera %d: Stopping processors", mCameraId);

    mFrameProcessor->removeListener(FRAME_PROCESSOR_LISTENER_MIN_ID,
                                    FRAME_PROCESSOR_LISTENER_MAX_ID,
                                    /*listener*/this);
    mFrameProcessor->requestExit();
    ALOGV("Camera %d: Waiting for threads", mCameraId);
    mFrameProcessor->join();
    ALOGV("Camera %d: Disconnecting device", mCameraId);

    // WORKAROUND: HAL refuses to disconnect while there's streams in flight
    {
        mDevice->clearStreamingRequest();

        status_t code;
        if ((code = mDevice->waitUntilDrained()) != OK) {
            ALOGE("%s: waitUntilDrained failed with code 0x%x", __FUNCTION__,
                  code);
        }
    }

    Camera2ClientBase::detachDevice();
}

/** Device-related methods */
void ProCamera2Client::onFrameAvailable(int32_t frameId,
                                        const CameraMetadata& frame) {
    ATRACE_CALL();
    ALOGV("%s", __FUNCTION__);

    Mutex::Autolock icl(mBinderSerializationLock);
    SharedCameraCallbacks::Lock l(mSharedCameraCallbacks);

    if (mRemoteCallback != NULL) {
        CameraMetadata tmp(frame);
        camera_metadata_t* meta = tmp.release();
        ALOGV("%s: meta = %p ", __FUNCTION__, meta);
        mRemoteCallback->onResultReceived(frameId, meta);
        tmp.acquire(meta);
    }

}

} // namespace android
