package com.sysolve.androidthings.cameracar;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;

import com.google.android.things.pio.Gpio;
import com.google.android.things.pio.GpioCallback;
import com.google.android.things.pio.PeripheralManager;
import com.google.android.things.pio.Pwm;
import com.google.android.things.pio.UartDevice;
import com.google.android.things.pio.UartDeviceCallback;
import com.sysolve.androidthings.contrib.driver.ws2812b.Ws2812b;
import com.sysolve.androidthings.utils.BoardSpec;
import com.sysolve.androidthings.utils.HttpServer;
import com.sysolve.androidthings.utils.Unzip;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import static android.content.ContentValues.TAG;

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    Gpio mButtonGpio = null;

    private Pwm pwmLeft;
    private Pwm pwmRight;

    Gpio left0;
    Gpio left1;
    Gpio right0;
    Gpio right1;
    Gpio stop;

    private static double PWM_FREQUENCY_HZ = 100000;

    private static final int SPEED_NORMAL = 100;
    private static final int SPEED_TURNING_INSIDE = 70;
    private static final int SPEED_TURNING_OUTSIDE = 250;

    int buttonPressedTimes = 0;

    private HandlerThread mInputThread;
    private Handler mInputHandler;

    Gpio echoDeviceEn;

    private Runnable mTransferUartRunnable = new Runnable() {
        @Override
        public void run() {
            transferUartData();
        }
    };


    Ws2812b mWs2812b;

    private int mFrame = 0;
    private Handler mHandler;
    private HandlerThread mPioThread;

    private static final int FRAME_DELAY_MS = 30; // 24fps

    int[] mLedColors = new int[] {
            Color.BLUE, Color.BLUE, Color.BLUE,
            Color.BLUE, Color.BLUE, Color.BLUE,
            Color.BLUE, Color.BLUE, Color.BLUE,
            Color.BLUE, //Color.RED, Color.RED,
            //Color.RED, Color.RED, Color.RED,
            //Color.RED, Color.RED, Color.RED,
            //Color.RED, Color.RED, Color.RED,
            //Color.RED, Color.GREEN, Color.BLUE,
            //Color.RED, Color.GREEN, Color.BLUE,
            //Color.RED, Color.GREEN, Color.BLUE
    };

    boolean showMoreColor = false;

    private Runnable mAnimateRunnable = new Runnable() {
        final float[] hsv = {1f, 1f, 1f};

        @Override
        public void run() {
            if (showMoreColor) {
                try {
                    for (int i = 0; i < mLedColors.length; i++) { // Assigns gradient colors.
                        int n = (i + mFrame) % (mLedColors.length * 3);
                        hsv[0] = n * 360.f / (mLedColors.length * 3);
                        mLedColors[i] = Color.HSVToColor(0, hsv);
                    }
                    mWs2812b.write(mLedColors);
                    mFrame = (mFrame + 1) % (mLedColors.length * 3);
                } catch (IOException e) {
                    Log.e(TAG, "Error while writing to LED strip", e);
                }
                mHandler.postDelayed(mAnimateRunnable, FRAME_DELAY_MS);
            } else {
                try {
                    for (int i = 0; i < mLedColors.length; i++) { // Assigns gradient colors.
                        mLedColors[i] = color;
                    }
                    mWs2812b.write(mLedColors);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                mHandler.postDelayed(mAnimateRunnable, 500);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        try {
            mWs2812b = new Ws2812b(BoardSpec.getSpiBus());
        } catch (IOException e) {
            // couldn't configure the device...
        }

        try {
            mWs2812b.write(mLedColors);
        } catch (IOException e) {
            // error setting LEDs
        }

        initCamera();

        /*
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int c = mLedColors[0];
        for (int i=0;i<mLedColors.length-1;++i) {
            mLedColors[i] = mLedColors[i+1];
        }
        */

        mPioThread = new HandlerThread("pioThread");
        mPioThread.start();

        mHandler = new Handler(mPioThread.getLooper());

        mHandler.postDelayed(mAnimateRunnable, 3000);


        try {
            File f = new File(HttpServer.wwwRoot);
            File index = new File(HttpServer.wwwRoot+"index.html");
            if (!f.exists() || !index.exists()) {
                f.mkdirs();
                Unzip.unzip(getResources().getAssets().open("www.zip"), HttpServer.wwwRoot);
                Log.i(TAG, "Unzip www");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Log.i(TAG, "Start");

        PeripheralManager manager = PeripheralManager.getInstance();
        Log.i(TAG, "PeripheralManager");

        try {
            Log.i(TAG, "Reg GPIOs");
            //Create a background looper thread for I/O
            mInputThread = new HandlerThread("InputThread");
            mInputThread.start();
            mInputHandler = new Handler(mInputThread.getLooper());

            // Attempt to access the UART device
            try {
                openUart(manager, BoardSpec.getUartName(), 9600);
                // Read any initially buffered data
                mInputHandler.post(mTransferUartRunnable);
            } catch (IOException e) {
                Log.e(TAG, "Unable to open UART device", e);
            }

            Log.i(TAG, "Reg GPIOs");

            pwmLeft = manager.openPwm(BoardSpec.getInstance().getPwm(0));
            pwmRight = manager.openPwm(BoardSpec.getInstance().getPwm(1));

            echoDeviceEn = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_18));
            echoDeviceEn.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);

            left0 = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_29));
            left1 = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_31));
            left0.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
            left1.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
            right0 = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_35));
            right1 = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_37));
            right0.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
            right1.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);

            stop = manager.openGpio(BoardSpec.getInstance().getGpioPin(BoardSpec.PIN_16));
            stop.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);

            pwmLeft.setPwmDutyCycle(50);       //percent, 0-100
            pwmLeft.setPwmFrequencyHz(PWM_FREQUENCY_HZ);
            pwmLeft.setEnabled(true);

            pwmRight.setPwmDutyCycle(50);
            pwmRight.setPwmFrequencyHz(PWM_FREQUENCY_HZ);
            pwmRight.setEnabled(true);

            //define a button for counter
            mButtonGpio = manager.openGpio(BoardSpec.getGoogleSampleButtonGpioPin());
            mButtonGpio.setDirection(Gpio.DIRECTION_IN);
            mButtonGpio.setEdgeTriggerType(Gpio.EDGE_FALLING);
            mButtonGpio.registerGpioCallback(new GpioCallback() {
                @Override
                public boolean onGpioEdge(Gpio gpio) {
                    buttonPressedTimes++;

                    if (status_back || status_forward || status==3) {
                        Log.i(TAG, "GPIO changed, button pressed " + buttonPressedTimes);
                        emStop();
                        status = 0;
                    } else if (status==0) {
                        color(Color.BLACK);
                        status = 1;
                    } else if (status==1) {
                        color(Color.BLUE);
                        status = 2;
                    } else if (status==1) {
                        color(Color.GREEN);
                        status = 2;
                    } else if (status==2) {
                        moreColor();
                        status = 3;
                        goForward();
                    }

                    // Return true to continue listening to events
                    return true;
                }
            });

            Log.i(TAG, "Start HTTP Server");
            new HttpServer(this);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    private DoorbellCamera mCamera;

    /**
     * A {@link Handler} for running Camera tasks in the background.
     */
    private Handler mCameraHandler;

    /**
     * Listener for new camera images.
     */
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    Log.d(TAG, "PhotoCamera OnImageAvailableListener");

                    Image image = reader.acquireLatestImage();
                    // get image bytes
                    ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
                    final byte[] imageBytes = new byte[imageBuf.remaining()];
                    imageBuf.get(imageBytes);
                    image.close();

                    onPictureTaken(imageBytes);
                }
            };

    public int picVersion = 0;

    /**
     * Handle image processing in Firebase and Cloud Vision.
     */
    private void onPictureTaken(final byte[] imageBytes) {
        Log.d(TAG, "PhotoCamera onPictureTaken");
        if (imageBytes != null) {
            String imageStr = Base64.encodeToString(imageBytes, Base64.NO_WRAP | Base64.URL_SAFE);
            Log.d(TAG, "imageBase64:"+imageStr);

            final Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
            if (bitmap != null) {
                File file=new File(HttpServer.wwwRoot + "pic.jpg");//将要保存图片的路径
                try {
                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
                    bos.flush();
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                picVersion++;
            }
        }
    }

    private HandlerThread mCameraThread;

    public void initCamera() {
        // We need permission to access the camera
        if (checkSelfPermission(Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            // A problem occurred auto-granting the permission
            Log.d(TAG, "PhotoCamera No permission");

            return;
        }

        //imageView = (ImageView)findViewById(R.id.imageView);

        DoorbellCamera.dumpFormatInfo(this);
        Log.d(TAG, "PhotoCamera inited");

        // Creates new handlers and associated threads for camera and networking operations.
        mCameraThread = new HandlerThread("CameraBackground");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());

        // Camera code is complicated, so we've shoved it all in this closet class for you.
        mCamera = DoorbellCamera.getInstance();
        mCamera.initializeCamera(this, mCameraHandler, mOnImageAvailableListener);

        /*imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i("takePicture", "click image to take picture");
                mCamera.takePicture();
            }
        });*/
    }

    public int status = 0;

    public void leftForward() {
        try {
            left0.setValue(false);
            left1.setValue(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void leftBack() {
        try {
            left0.setValue(true);
            left1.setValue(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void rightForward() {
        try {
            right0.setValue(false);
            right1.setValue(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void rightBack() {
        try {
            right0.setValue(true);
            right1.setValue(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void go() {
        try {
            stop.setValue(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void emStop() {
        status_forward = false;
        status_back = false;
        try {
            stop.setValue(false);
            lrdSpeed = 0;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    boolean mKeyPressed;

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        /*if (keyCode == KeyEvent.KEYCODE_A) { //29
            if (!mKeyPressed) {
                mKeyPressed = true;
                mResetHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY);
                mResetHandler.postDelayed(mResetRunnable, RESET_DELAY);
            }
            return true;
        }*/
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        /*if (keyCode == KeyEvent.KEYCODE_A) { //29
            mKeyPressed = false;
            // No effect if these have already run
            mResetHandler.removeCallbacks(mDisconnectRunnable);
            mResetHandler.removeCallbacks(mResetRunnable);
            return true;
        }*/
        return handleKeyCode(keyCode) || super.onKeyUp(keyCode, event);
    }

    // For testing commands to the motors via ADB.
    private boolean handleKeyCode(int keyCode) {
        //if (mCarController != null) {
        Log.i(TAG,"Key Press "+keyCode);
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_UP: //19
                    goForward();
                    return true;
                case KeyEvent.KEYCODE_DPAD_DOWN: //20
                    goBack();
                    return true;
                case KeyEvent.KEYCODE_DPAD_LEFT: //21
                    turnLeft();
                    return true;
                case KeyEvent.KEYCODE_DPAD_RIGHT: //22
                    turnRight();
                    return true;
                case KeyEvent.KEYCODE_DPAD_CENTER: //23
                    emStop();
                    return true;
            }
        //}
        return false;
    }

    public int lrdSpeed = 0;

    public void leftT() {
        //leftBack();
        //rightForward();
        lrdSpeed -= 1;
        try {
            pwmLeft.setPwmDutyCycle(speed + lrdSpeed);
            pwmRight.setPwmDutyCycle(speed - lrdSpeed);
        } catch (IOException e) {
            e.printStackTrace();
        }
        go();
    }

    public void noT() {
        lrdSpeed = 0;
        //leftForward();
        //rightBack();
        try {
            pwmLeft.setPwmDutyCycle(speed);
            pwmRight.setPwmDutyCycle(speed);
        } catch (IOException e) {
            e.printStackTrace();
        }
        go();
    }

    public void rightT() {
        lrdSpeed += 1;
        //leftForward();
        //rightBack();
        try {
            pwmLeft.setPwmDutyCycle(speed + lrdSpeed);
            pwmRight.setPwmDutyCycle(speed - lrdSpeed);
        } catch (IOException e) {
            e.printStackTrace();
        }
        go();
    }

    public void turnLeft() {
        //leftForward();
        //rightBack();
        try {
            pwmLeft.setPwmDutyCycle(speed*0.8);
            pwmRight.setPwmDutyCycle(speed*1.2);
        } catch (IOException e) {
            e.printStackTrace();
        }

        go();
    }

    public void turnRight() {
        //leftForward();
        //rightBack();
        try {
            pwmLeft.setPwmDutyCycle(speed*1.2);
            pwmRight.setPwmDutyCycle(speed*0.8);
        } catch (IOException e) {
            e.printStackTrace();
        }

        go();
    }

    boolean status_back;
    boolean status_forward;

    public void goBack() {
        status_forward = false;
        status_back = true;
        leftBack();
        rightBack();
        go();
    }

    public void goForward() {
        try {
            pwmLeft.setPwmDutyCycle(speed + lrdSpeed);
            pwmRight.setPwmDutyCycle(speed - lrdSpeed);
        } catch (IOException e) {
            e.printStackTrace();
        }

        status_back = false;
        status_forward = true;
        leftForward();
        rightForward();
        go();
    }

    double speed = 30;

    public void speedDecrease() {
        if (speed>10) {
            speed = speed - 5;
            try {
                pwmLeft.setPwmDutyCycle(speed + lrdSpeed);
                pwmRight.setPwmDutyCycle(speed - lrdSpeed);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void speedAdd() {
        if (speed<100) {
            speed = speed + 5;
            try {
                pwmLeft.setPwmDutyCycle(speed + lrdSpeed);
                pwmRight.setPwmDutyCycle(speed - lrdSpeed);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private UartDevice echoDevice;

    private void openUart(PeripheralManager service, String name, int baudRate) throws IOException {
        echoDevice = service.openUartDevice(name);
        // Configure the UART
        echoDevice.setBaudrate(baudRate);
        echoDevice.setDataSize(8);
        echoDevice.setParity(UartDevice.PARITY_NONE);
        echoDevice.setStopBits(1);

        Log.i(TAG, "Open UART device");

        echoDevice.registerUartDeviceCallback(mInputHandler, mCallback);
    }

    /**
     * Close the UART device connection, if it exists
     */
    private void closeUart() throws IOException {
        if (echoDevice != null) {
            echoDevice.unregisterUartDeviceCallback(mCallback);
            try {
                echoDevice.close();
            } finally {
                echoDevice = null;
            }
        }
    }

    /**
     * Callback invoked when UART receives new incoming data.
     */
    private UartDeviceCallback mCallback = new UartDeviceCallback() {
        @Override
        public boolean onUartDeviceDataAvailable(UartDevice uart) {
            // Queue up a data transfer
            transferUartData();
            //Continue listening for more interrupts
            return true;
        }

        @Override
        public void onUartDeviceError(UartDevice uart, int error) {
            Log.w(TAG, uart + ": Error event " + error);
        }
    };

    public static int CHUNK_SIZE = 128;

    public boolean echo_prefix;
    public int echoHigh;
    public int echoLow;
    public int echoCRC;

    //private static byte[] prefix = "IoT".getBytes();
    /**
     * Loop over the contents of the UART RX buffer, transferring each
     * one back to the TX buffer to create a loopback service.
     *
     * Potentially long-running operation. Call from a worker thread.
     */
    private void transferUartData() {
        if (echoDevice != null) {
            // Loop until there is no more data in the RX buffer.
            try {
                byte[] buffer = new byte[CHUNK_SIZE];
                int read;

                while ((read = echoDevice.read(buffer, buffer.length)) > 0) {
                    //mLoopbackDevice.write(prefix, prefix.length);
                    //mLoopbackDevice.write(buffer, read);
                    for (int i=0;i<read;) {
                        if (buffer[i]==(byte)0xFF) {
                            if (i+3<read) {
                                if ((( (0|buffer[i]) + (0|buffer[i+1]) + (0|buffer[i+2]) ) & 0xFF) == (0| buffer[i+3])) {
                                    int distance = (0|buffer[i + 1]) * 256 + (0|buffer[i+2]);
                                    if (status_forward &&  distance>0 && distance<100)
                                        emStop();
                                    Log.i(TAG, "distance="+distance+"mm");
                                } else {
                                    Log.i(TAG, "distance CRC Error");
                                }
                                i+=4;
                            } else {
                                break;
                            }
                        } else {
                            i++;
                        }
                    }
                }
            } catch (IOException e) {
                Log.w(TAG, "Unable to transfer data over UART", e);
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // Close the LED strip when finished:
        try {
            mWs2812b.close();
        } catch (IOException e) {
            // error closing LED strip
        }

        try {
            left0.close();
            left1.close();
            right0.close();
            right1.close();
            echoDeviceEn.close();
            stop.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            pwmLeft.close();
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
        }
        try {
            pwmRight.close();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }

        if (mButtonGpio!=null) try {
            mButtonGpio.close();
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
        }

        // Terminate the worker thread
        if (mInputThread != null) {
            mInputThread.quitSafely();
        }

        // Attempt to close the UART device
        try {
            closeUart();
        } catch (IOException e) {
            Log.e(TAG, "Error closing UART device:", e);
        }

        mCamera.shutDown();

        mCameraThread.quitSafely();
    }

    int color;

    public void color(int color) {
        showMoreColor = false;
       this.color = color;
    }

    public void moreColor() {
        showMoreColor = true;
    }

    public void superSound(boolean on) {
        try {
            if (on) {
                echoDeviceEn.setValue(false);
            } else {
                echoDeviceEn.setValue(true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    Handler handler = new Handler();

    public void takePicture() {

            handler.post(new Runnable() {
                @Override
                public void run() {
                    try {
                        Log.i(TAG, "PhotoCamera take Picture");
                        mCamera.takePicture();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
    }
}