/*
 * Copyright (C) 2017 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_NDEBUG 0
#define LOG_TAG "CameraZSLTests"

#include <gtest/gtest.h>

#include <android/content/AttributionSourceState.h>
#include <android/hardware/ICameraService.h>
#include <binder/ProcessState.h>
#include <camera/Camera.h>
#include <camera/CameraMetadata.h>
#include <camera/CameraParameters.h>
#include <camera/CameraUtils.h>
#include <camera/StringUtils.h>
#include <gui/Flags.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <utils/Errors.h>
#include <utils/Log.h>

using namespace android;
using namespace android::hardware;

class CameraZSLTests : public ::testing::Test,
    public ::android::hardware::BnCameraClient {
protected:

    CameraZSLTests() : numCameras(0), mPreviewBufferCount(0),
    mAutoFocusMessage(false), mSnapshotNotification(false) {}

    //Gtest interface
    void SetUp() override;
    void TearDown() override;

    //CameraClient interface
    void notifyCallback(int32_t msgType, int32_t, int32_t) override;
    void dataCallback(int32_t msgType, const sp<IMemory>&,
            camera_frame_metadata_t *) override;
    void dataCallbackTimestamp(nsecs_t, int32_t,
            const sp<IMemory>&) override {};
    void recordingFrameHandleCallbackTimestamp(nsecs_t,
            native_handle_t*) override {};
    void recordingFrameHandleCallbackTimestampBatch(
            const std::vector<nsecs_t>&,
            const std::vector<native_handle_t*>&) override {};

    status_t waitForPreviewStart();
    status_t waitForEvent(Mutex &mutex, Condition &condition, bool &flag);

    mutable Mutex mPreviewLock;
    mutable Condition mPreviewCondition;
    mutable Mutex mAutoFocusLock;
    mutable Condition mAutoFocusCondition;
    mutable Mutex mSnapshotLock;
    mutable Condition mSnapshotCondition;

    int32_t numCameras;
    size_t mPreviewBufferCount;
    sp<ICameraService> mCameraService;
    sp<SurfaceComposerClient> mComposerClient;
    bool mAutoFocusMessage;
    bool mSnapshotNotification;
    static const int32_t kPreviewThreshold  = 8;
    static const nsecs_t kPreviewTimeout    = 5000000000;  // 5 [s.]
    static const nsecs_t kEventTimeout      = 10000000000; // 10 [s.]
};

void CameraZSLTests::SetUp() {
    ::android::binder::Status rc;
    ProcessState::self()->startThreadPool();
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("media.camera"));
    mCameraService = interface_cast<ICameraService>(binder);
    AttributionSourceState clientAttribution;
    clientAttribution.deviceId = kDefaultDeviceId;
    rc = mCameraService->getNumberOfCameras(
            hardware::ICameraService::CAMERA_TYPE_ALL, clientAttribution, /*devicePolicy*/0,
            &numCameras);
    EXPECT_TRUE(rc.isOk());

    mComposerClient = new SurfaceComposerClient;
    ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
}

void CameraZSLTests::TearDown() {
    mCameraService.clear();
    mComposerClient->dispose();
}

void CameraZSLTests::notifyCallback(int32_t msgType, int32_t,
        int32_t) {
    if (CAMERA_MSG_FOCUS == msgType) {
        Mutex::Autolock l(mAutoFocusLock);
        mAutoFocusMessage = true;
        mAutoFocusCondition.broadcast();
    } else {
        ALOGV("%s: msgType: %d", __FUNCTION__, msgType);
    }
};

void CameraZSLTests::dataCallback(int32_t msgType, const sp<IMemory>& /*data*/,
        camera_frame_metadata_t *) {
    switch (msgType) {
    case CAMERA_MSG_PREVIEW_FRAME: {
        Mutex::Autolock l(mPreviewLock);
        mPreviewBufferCount++;
        mPreviewCondition.broadcast();
        break;
    }
    case CAMERA_MSG_COMPRESSED_IMAGE: {
        Mutex::Autolock l(mSnapshotLock);
        mSnapshotNotification = true;
        //TODO: Add checks on incoming Jpeg
        mSnapshotCondition.broadcast();
        break;
    }
    default:
        ALOGV("%s: msgType: %d", __FUNCTION__, msgType);
    }
}

status_t CameraZSLTests::waitForPreviewStart() {
    status_t rc = NO_ERROR;
    Mutex::Autolock l(mPreviewLock);
    mPreviewBufferCount = 0;

    while (mPreviewBufferCount < kPreviewThreshold) {
        rc = mPreviewCondition.waitRelative(mPreviewLock,
                kPreviewTimeout);
        if (NO_ERROR != rc) {
            break;
        }
    }

    return rc;
}

status_t CameraZSLTests::waitForEvent(Mutex &mutex,
        Condition &condition, bool &flag) {
    status_t rc = NO_ERROR;
    Mutex::Autolock l(mutex);
    flag = false;

    while (!flag) {
        rc = condition.waitRelative(mutex,
                kEventTimeout);
        if (NO_ERROR != rc) {
            break;
        }
    }

    return rc;
}

