package com.handset.serialportsensor.presenter;

import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import com.blankj.utilcode.util.SPUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.handset.serialportlib.CRCUtils;
import com.handset.serialportsensor.R;
import com.handset.serialportsensor.activity.SPKey;
import com.handset.serialportsensor.bean.RawData;
import com.handset.serialportlib.ByteUtils;
import com.handset.serialportlib.SerialPort;
import com.handset.serialportsensor.utils.SplitUtils;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashSet;

import org.joda.time.DateTime;

public class SurfacePresenter {
    private static final String logTag = "HANDSET-JE002-EX";

    private final static String POWER_EN_PATH = "/sys/class/peijian_dev/function/PeijianPowerEn";
    private final static String POGO_WAKE = "/sys/class/peijian_dev/function/pogo_wake";

    private final static String SERIAL_PATH = "dev/ttyHSL1";
    private final static Integer SERIAL_BAUD_RATE = 38400;
    private final static String mDateFormat = "HH:mm:ss:SSS";
    private SurfaceContract.ISerialView mSerialView;

    private LinkedHashSet<String> mSendHistory;

    private int mCheckDigit;
    private int mDataBits;
    private int mStopBit;

    private boolean isHexReceive;
    private boolean isHexSend;
    private boolean isShowSend;
    private boolean isShowTime;

    private SerialPort mSerialPort;
    private boolean isInterrupted;
    private Disposable mReceiveDisposable;
    private ObservableEmitter<String> mEmitter;
    private Disposable mSendDisposable;

    public SurfacePresenter(SurfaceContract.ISerialView view) {
        mSerialView = view;
        isHexReceive = true;
        isHexSend = SPUtils.getInstance().getBoolean(SPKey.SETTING_SEND_TYPE, (true));
        isShowSend = SPUtils.getInstance().getBoolean(SPKey.SETTING_RECEIVE_SHOW_SEND, (true));
        isShowTime = SPUtils.getInstance().getBoolean(SPKey.SETTING_RECEIVE_SHOW_TIME, (true));

        try {
            mSendHistory = (LinkedHashSet<String>) SPUtils.getInstance().getStringSet(SPKey.SEND_HISTORY, new LinkedHashSet<>((30)));
        } catch (ClassCastException e) {
            e.printStackTrace();
            mSendHistory = new LinkedHashSet<>((30));
        }
    }

    private void refreshValueFormSp() {
        //String mPath = SPUtils.getInstance().getString(SPKey.SERIAL_PORT, (""));
        //int mBaudRate = Integer.parseInt(SPUtils.getInstance().getString(SPKey.BAUD_RATE, ("9600")));
        mCheckDigit = Integer.parseInt(SPUtils.getInstance().getString(SPKey.CHECK_DIGIT, ("0")));
        mDataBits = Integer.parseInt(SPUtils.getInstance().getString(SPKey.DATA_BITS, ("8")));
        mStopBit = Integer.parseInt(SPUtils.getInstance().getString(SPKey.STOP_BIT, ("1")));
    }

    public void open() {
        refreshValueFormSp();
        try {
            mSerialPort = new SerialPort(new File(SERIAL_PATH), SERIAL_BAUD_RATE, mCheckDigit, mDataBits, mStopBit, (0));
        } catch (Exception e) {
            e.printStackTrace();
            mSerialPort = null;
            ToastUtils.showLong(R.string.open_serial_port_failure);
        }

        if (mSerialPort != null) {
            this.initBLNStatus('1');
            isInterrupted = false;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            onReceiveSubscribe();
            onSendSubscribe();
            mSerialView.onSerialOpen((mSerialPort != null));
            //ToastUtils.showLong(R.string.open_serial_port_success);
        } else {
            mSerialView.onSerialError((0));
        }
    }

    public void open(Integer baudRate) {
        if (baudRate == null) {
            Log.i(logTag, ("onSerialBaudRate null"));
            return;
        }

        refreshValueFormSp();
        try {
            Log.i(logTag, ("onSerialBaudRate: " + baudRate));
            mSerialPort = new SerialPort(new File(SERIAL_PATH), baudRate, mCheckDigit, mDataBits, mStopBit, (0));
        } catch (IOException e) {
            e.printStackTrace();
            ToastUtils.showLong(R.string.open_serial_port_failure);
        }

        if (mSerialPort != null) {
            this.initBLNStatus('1');
            isInterrupted = false;

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            onReceiveSubscribe();
            onSendSubscribe();
        }

        mSerialView.onSerialOpen((mSerialPort != null));
    }

    public void close() {
        Log.i(logTag, ("closing!!!"));
        this.initBLNStatus('0');
        isInterrupted = true;

        disposable(mReceiveDisposable);
        disposable(mSendDisposable);
        mEmitter = null;
        mSerialPort = null;

        mSerialView.onSerialOpen((false));
    }

    public void initBLNStatus(char status) {
        setBLNStatus(status, POWER_EN_PATH);
        setBLNStatus(status, POGO_WAKE);
    }

