package com.module.pay.nayax;

import com.bitmick.marshall.interfaces.lowlevel_i;
import com.bitmick.utils.ByteArrayUtils;
import com.bitmick.utils.Log;
import com.bitmick.utils.Utils;

import java.io.File;
import java.io.IOException;

import android_serialport_api.SerialPort;

/**
 * 针对信用卡设备实现的串口工具
 */
public class SerialTool implements lowlevel_i
{


    // ===========================================================
    // Constants
    // ===========================================================

    public static final String TAG = SerialTool.class.getSimpleName();

    // ===========================================================
    // Fields
    // ===========================================================

    private link_events_t m_link_events;


    /**
     * 串口工具
     * 也可以使用第三方包自行替换
     */
    private SerialPort m_serial_port;

    private String m_serial_port_number;
    private int m_serial_port_baud_rate;

    private Thread m_client_thread;
    private boolean m_client_thread_running;

    // ===========================================================
    // Abstract methods
    // ===========================================================

    // ===========================================================b
    // Constructors
    // ===========================================================

    // ===========================================================
    // Methods from SuperClass/Interfaces (and supporting methods)
    // ===========================================================

    @Override
    public void init(Object i_f, Object i_f_params)
    {
        m_serial_port_number = (String) i_f;
        m_serial_port_baud_rate = (Integer) i_f_params;
    }

    @Override
    public void register_link_events(link_events_t events)
    {
        m_link_events = events;
    }

    @Override
    public void start()
    {

        try
        {
            m_serial_port = new SerialPort(new File(m_serial_port_number), m_serial_port_baud_rate, 0);
        }
        catch (Exception e)
        {
            Log.e(TAG, "failed opening serial port");

            m_serial_port = null;
            e.printStackTrace();
            return;
        }

        m_client_thread = new Thread(new ClientAltHandlingRunnable());
        m_client_thread.start();

    }

    @Override
    public void stop()
    {
        if (m_serial_port != null)
        {
            m_serial_port.close();
        }
    }

    @Override
    public void reset()
    {

    }

    @Override
    public boolean transmit(byte[] data, int len)
    {

        try
        {
            m_serial_port.getOutputStream().write(data, 0, len);
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public void onLinkTimerTick(long ms)
    {
    }

    // ===========================================================
    // Methods
    // ===========================================================

    //////////////////////////////////////////////////////////////////////////////////////////////
    // class name:
    // notes:
    /////////////////////////////////////////////////////////////////////////////////////////////
    private class ClientAltHandlingRunnable implements Runnable
    {

        private int head;
        private int tail;
        private byte[] rx_buf;
        private boolean thread_running;

        public void stop()
        {
            thread_running = true;
        }

        private void reset()
        {
            head = 0;
            tail = 0;
        }

        private void serial_reset()
        {
            head = 0;
            tail = 0;
        }

        private int serial_read(byte[] buf, int offset)
        {
            int read_len = 0;

            synchronized (m_serial_port)
            {
                int to_read = 0;
                try
                {
                    to_read = Math.min(m_serial_port.getInputStream().available(), (buf.length - offset));
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                    return read_len;
                }
                if (to_read > 0)
                {
                    try
                    {
                        read_len = m_serial_port.getInputStream().read(buf, offset, to_read);
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }

            return read_len;
        }

        @Override
        public void run()
        {
            m_client_thread_running = true;

            byte[] rx_buf = new byte[4096 * 4];

            serial_reset();

            while (m_client_thread_running)
            {
                int read_len = serial_read(rx_buf, head);

                head += read_len;

                if (head != tail)
                {
                    Boolean cont = true;

                    // try to handle as many packets that might be inside buffer
                    do
                    {
                        int exp_len = ByteArrayUtils.byteArrToShort(rx_buf, tail) + 2;
                        int rcv_len = head - tail;

                        if (exp_len < 9 || exp_len > 512)
                        {
                            serial_reset();
                        }
                        else if (rcv_len >= exp_len)
                        {


                            // pass packet up
                            if (!m_link_events.onReceive(rx_buf, tail, exp_len))
                            {
                                serial_reset();
                            }
                            else
                            {
                                tail += exp_len;

                                // wrap around, only after packet processed succesfully
                                if ((rx_buf.length - tail) <= 412 && tail == head) serial_reset();
                            }

                        }

                        cont = (head != tail) && (rcv_len >= exp_len);
                    } while (cont);
                }
                else
                {
                    Utils.threadSleep(1);
                }
            }
        }
    }



}
