package com.ck.camera.demo;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;

import com.sun.jna.ptr.ByteByReference;
import com.sun.jna.ptr.DoubleByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.Pointer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import CameraVision.CKSDK;
import CameraVision.CKSDK.*;

import static android.os.SystemClock.elapsedRealtimeNanos;


public class Camera {

        class OpenedDev {
            private UsbDevice m_Device;
            private UsbDeviceConnection m_Connection;

            public OpenedDev(UsbDevice device) {
                m_Device = device;
            }

            public boolean Open() {
                UsbManager manager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
                m_Connection = manager.openDevice(m_Device);
                return m_Connection != null;
            }

        public void Close() {
            if (m_Connection != null) {
                m_Connection.close();
                m_Connection = null;
            }
            m_Device = null;

        }

        public short GetVID() { return (short)m_Device.getVendorId(); }
        public short GetPID() { return (short)m_Device.getProductId(); }
        public int GetFd() {
            return m_Connection.getFileDescriptor();
        }
        public String GetPath() { return m_Device.getDeviceName(); }

    }

    private Context mContext;
    private CameraCallback mEnumerateCallback;
    private List<UsbDevice> mDeviceList;
    private List<OpenedDev> mOpenedDevList;
    private final String ACTION_USB_PERMISSION = "com.ck.camera.USB_PERMISSION";

    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    boolean ok = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);

                    if (mDeviceList.contains(device)) {
                        mDeviceList.remove(device);

                        if (ok) {
                            OpenedDev dev = new OpenedDev(device);
                            if (dev.Open()) {
                                mOpenedDevList.add(dev);
                            }
                        }

                        if (mDeviceList.isEmpty())
                            EnumerateDeviceDone();
                    }
                }
            }
            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                System.out.printf("======= new device in\n");
                CameraScanDevice();
                }
            if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
                System.out.printf("===== new device out\n");
            }



        }
    };

    public Camera(Context context)
    {
        mContext = context;
        //GLR2Y = new GLRGB2YUV(mContext, 800, 600);

    }

    public String GetDevPath(int index)
    {
        String str = "no device";
        if(mOpenedDevList == null) return str;

        int nDev = mOpenedDevList.size();
        if(mOpenedDevList.isEmpty() || index >= nDev )
        {
            return str;
        }
        int i = 0;
        for (OpenedDev dev: mOpenedDevList)
        {

             str = dev.GetPath();
              if(i == index) break;
            ++i;
        }

        return str;
    }

    private void CameraScanDevice()
    {
        if(mEnumerateCallback == null) return;

        UsbManager manager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
        Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

        assert (mOpenedDevList == null);
        mOpenedDevList = new ArrayList<OpenedDev>();
        mDeviceList = new ArrayList<UsbDevice>();
        while(deviceIterator.hasNext()){
            UsbDevice device = deviceIterator.next();

            int vid = device.getVendorId();
            int pid = device.getProductId();

            if (vid == 0xf266 && pid == 0x9A0A) {
                if (!manager.hasPermission(device) ) {
                    mDeviceList.add(device);
                    RequestUsbPermission(device);
                }
                else {
                    OpenedDev dev = new OpenedDev(device);
                    if (dev.Open() )
                        mOpenedDevList.add(dev);
                }
            }
        }

        if (mDeviceList.isEmpty()) {
            EnumerateDeviceDone();
        }

    }
    // 无需root
    public boolean CameraEnumerateDeviceEx2(CameraCallback EnumerateCallback)
    {
        if (mEnumerateCallback != null)
            return false;

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        intentFilter.addAction(ACTION_USB_PERMISSION);

        mContext.registerReceiver(mUsbReceiver, intentFilter);

        mEnumerateCallback = EnumerateCallback;

        CameraScanDevice();

        return true;
    }

    private void EnumerateDeviceDone()
    {
        assert (mDeviceList.isEmpty());
        assert (mEnumerateCallback != null);

        int nDev = mOpenedDevList.size();
        String[] paths = new String[nDev];
        if (nDev > 0) {
            int[] fds = new int[nDev];
            int[] pids = new int[nDev];

            int i = 0;
            for (OpenedDev dev: mOpenedDevList) {
                fds[i] = dev.GetFd();
                pids[i] = (((int)dev.GetVID() << 16) | (0x0000FFFF&(int)dev.GetPID()));
                paths[i] = dev.GetPath();
                System.out.printf("vid:%x, pid:%x, pids:%x\n", dev.GetVID(), dev.GetPID(), pids[i]);
                Log.i("Camera", paths[i]);
                ++i;
            }
            nDev = CameraEnumerateDeviceFromOpenedDevList(nDev, fds, pids, paths);
        }

        for (OpenedDev dev : mOpenedDevList) {
            dev.Close();
        }
        mOpenedDevList = null;

        mEnumerateCallback.onEnumerateDeviceCompleted(nDev);

        //mEnumerateCallback = null;
    }

    private boolean RequestUsbPermission(UsbDevice device) {
        UsbManager manager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        PendingIntent mPermissionIntent = PendingIntent.getBroadcast(mContext, 0,
            new Intent(ACTION_USB_PERMISSION), 0);

        manager.requestPermission(device, mPermissionIntent);
        return true;
    }

    // android4 root
    // android5+ 有usb访问权限
    public int CameraEnumerateDeviceEx()
    {
        IntByReference cameraNumRef = new IntByReference(0  );
        CKSDK.API.CameraEnumerateDevice(cameraNumRef);
        return cameraNumRef.getValue();
    }

    int CameraInitEx(int iDeviceIndex, int iParamLoadMode, int emTeam)
    {
        CameraUninit();

        PointerByReference hCameraRef = new PointerByReference();
        int status = CKSDK.API.CameraInitEx(hCameraRef, iDeviceIndex, iParamLoadMode, CKSDK.emSdkParameterTeam.PARAMETER_TEAM_DEFAULT.value);
        if(status != CKSDK.CameraSdkStatus.CAMERA_STATUS_SUCCESS.value)
            return -1;

        m_hCamera = hCameraRef.getValue();
        CKSDK.API.CameraSetAeState(m_hCamera, false);  // manual exposure
        CKSDK.API.CameraSetExposureTime(m_hCamera, 100.0 * 1000);  // exposure time is 10ms
        //CKSDK.API.CameraSetFrameSpeed(m_hCamera, 1);    // low speed
        CKSDK.API.CameraSetIspOutFormat(m_hCamera, CKSDK.emMediaType.CAMERA_MEDIA_TYPE_BGRA8.value);
        //CKSDK.API.CameraSetResolution(m_hCamera, emResolutionMode.IMAGEOUT_MODE_2048X1944.value);
        CKSDK.API.CameraPlay(m_hCamera);


        return 0;
    }

    public int CameraUninit()
    {
        if(CameraIsOpen())
           CKSDK.API.CameraUnInit(m_hCamera);
        //CameraStopRecord();
        return 0;
    }

    public Bitmap CameraCaptureImage()
    {
        if(!CameraIsOpen())
            return null;

        CKSDK.stImageInfo.ByReference imgInfoRef = new CKSDK.stImageInfo.ByReference();
        Pointer frameBuff = CKSDK.API.CameraGetImageBufferEx(m_hCamera, imgInfoRef, 1000);
        if(frameBuff != null)
            return null;
        Bitmap frameBitmap = Bitmap.createBitmap(frameBuff.getIntArray(0, imgInfoRef.iWidth*imgInfoRef.iHeight*3),
            imgInfoRef.iWidth, imgInfoRef.iHeight, Bitmap.Config.ARGB_8888);

        return frameBitmap;
    }
