#ifndef __PHONE_DEVICE_H__
#define __PHONE_DEVICE_H__

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <map>
#include <atomic>
#include <filesystem>
#include <thread>

#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraError.h>
#include <camera/NdkCameraDevice.h>
#include <camera/NdkCameraMetadataTags.h>
#include <media/NdkImageReader.h>
#include <Client/Device.h>
#include <string>

#include "camera2/Camera2Helper.h"
#include "camera2/ndkcamera.h"

#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "error", __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "debug", __VA_ARGS__))

#if 0
void BitmapToMat2(JNIEnv *env, jobject& bitmap, cv::Mat& mat, jboolean needUnPremultiplyAlpha) {
	AndroidBitmapInfo info;
	void *pixels = 0;
	cv::Mat &dst = mat;

	// try {
		ALOGD("nBitmapToMat");
		CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
		CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
				  info.format == ANDROID_BITMAP_FORMAT_RGB_565);
		CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
		CV_Assert(pixels);
		dst.create(info.height, info.width, CV_8UC4);
		if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
			ALOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4");
			cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
			if (needUnPremultiplyAlpha) cvtColor(tmp, dst, cv::COLOR_mRGBA2RGBA);
			else tmp.copyTo(dst);
		} else {
			// info.format == ANDROID_BITMAP_FORMAT_RGB_565
			ALOGD("nBitmapToMat: RGB_565 -> CV_8UC4");
			cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
			cvtColor(tmp, dst, cv::COLOR_BGR5652RGBA);
		}
		AndroidBitmap_unlockPixels(env, bitmap);
		return;
	/*
	} catch (const cv::Exception &e) {
		AndroidBitmap_unlockPixels(env, bitmap);
		LOGE("nBitmapToMat catched cv::Exception: %s", e.what());
		jclass je = env->FindClass("org/opencv/core/CvException");
		if (!je) je = env->FindClass("java/lang/Exception");
		env->ThrowNew(je, e.what());
		return;
	} catch (...) {
		AndroidBitmap_unlockPixels(env, bitmap);
		LOGE("nBitmapToMat catched unknown exception (...)");
		jclass je = env->FindClass("java/lang/Exception");
		env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
		return;
	}
	 */
}

void BitmapToMat(JNIEnv *env, jobject& bitmap, cv::Mat& mat) {
	BitmapToMat2(env, bitmap, mat, false);
}

void MatToBitmap2
		(JNIEnv *env, cv::Mat& mat, jobject& bitmap, jboolean needPremultiplyAlpha) {
	AndroidBitmapInfo info;
	void *pixels = 0;
	cv::Mat &src = mat;

	// try {
		ALOGD("nMatToBitmap");
		CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
		CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
				  info.format == ANDROID_BITMAP_FORMAT_RGB_565);
		CV_Assert(src.dims == 2 && info.height == (uint32_t) src.rows &&
				  info.width == (uint32_t) src.cols);
		CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
		CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
		CV_Assert(pixels);
		if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
			cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
			if (src.type() == CV_8UC1) {
				ALOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
				cvtColor(src, tmp, cv::COLOR_GRAY2RGBA);
			} else if (src.type() == CV_8UC3) {
				ALOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
				cvtColor(src, tmp, cv::COLOR_RGB2RGBA);
			} else if (src.type() == CV_8UC4) {
				ALOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
				if (needPremultiplyAlpha)
					cvtColor(src, tmp, cv::COLOR_RGBA2mRGBA);
				else
					src.copyTo(tmp);
			}
		} else {
			// info.format == ANDROID_BITMAP_FORMAT_RGB_565
			cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
			if (src.type() == CV_8UC1) {
				ALOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
				cvtColor(src, tmp, cv::COLOR_GRAY2BGR565);
			} else if (src.type() == CV_8UC3) {
				ALOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
				cvtColor(src, tmp, cv::COLOR_RGB2BGR565);
			} else if (src.type() == CV_8UC4) {
				ALOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
				cvtColor(src, tmp, cv::COLOR_RGBA2BGR565);
			}
		}
		AndroidBitmap_unlockPixels(env, bitmap);
		return;
		/*
	} catch (const cv::Exception &e) {
		AndroidBitmap_unlockPixels(env, bitmap);
		LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
		jclass je = env->FindClass("org/opencv/core/CvException");
		if (!je) je = env->FindClass("java/lang/Exception");
		env->ThrowNew(je, e.what());
		return;
	} catch (...) {
		AndroidBitmap_unlockPixels(env, bitmap);
		LOGE("nMatToBitmap catched unknown exception (...)");
		jclass je = env->FindClass("java/lang/Exception");
		env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
		return;
	}*/

}

void MatToBitmap(JNIEnv *env, cv::Mat& mat, jobject& bitmap) {
	MatToBitmap2(env, mat, bitmap, false);
}
#endif

class CPhoneDevice : public IDevice
{
public:

	class CPhoneCamera : public NdkCamera {
	public:
		CPhoneCamera(CPhoneDevice* dev, int32_t width, int32_t height, const NdkCamera::CAMERA_PARAMS& params);
		virtual ~CPhoneCamera();
		virtual bool on_image(cv::Mat& rgb);
		virtual void on_error(const std::string& msg);
		virtual void onDisconnected(ACameraDevice* device);

	protected:
		CPhoneDevice* m_dev;
	};

    struct TIMER_CONTEXT
    {
        CPhoneDevice* device;
        unsigned int timerType;
        unsigned long times;
		void* data;
		unsigned long expectedTimes;
        unsigned long uid;
    };

