package com.felhr.usbserial;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.util.Log;


public class CP2130SpiDevice extends UsbSpiDevice
{
    private static String CLASS_ID = CP2130SpiDevice.class.getSimpleName();

    public static final int CLOCK_12MHz = 0;
    public static final int CLOCK_6MHz = 1;
    public static final int CLOCK_3MHz = 2;
    public static final int CLOCK_1_5MHz = 3;
    public static final int CLOCK_750KHz = 4;
    public static final int CLOCK_375KHz = 5;
    public static final int CLOCK_187_5KHz = 6;
    public static final int CLOCK_93_75KHz = 7;

    private static final int BM_REQ_DEVICE_2_HOST = 0xc0;
    private static final int BM_REQ_HOST_2_DEVICE = 0x40;

    private static final int SET_SPI_WORD = 0x31;
    private static final int SET_GPIO_CHIP_SELECT = 0x25;
    private static final int GET_SPI_WORD = 0x30;

    private UsbInterface mInterface;
    private UsbEndpoint inEndpoint;
    private UsbEndpoint outEndpoint;
    private UsbRequest requestIN;

    private int currentChannel;

    public CP2130SpiDevice(UsbDevice device, UsbDeviceConnection connection)
    {
       this(device, connection, -1);
    }

    public CP2130SpiDevice(UsbDevice device, UsbDeviceConnection connection, int iface)
    {
        super(device, connection);
        mInterface = device.getInterface(iface >= 0 ? iface : 0);
        currentChannel = 0;
    }


    @Override
    public boolean connectSPI()
    {
        boolean ret = openCP2130();

        if(!ret)
            return false;

        // Restart the working thread if it has been killed before and  get and claim interface
        restartWorkingThread();
        restartWriteThread();

        // Pass references to the threads
        setThreadsParams(inEndpoint, outEndpoint);

        return true;
    }

    @Override
    public int getSelectedSlave()
    {
        return currentChannel;
    }

    @Override
    public void writeMOSI(byte[] buffer)
    {
        byte[] buffCommand = new byte[buffer.length + 8];
        buffCommand[0] = 0x00;
        buffCommand[1] = 0x00;
        buffCommand[2] = 0x01;
        buffCommand[3] = (byte) 0x80;
        buffCommand[4] = (byte) (buffer.length & 0xff);
        buffCommand[5] = (byte) ((buffer.length >> 8) & 0xff);
        buffCommand[6] = (byte) ((buffer.length >> 16) & 0xff);
        buffCommand[7] = (byte) ((buffer.length >> 24) & 0xff);

        System.arraycopy(buffer, 0, buffCommand, 8, buffer.length);

        serialBuffer.putWriteBuffer(buffCommand);
    }

    @Override
    public void setClock(int clockDivider)
    {
        switch(clockDivider)
        {
            case CLOCK_12MHz:
                setSetSpiWord(currentChannel, CLOCK_12MHz);
                break;
            case CLOCK_6MHz:
                setSetSpiWord(currentChannel, CLOCK_6MHz);
                break;
            case CLOCK_3MHz:
                setSetSpiWord(currentChannel, CLOCK_3MHz);
                break;
            case CLOCK_1_5MHz:
                setSetSpiWord(currentChannel, CLOCK_1_5MHz);
                break;
            case CLOCK_750KHz:
                setSetSpiWord(currentChannel, CLOCK_750KHz);
                break;
            case CLOCK_375KHz:
                setSetSpiWord(currentChannel, CLOCK_375KHz);
                break;
            case CLOCK_187_5KHz:
                setSetSpiWord(currentChannel, CLOCK_187_5KHz);
                break;
            case CLOCK_93_75KHz:
                setSetSpiWord(currentChannel, CLOCK_93_75KHz);
                break;
        }
    }

    @Override
    public void readMISO(int lengthBuffer)
    {
        byte[] buffCommand = new byte[8];
        buffCommand[0] = 0x00;
        buffCommand[1] = 0x00;
        buffCommand[2] = 0x00;
        buffCommand[3] = (byte) 0x80;
        buffCommand[4] = (byte) (lengthBuffer & 0xff);
        buffCommand[5] = (byte) ((lengthBuffer >> 8) & 0xff);
        buffCommand[6] = (byte) ((lengthBuffer >> 16) & 0xff);
        buffCommand[7] = (byte) ((lengthBuffer >> 24) & 0xff);

        serialBuffer.putWriteBuffer(buffCommand);
    }