    private void setBLNStatus(char status, String path) {
        FileOutputStream fos = null;
        try {
            File file = new File(path);
            if (file.exists()) {
                fos = new FileOutputStream(file);
                fos.write(status);
                fos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void onSendSubscribe() {
        mSendDisposable =
                Observable.create((ObservableOnSubscribe<String>) emitter -> mEmitter = emitter)
                        .filter(s -> !TextUtils.isEmpty(s))
                        .doOnNext(s -> mSendHistory.add(s))
                        .doOnNext(s -> {
                            Log.i(logTag, ("Sent: " + addSpace(s.toUpperCase())));
                            mSerialPort.getOutputStream().write(isHexSend ? ByteUtils.hexStrToBytes(s) : ByteUtils.strToAsciiBytes(s));
                            mSerialPort.getOutputStream().flush();
                        })
                        .observeOn(AndroidSchedulers.mainThread())
                        .filter(s -> isShowSend)
                        .subscribe(s -> {
                            //Log.d(logTag, ("sender: " + addSpace(s.toUpperCase())));
                            mSerialView.onSerialData(new RawData(RawData.TYPE_SEND, isShowTime ? DateTime.now().toString(mDateFormat) : "", addSpace(s.toUpperCase())));
                        }, Throwable::printStackTrace);
    }

    private void onReceiveSubscribe() {
        mReceiveDisposable =
                Flowable.create((FlowableOnSubscribe<byte[]>) emitter -> {
                            int first, available;
                            InputStream is = mSerialPort.getInputStream();
                            while (!isInterrupted
                                    && mSerialPort != null
                                    && is != null
                                    && (first = is.read()) != -1) {
                                do {
                                    available = is.available();
                                    SystemClock.sleep((1));
                                } while (available != is.available());

                                available = is.available();
                                byte[] bytes = new byte[available + 1];
                                int n = is.read(bytes, (1), available);
                                Log.d(logTag, ("Received n: " + n + ", raw: " + ByteUtils.bytesToHexStr(bytes)));
                                bytes[0] = (byte) (first & 0xFF);
                                Log.d(logTag, ("Received first: " + first + ", raw: " + ByteUtils.bytesToHexStr(bytes)));
                                emitter.onNext(bytes);
                            }
                            Log.d(logTag, ("onReceiveSubscribe close:"));
                            close();
                        }, BackpressureStrategy.MISSING
                ).retry().map(bytes -> isHexReceive ? ByteUtils.bytesToHexStr(bytes) : ByteUtils.bytesToAscii(bytes))
                        .filter(this::validate)
                        .filter(this::crcVerify)
                        .map(this::getBody)
                        .subscribeOn(Schedulers.single())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(
                                it -> {
                                    Log.v(logTag, ("Received: " + addSpace(it)));
                                    mSerialView.onSerialData(new RawData(RawData.TYPE_RECEIVE, isShowTime ? DateTime.now().toString(mDateFormat) : "", it));
                                }, Throwable::printStackTrace
                        );
    }

    private boolean validate(String raw) {
        try {
            Log.v(logTag, ("len: " + raw.length()));
            return !TextUtils.isEmpty(raw) && raw.length() >= 22;
        } catch (Exception e) {
            Log.v(logTag, ("validate, exp: " + e.getMessage()));
            return false;
        }
    }

    private boolean crcVerify(String rawData) {
        try {
            Log.v(logTag, ("crcVerify: " + rawData));
            for (String str : SplitUtils.split(rawData)) {
                if (TextUtils.isEmpty(str) || str.length() < 4) continue;
                if (!CRCUtils.genCrc(str.substring(4, str.length() - 4)).equals(str.substring(str.length() - 4))) {
                    Log.w(logTag, ("str: " + str + " crc fail!"));
                    return  (str.charAt(12) == '0' && str.charAt(13) == '0' && str.charAt(14) == '2' && str.charAt(15) == '6');
                }
            }
            return true;
        } catch (Exception e) {
            Log.v(logTag, ("crcVerify, exp: " + e.getMessage()));
            return false;
        }
    }

    private String getBody(String raw) {
        return raw;
    }

    private String addSpace(String s) {
        if (s.length() % 2 == 0) {
            StringBuilder builder = new StringBuilder();
            char[] array = s.toCharArray();
            int length = array.length;
            for (int i = 0; i < length; i += 2) {
                if (i != 0 && i <= length - 2) {
                    builder.append(" ");
                }

                builder.append(array[i]);
                builder.append(array[i + 1]);
            }

            return builder.toString();
        }
        return s;
    }

    public void write(String message) {
        if (mEmitter == null) {
            ToastUtils.showLong(R.string.open_serial_port_first);
            return;
        }

        if (TextUtils.isEmpty(message)) {
            Log.w(logTag, "Message is empty!");
            return;
        }
        mEmitter.onNext(message);
    }

    public void onDestroy() {
        close();
        SPUtils.getInstance().put(SPKey.SEND_HISTORY, mSendHistory);
    }

    private void disposable(Disposable disposable) {
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
        }
    }
}