	CPhoneDevice(JavaVM* vm, jobject service, const std::string& appPath, unsigned int netId, unsigned int versionCode);
	virtual ~CPhoneDevice();

    virtual void SetListener(IListener* listener);
	virtual void SetRecognizationCfg(const CFG_RECOGNIZATION* pRecognizationCfg);
	virtual bool BindNetwork(int sock);
	virtual bool SelfTest(std::string& result);
	virtual bool UpdateTime(time_t ts);
	virtual bool UpdateSchedules();
	virtual bool QuerySystemProperties(map<string, string>& properties);
	virtual bool InstallAPP(const std::string& path, unsigned int delayedTime);
    virtual bool Reboot(int resetType);
	virtual bool EnableGPS(bool enabled);
    virtual float QueryBattaryVoltage(int timesForAvg, bool* isCharging);
	virtual bool RequestPosition();
    virtual timer_uid_t RegisterHeartbeat(unsigned int timerType, unsigned int timeout, time_t tsForNextPhoto);
	virtual bool TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector<OSD_INFO>& osds, const std::string& path);
	virtual bool CloseCamera();
    virtual timer_uid_t RegisterTimer(unsigned int timerType, unsigned int timeout, void* data, unsigned long times = 0);
    virtual bool UnregisterTimer(timer_uid_t uid);
    virtual unsigned long RequestWakelock(unsigned long timeout);
    virtual bool ReleaseWakelock(unsigned long wakelock);

    bool GetNextScheduleItem(uint32_t tsBasedZero, uint32_t scheduleTime, vector<uint32_t>& items);

	void UpdatePosition(double lon, double lat, double radius, time_t ts);
	bool OnVideoReady(bool result, const char* path, unsigned int photoId);
	void UpdateSignalLevel(int signalLevel);
	void UpdateTfCardPath(const std::string& tfCardPath)
	{
		m_tfCardPath = tfCardPath;
	}
    void SetBuildTime(time_t buildTime)
    {
        mBuildTime = buildTime;
    }
	void UpdateSimcard(const std::string& simcard);

protected:

	std::string GetFileName() const;
	std::string GetVersion() const;

	bool SendBroadcastMessage(std::string action, int value);
	// bool MatchCaptureSizeRequest(ACameraManager *cameraManager, const char *selectedCameraId, unsigned int width, unsigned int height, uint32_t cameraOrientation_,

    inline bool TakePhotoCb(bool res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime, const std::vector<IDevice::RECOG_OBJECT>& objects) const
    {
        if (m_listener != NULL)
        {
            return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime, objects);
        }

        return false;
    }

    inline bool TakePhotoCb(bool res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime) const
    {
        if (m_listener != NULL)
        {
            std::vector<IDevice::RECOG_OBJECT> objects;
            return m_listener->OnPhotoTaken(res, photoInfo, path, photoTime, objects);
        }

        return false;
    }

	void QueryPowerInfo(std::map<std::string, std::string>& powerInfo);
    std::string QueryCpuTemperature();

	bool OnImageReady(cv::Mat& mat);
	void onError(const std::string& msg);
	void onDisconnected(ACameraDevice* device);

	void CloseCamera2(CPhoneCamera* camera, unsigned int photoId, bool turnOffOtg);

	void TurnOnCameraPower(JNIEnv* env);
	void TurnOffCameraPower(JNIEnv* env);

	void TurnOnOtg(JNIEnv* env);
	void TurnOffOtg(JNIEnv* env);

    static void handleSignal(int sig, siginfo_t *si, void *uc);
    bool RegisterHandlerForSignal(int sig);
	void static handleTimer(union sigval v);
    void handleTimerImpl(TIMER_CONTEXT* context);
	void static handleRebootTimer(union sigval v);
    // void handleRebootTimerImpl();
	void RestartApp(int rebootType, long timeout);

	int QueryBatteryVoltage(int retries);

protected:

	std::mutex m_devLocker;

    JavaVM* m_vm;
    jobject m_javaService;
	std::string m_appPath;
	std::string m_tfCardPath;

	jmethodID mRegisterHeartbeatMid;
	jmethodID mUpdateCaptureScheduleMid;
	jmethodID mUpdateTimeMid;
	jmethodID mStartRecordingMid;

	jmethodID mRequestWakelockMid;
	jmethodID mReleaseWakelockMid;

	jmethodID mGetSystemInfoMid;

	jmethodID mRebootMid;
	jmethodID mInstallAppMid;
	jmethodID mEnableGpsMid;
	jmethodID mRequestPositionMid;

	std::string mPath;
    IDevice::PHOTO_INFO mPhotoInfo;
	vector<IDevice::OSD_INFO> mOsds;
    IListener* m_listener;
	const CFG_RECOGNIZATION* m_pRecognizationCfg;
    unsigned int mNetId;
	unsigned int mVersionCode;
	time_t mBuildTime;

	atomic_ulong m_timerUidFeed;
    atomic_ulong m_wakelockIdFeed;
	std::map<IDevice::timer_uid_t, TIMER_CONTEXT*> mTimers;

	mutable CPhoneCamera*	mCamera;

    time_t mHeartbeatStartTime;
    unsigned int mHeartbeatDuration;

	long mCameraPowerCount;
	long mOtgCount;
	std::thread m_threadClose;

	int m_signalLevel;
	time_t m_signalLevelUpdateTime;

	std::string m_simcard;

};


#endif // __PHONE_DEVICE_H__