TEST_F(CameraZSLTests, TestAllPictureSizes) {
    ::android::binder::Status rc;

    for (int32_t cameraId = 0; cameraId < numCameras; cameraId++) {
        sp<Surface> previewSurface;
        sp<SurfaceControl> surfaceControl;
        sp<ICamera> cameraDevice;

        std::string cameraIdStr = std::to_string(cameraId);
        bool isSupported = false;
        rc = mCameraService->supportsCameraApi(cameraIdStr,
                hardware::ICameraService::API_VERSION_1, &isSupported);
        EXPECT_TRUE(rc.isOk());

        // We only care about camera Camera1 ZSL support.
        if (!isSupported) {
            continue;
        }

        CameraMetadata metadata;
        AttributionSourceState clientAttribution;
        clientAttribution.deviceId = kDefaultDeviceId;
        rc = mCameraService->getCameraCharacteristics(cameraIdStr,
                /*targetSdkVersion*/__ANDROID_API_FUTURE__, /*overrideToPortrait*/false,
                clientAttribution, /*devicePolicy*/0, &metadata);
        if (!rc.isOk()) {
            // The test is relevant only for cameras with Hal 3.x
            // support.
            continue;
        }
        EXPECT_FALSE(metadata.isEmpty());
        camera_metadata_entry_t availableCapabilities =
                metadata.find(ANDROID_REQUEST_AVAILABLE_CAPABILITIES);
        EXPECT_TRUE(0 < availableCapabilities.count);
        bool isReprocessSupported = false;
        const uint8_t *caps = availableCapabilities.data.u8;
        for (size_t i = 0; i < availableCapabilities.count; i++) {
            if (ANDROID_REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING ==
                caps[i]) {
                isReprocessSupported = true;
                break;
            }
        }
        if (!isReprocessSupported) {
            // ZSL relies on this feature
            continue;
        }

        clientAttribution.uid = hardware::ICameraService::USE_CALLING_UID;
        clientAttribution.pid = hardware::ICameraService::USE_CALLING_PID;
        clientAttribution.packageName = "ZSLTest";
        rc = mCameraService->connect(this, cameraId,
                /*targetSdkVersion*/__ANDROID_API_FUTURE__,
                /*overrideToPortrait*/false, /*forceSlowJpegMode*/false, clientAttribution,
                /*devicePolicy*/0, &cameraDevice);
        EXPECT_TRUE(rc.isOk());

        CameraParameters params(cameraDevice->getParameters());

        String8 focusModes(params.get(
                CameraParameters::KEY_SUPPORTED_FOCUS_MODES));
        bool isAFSupported = false;
        const char *focusMode = nullptr;
        if (focusModes.contains(CameraParameters::FOCUS_MODE_AUTO)) {
            // If supported 'auto' should be set by default
            isAFSupported = true;
        } else if (focusModes.contains(
                CameraParameters::FOCUS_MODE_CONTINUOUS_PICTURE)) {
            isAFSupported = true;
            focusMode = CameraParameters::FOCUS_MODE_CONTINUOUS_PICTURE;
        } else if (focusModes.contains(
                CameraParameters::FOCUS_MODE_CONTINUOUS_VIDEO)) {
            isAFSupported = true;
            focusMode = CameraParameters::FOCUS_MODE_CONTINUOUS_VIDEO;
        } else if (focusModes.contains(CameraParameters::FOCUS_MODE_MACRO)) {
            isAFSupported = true;
            focusMode = CameraParameters::FOCUS_MODE_MACRO;
        }

        if (!isAFSupported) {
            // AF state is needed
            continue;
        }

        if (nullptr != focusMode) {
            params.set(CameraParameters::KEY_FOCUS_MODE, focusMode);
            ASSERT_EQ(NO_ERROR, cameraDevice->setParameters(params.flatten()));
        }

        int previewWidth, previewHeight;
        params.getPreviewSize(&previewWidth, &previewHeight);
        ASSERT_TRUE((0 < previewWidth) && (0 < previewHeight));

        surfaceControl = mComposerClient->createSurface(
                String8("Test Surface"),
                previewWidth, previewHeight,
                CameraParameters::previewFormatToEnum(
                        params.getPreviewFormat()),
                GRALLOC_USAGE_HW_RENDER);

        ASSERT_TRUE(nullptr != surfaceControl.get());
        ASSERT_TRUE(surfaceControl->isValid());

        SurfaceComposerClient::Transaction{}
                .setLayer(surfaceControl, 0x7fffffff)
                .show(surfaceControl)
                .apply();

        previewSurface = surfaceControl->getSurface();
        ASSERT_TRUE(previewSurface != NULL);
        ASSERT_EQ(NO_ERROR, cameraDevice->setPreviewTarget(previewSurface
#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
                                                                   ->getIGraphicBufferProducer()
#endif
                                                                   ));

        cameraDevice->setPreviewCallbackFlag(
                CAMERA_FRAME_CALLBACK_FLAG_CAMCORDER);

        Vector<Size> pictureSizes;
        params.getSupportedPictureSizes(pictureSizes);
        for (size_t i = 0; i < pictureSizes.size(); i++) {
            params.setPictureSize(pictureSizes[i].width,
                    pictureSizes[i].height);
            ASSERT_EQ(NO_ERROR, cameraDevice->setParameters(params.flatten()));
            ASSERT_EQ(NO_ERROR, cameraDevice->startPreview());
            ASSERT_EQ(NO_ERROR, waitForPreviewStart());

            ASSERT_EQ(NO_ERROR, cameraDevice->autoFocus());
            ASSERT_EQ(NO_ERROR, waitForEvent(mAutoFocusLock,
                    mAutoFocusCondition, mAutoFocusMessage));

            ASSERT_EQ(NO_ERROR,
                    cameraDevice->takePicture(CAMERA_MSG_COMPRESSED_IMAGE));
            ASSERT_EQ(NO_ERROR, waitForEvent(mSnapshotLock, mSnapshotCondition,
                    mSnapshotNotification));
        }

        cameraDevice->stopPreview();
        rc = cameraDevice->disconnect();
        EXPECT_TRUE(rc.isOk());
    }
}