    @Override
    public void writeRead(byte[] buffer, int lengthRead)
    {
        byte[] buffCommand = new byte[8 + buffer.length];
        buffCommand[0] = 0x00;
        buffCommand[1] = 0x00;
        buffCommand[2] = 0x02;
        buffCommand[3] = (byte) 0x80;
        buffCommand[4] = (byte) (lengthRead & 0xff);
        buffCommand[5] = (byte) ((lengthRead >> 8) & 0xff);
        buffCommand[6] = (byte) ((lengthRead >> 16) & 0xff);
        buffCommand[7] = (byte) ((lengthRead >> 24) & 0xff);

        System.arraycopy(buffer, 0, buffCommand, 8, buffer.length);

        serialBuffer.putWriteBuffer(buffCommand);
    }

    @Override
    public void selectSlave(int nSlave)
    {
        if(nSlave > 10 || nSlave < 0)
        {
            Log.i(CLASS_ID, "selected slave must be in 0-10 range");
            return;
        }

        setGpioChipSelect(nSlave, true);
    }

    @Override
    public int getClockDivider()
    {
        byte[] data = getSpiWord();
        return data[currentChannel] & 0x07;
    }

    @Override
    public void closeSPI()
    {
        killWorkingThread();
        killWriteThread();
        connection.releaseInterface(mInterface);
    }

    private boolean openCP2130()
    {
        if(connection.claimInterface(mInterface, true))
        {
            Log.i(CLASS_ID, "Interface succesfully claimed");
        }else
        {
            Log.i(CLASS_ID, "Interface could not be claimed");
            return false;
        }

        // Assign endpoints
        int numberEndpoints = mInterface.getEndpointCount();
        for(int i=0;i<=numberEndpoints-1;i++)
        {
            UsbEndpoint endpoint = mInterface.getEndpoint(i);
            if(endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK
                    && endpoint.getDirection() == UsbConstants.USB_DIR_IN)
            {
                inEndpoint = endpoint;
            }else
            {
                outEndpoint = endpoint;
            }
        }

        return true;
    }

    private void setSetSpiWord(int channel, int freq)
    {
        byte[] payload = new byte[2];

        if(channel >= 0 && channel <= 10)
        {
            payload[0] = (byte) channel;
        }else
        {
            Log.i(CLASS_ID, "Channel not valid");
            return;
        }
        payload[1] = (byte) (freq);
        payload[1] = (byte) (payload[1] | (1 << 3)); // Push pull chip select pin mode

        setControlCommandOut(SET_SPI_WORD, 0, 0, payload);

    }

    private void setGpioChipSelect(int channel, boolean othersDisabled)
    {
        byte[] payload = new byte[2];

        if(channel >= 0 && channel <= 10)
        {
            payload[0] = (byte) channel;
        }else
        {
            Log.i(CLASS_ID, "Channel not valid");
            return;
        }

        byte control;
        if(othersDisabled)
            control = 0x02;
        else
            control = 0x01;

        payload[1] = control;

        int ret = setControlCommandOut(SET_GPIO_CHIP_SELECT, 0x00, 0x00, payload);

        if(ret != -1)
            currentChannel = channel;

    }

    private byte[] getSpiWord()
    {
        return setControlCommandIn(GET_SPI_WORD, 0x00, 0x00, 2);
    }

    private int setControlCommandOut(int request, int value, int index, byte[] data)
    {
        int dataLength = 0;
        if(data != null)
        {
            dataLength = data.length;
        }
        int response = connection.controlTransfer(BM_REQ_HOST_2_DEVICE, request, value, mInterface.getId(), data, dataLength, USB_TIMEOUT);
        Log.i(CLASS_ID,"Control Transfer Response: " + String.valueOf(response));
        return response;
    }

    private byte[] setControlCommandIn(int request, int value, int index, int length)
    {
        byte[] data = new byte[length];
        int response = connection.controlTransfer(BM_REQ_DEVICE_2_HOST, request, value, mInterface.getId(), data, length, USB_TIMEOUT);
        Log.i(CLASS_ID,"Control Transfer Response: " + String.valueOf(response));
        return data;
    }
}