/*
    public int[] CameraCapture(SurfaceHolder surfaceHolder)
    {
        if(!CameraIsOpen())
            return new int[]{-1, 0, 0};

        Pointer frameBuff;
        CKSDK.stImageInfo.ByReference imgInfo = new CKSDK.stImageInfo.ByReference();


        //{
        //    PointerByReference hRawBuf = new PointerByReference();
        //    while (true) {
        //        int status = CKSDK.API.CameraGetRawImageBuffer(m_hCamera, hRawBuf, 1000);
        //        if (status == CameraSdkStatus.CAMERA_STATUS_SUCCESS.value)
        //             break;
        //    }
        //
        //    frameBuff = CKSDK.API.CameraGetImageInfo(m_hCamera, hRawBuf.getPointer(), imgInfo);
        //}


        long time = SystemClock.elapsedRealtimeNanos();
        while(true) {
            frameBuff = CKSDK.API.CameraGetImageBufferEx(m_hCamera, imgInfo, 1000);
            if (frameBuff == null)
                continue;
            else
                break;
        }

        long time1 = SystemClock.elapsedRealtimeNanos();

        int[] bitmapData = frameBuff.getIntArray(0, imgInfo.iWidth*imgInfo.iHeight);


        long time2 = SystemClock.elapsedRealtimeNanos();
        Bitmap frameBitmap = Bitmap.createBitmap(bitmapData,
                imgInfo.iWidth, imgInfo.iHeight, Bitmap.Config.RGB_565);
        //Matrix m = new Matrix();
        //m.setRotate(90, frameBitmap.getWidth()/2, frameBitmap.getHeight()/2);

        long time3 = SystemClock.elapsedRealtimeNanos();

        Canvas canvas = surfaceHolder.lockCanvas();
        //Rect imgRect = new Rect(0, 0, imgInfo.iWidth, imgInfo.iHeight);
        //Rect destRect = new Rect(0, 0, canvas.getWidth(), canvas.getHeight());
        //canvas.drawBitmap(frameBitmap, imgRect, destRect, null);  // new Paint(Paint.ANTI_ALIAS_FLAG)
        canvas.drawBitmap(frameBitmap, 0, 0, null);

        surfaceHolder.unlockCanvasAndPost(canvas);
        long time4 = SystemClock.elapsedRealtimeNanos();
        Log.i("CKCamera", "get image time:" + (time1 - time) + " array time:" + (time2 - time1)
                + " image time:" + (time3 - time2) +" display time:" + (time4 - time3));
        return new int[]{0, imgInfo.iWidth, imgInfo.iHeight};
    }
*/
    public synchronized int CameraCapture(Handler handler)
    {
        if(!CameraIsOpen())
            return -1;

        Pointer frameBuff;
        CKSDK.stImageInfo.ByReference imgInfo = new CKSDK.stImageInfo.ByReference();

        while(true) {
            frameBuff = CKSDK.API.CameraGetImageBufferEx(m_hCamera, imgInfo, 1000);
            if (frameBuff == null)
                continue;
            else
                break;
        }
//
//        ///////////////////////////////////////////////////////////////
//        if(m_mediaCodec != null) {
//            // 将图像数据放入编码队列
//            // put image data to input queue
//            while (true) {
//                // 获取输入缓冲buffer
//                // get the input buffer
//                int inputBufferId = m_mediaCodec.dequeueInputBuffer(-1);
//                if (inputBufferId < 0)
//                    continue;
//                // 填充图像数据到输入缓存buffer
//                // fill the image data to the input buffer
//                byte[] yuvout = null;
//                try {
//                    GLRGB2YUV GLR2Y = new GLRGB2YUV(mContext, 800, 600);
//                    GLR2Y.putRgbData(frameBuff.getByteArray(0, imgInfo.iWidth * imgInfo.iHeight * 4));
//                    yuvout = GLR2Y.startCoverImg();
//                    Log.d("CKCamera", "yuvout size:" + yuvout.length);
//                }
//                catch (Exception e)
//                {
//                    Log.e("CKCamera", "convert RGB to YUV error!");
//                    break;
//                }
//                ByteBuffer inputBuffer = m_mediaCodec.getInputBuffer(inputBufferId);
//                inputBuffer.put(yuvout);
//                long timestamp = (SystemClock.elapsedRealtime() - m_recordStartTime) * 1000;
//                m_mediaCodec.queueInputBuffer(inputBufferId, 0, yuvout.length, timestamp, m_mediaCodec.BUFFER_FLAG_CODEC_CONFIG);
//
//                break;
//            }
//
//            // 将编码后的图像数据读取出来
//            while(true) {
//                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//                int outputBufferId = m_mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
//                if (outputBufferId >= 0) {
//                    ByteBuffer outputBuffer = m_mediaCodec.getOutputBuffer(outputBufferId);
//                    MediaFormat bufferFormat = m_mediaCodec.getOutputFormat(outputBufferId);
//                    if(m_videoStream != null)
//                    {
//                        try {
//                            //Log.i("CKCamera", "Write video data size " + outputBuffer.array().length);
//                            byte[] videoBuff = null;
//                            if(outputBuffer.hasArray()) {
//                                videoBuff = outputBuffer.array();
//                            }
//                            else
//                            {
//                                videoBuff = new byte[bufferInfo.size];
//                                Log.i("Encoder", "encoder size " + bufferInfo.size);
//                                outputBuffer.get(videoBuff);
//                            }
//                            m_videoStream.write(videoBuff);
//                        }
//                        catch(IOException e)
//                        {
//                            e.printStackTrace();
//                            Log.e("CKCamera", "write video file failed.");
//                        }
//                    }
//                    m_mediaCodec.releaseOutputBuffer(outputBufferId, false);
//                }
//                else
//                    break;
//            }s
//        }

        int[] bitmapData = frameBuff.getIntArray(0, imgInfo.iWidth*imgInfo.iHeight);
        Message message = new Message();
        message.what = 3;
        message.arg1 = imgInfo.iWidth;
        message.arg2 = imgInfo.iHeight;
        message.obj = bitmapData;
        handler.sendMessage(message);

        return 0;
    }

    public int CameraGetFrameStatistic(CKSDK.tFrameStatistic.ByReference frameStatistic)
    {
        if(!CameraIsOpen())
            return -1;
        return CKSDK.API.CameraGetFrameStatistic(m_hCamera, frameStatistic);
    }

    public int CameraSetImageResolution(int index)
    {
        if(!CameraIsOpen())
            return -1;
        return CKSDK.API.CameraSetResolution(m_hCamera, index);
    }
    public int CameraSetOnceWB()
    {
        if(!CameraIsOpen())
            return -1;
        return CKSDK.API.CameraSetOnceWB(m_hCamera);
    }

    private int CameraEnumerateDeviceFromOpenedDevList(int DevNum, int[] fds, int[] pids, String[] paths)
    {
        IntByReference devNumRef = new IntByReference(0);
        int status = CKSDK.API.CameraEnumerateDeviceFromOpenedDevList(devNumRef, DevNum, fds, pids, paths);
        if(status != CameraSdkStatus.CAMERA_STATUS_SUCCESS.value)
            return status;
        return devNumRef.getValue();
    }

    public synchronized boolean CameraStartRecord()
    {

//        m_mediaFormat = MediaFormat.createVideoFormat("video/avc", 800, 600);
//        //m_mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 800);
//        //m_mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 600);
//        m_mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 800*600*10);
//        m_mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
//        m_mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
//        m_mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
//
//        try {
//            m_mediaCodec = MediaCodec.createEncoderByType("video/avc");
//        } catch (Exception e) {
//            Log.e("CKCamera", "Create codec failed");
//            e.printStackTrace();
//            return false;
//        }
//
//        try {
//            m_mediaCodec.configure(m_mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//        }
//        catch(IllegalArgumentException e)
//        {
//
//        }
//        catch(IllegalStateException e)
//        {
//            e.printStackTrace();
//            return false;
//        }
//        catch (MediaCodec.CryptoException e)
//        {
//
//        }
////        catch(MediaCodec.CodecException e)
////        {
////
////        }
//        //MediaFormat outputFormat = m_mediaCodec.getOutputFormat();
//
//        //m_mediaMuxer = new MediaMuxer("/mnt/sdcard", )
//
//        m_mediaCodec.start();
//        m_recordStartTime = SystemClock.elapsedRealtime();
//
//        // open file
//        try {
//            String fileName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/video.h264";
//            m_videoStream = new FileOutputStream(fileName, false);
//        }
//        catch (Exception e)
//        {
//            m_videoStream = null;
//        }

        return true;
    }

    public synchronized boolean CameraStopRecord()
    {
//        if(m_mediaCodec == null)
//            return false;
//
//        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//        while(true)
//        {
//            int outputBufferId = m_mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
//            if(outputBufferId < 0)
//                break;
//            if(outputBufferId == MediaCodec.BUFFER_FLAG_END_OF_STREAM)
//            {
//                // record is finish.
//                break;
//            }
//            else
//            {
//                ByteBuffer outputBuffer = m_mediaCodec.getOutputBuffer(outputBufferId);
//
//                MediaFormat bufferFormat = m_mediaCodec.getOutputFormat(outputBufferId);
//                m_mediaCodec.releaseOutputBuffer(outputBufferId, false);
//            }
//        }
//        m_mediaCodec.stop();
//        m_mediaCodec.release();
//        m_mediaCodec = null;
//
//        //////////////////////////////
//        try {
//            m_videoStream.close();
//        }
//        catch(Exception e)
//        {
//            Log.e("CKCamera", "Close video file failed!");
//        }
//        m_videoStream = null;

        return true;
    }

    public boolean CameraIsOpen()
    {
        return m_hCamera != null;
    }

    private Pointer m_hCamera;
    private int[] imgBuf = new int[4];
    private MediaCodec m_mediaCodec;
    private MediaFormat m_mediaFormat;
    private MediaMuxer m_mediaMuxer;
    private long m_recordStartTime;
    private File m_videoFile;
    private FileOutputStream m_videoStream;
}
