package com.felhr.usbmassstorageforandroid.scsi;

import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;

import com.felhr.usbmassstorageforandroid.bulkonly.BulkOnlyCommunicator;
import com.felhr.usbmassstorageforandroid.bulkonly.BulkOnlyStatusInterface;

import java.util.concurrent.atomic.AtomicBoolean;

import commandwrappers.CommandBlockWrapper;
import commandwrappers.CommandStatusWrapper;

/**
 * Created by Felipe Herranz([email protected]) on 11/12/14.
 */
public class SCSICommunicator
{
    private SCSIInterface scsiInterfaceCallback;
    private BulkOnlyCommunicator communicator;
    private SCSICommandBuffer buffer;
    private SCSICommandHandler commandHandler;

    /*
        Read10 response can be greater than a sector (512 bytes)
        In order to do not send 512 bytes packets to the upper layers
        currentRead10Response will append all packets and will send them altogether.
     */
    private SCSIRead10Response currentRead10Response;
    private int lengthResponse;

    public SCSICommunicator(UsbDevice mDevice, UsbDeviceConnection mConnection)
    {
        this.communicator = new BulkOnlyCommunicator(mDevice, mConnection);
        this.buffer = new SCSICommandBuffer();
        this.commandHandler = new SCSICommandHandler();
        this.commandHandler.start();
    }

    public boolean openSCSICommunicator(SCSIInterface scsiInterfaceCallback)
    {
        this.scsiInterfaceCallback = scsiInterfaceCallback;
        return communicator.startBulkOnly(mCallback);
    }

    public void closeSCSICommunicator()
    {
        commandHandler.stopHandler();
    }

    public void reset()
    {
        communicator.reset();
    }

    public void resetRecovery()
    {
        communicator.resetRecovery();
    }

    public void inquiry(boolean evpd, int pageCode, int allocationLength)
    {
        SCSIInquiry inquiry = new SCSIInquiry(evpd, pageCode, allocationLength);
        buffer.putCommand(inquiry);
    }

    public void readCapacity10(int logicalBlockAddress, boolean pmi)
    {
        SCSIReadCapacity10 readCapacity10 = new SCSIReadCapacity10(logicalBlockAddress, pmi);
        buffer.putCommand(readCapacity10);
    }

    public void read10(int rdProtect, boolean dpo, boolean fua,
                       boolean fuaNv, int logicalBlockAddress,
                       int groupNumber, int transferLength)
    {
        SCSIRead10 read10 = new SCSIRead10(rdProtect, dpo, fua,
                fuaNv, logicalBlockAddress, groupNumber,
                transferLength);
        lengthResponse = transferLength * 512;
        buffer.putCommand(read10);
    }

    public void requestSense(boolean desc, int allocationLength)
    {
        SCSIRequestSense requestSense = new SCSIRequestSense(desc, allocationLength);
        buffer.putCommand(requestSense);
    }

    public void testUnitReady()
    {
        SCSITestUnitReady testUnitReady = new SCSITestUnitReady();
        buffer.putCommand(testUnitReady);
    }

    public void write10(int wrProtect, boolean dpo, boolean fua,
                        boolean fuaNv, int logicalBlockAddress, int groupNumber,
                        int transferLength, byte[] data)
    {
       SCSIWrite10 write10 = new SCSIWrite10(wrProtect, dpo, fua,
               fuaNv, logicalBlockAddress, groupNumber,
               transferLength);

        write10.setDataPhaseBuffer(data);
        buffer.putCommand(write10);
    }

    public void modeSense10(boolean llbaa, boolean dbd, int pc,
                            int pageCode, int subPageCode, int allocationLength)
    {
        SCSIModeSense10 modeSense10 = new SCSIModeSense10(llbaa, dbd, pc, pageCode, subPageCode, allocationLength);
        buffer.putCommand(modeSense10);
    }

    public void modeSelect10(boolean pageFormat, boolean savePages, int parameterListLength)
    {
        SCSIModeSelect10 modeSelect10 = new SCSIModeSelect10(pageFormat, savePages, parameterListLength);
        buffer.putCommand(modeSelect10);
    }

    public void formatUnit(boolean fmtpinfo, boolean rtoReq, boolean longList,
                           boolean fmtData, boolean cmplst, int defectListFormat)
    {
        SCSIFormatUnit formatUnit = new SCSIFormatUnit(fmtpinfo, rtoReq, longList,
                fmtData, cmplst, defectListFormat);

        buffer.putCommand(formatUnit);
    }

    public void preventAllowRemoval(int lun, boolean prevent)
    {
        SCSIPreventAllowRemoval preventAllowRemoval = new SCSIPreventAllowRemoval(lun, prevent);
        buffer.putCommand(preventAllowRemoval);
    }

    private BulkOnlyStatusInterface mCallback = new BulkOnlyStatusInterface()
    {
        @Override
        public void onOperationStarted(boolean status)
        {
            scsiInterfaceCallback.onSCSIOperationStarted(status);
        }

        @Override
        public void onOperationCompleted(CommandStatusWrapper csw)
        {
            if(csw.getbCSWStatus() == 0x00 && currentRead10Response != null)
            {
                scsiInterfaceCallback.onSCSIDataReceived(currentRead10Response);
                currentRead10Response = null;
            }

            if(csw.getbCSWStatus() == 0x02)
              communicator.resetRecovery();

            scsiInterfaceCallback.onSCSIOperationCompleted((int) csw.getbCSWStatus(), csw.getdCSWDataResidue());
            buffer.goAhead();
        }

        @Override
        public void onDataToHost(byte[] data)
        {
            SCSICommand lastCommand = commandHandler.getLastSCSICommand();
            SCSIResponse response = null;
            if(lastCommand instanceof SCSIInquiry)
            {
                response = SCSIInquiryResponse.getResponse(data);
                scsiInterfaceCallback.onSCSIDataReceived(response);
            }else if(lastCommand instanceof SCSIModeSense10)
            {
                response = SCSIModeSense10Response.getResponse(data);
                scsiInterfaceCallback.onSCSIDataReceived(response);
            }else if(lastCommand instanceof SCSIRead10)
            {
                // This case is different because more than one sector are probably be read
                if(currentRead10Response == null)
                    currentRead10Response = SCSIRead10Response.getResponse(data, lengthResponse);
                else
                    currentRead10Response.addToBuffer(data);
            }else if(lastCommand instanceof SCSIReadCapacity10)
            {
                response = SCSIReadCapacity10Response.getResponse(data);
                scsiInterfaceCallback.onSCSIDataReceived(response);
            }else if(lastCommand instanceof SCSIReportLuns)
            {
                response = SCSIReportLunsResponse.getResponse(data);
                scsiInterfaceCallback.onSCSIDataReceived(response);
            }else if(lastCommand instanceof SCSIRequestSense)
            {
                response = SCSIRequestSenseResponse.getResponse(data);
                scsiInterfaceCallback.onSCSIDataReceived(response);
            }
        }
    };

    private class SCSICommandHandler extends Thread
    {
        private AtomicBoolean keep;

        private SCSICommand lastCommand;

        public SCSICommandHandler()
        {
            this.keep = new AtomicBoolean(true);
        }

        @Override
        public void run()
        {
            while(keep.get())
            {
                SCSICommand scsiCommand = buffer.getCommand();
                lastCommand = scsiCommand;
                CommandBlockWrapper cbw = scsiCommand.getCbw();
                communicator.sendCbw(cbw, scsiCommand.getDataPhaseBuffer());
            }
        }

        public void stopHandler()
        {
            keep.set(false);
        }

        public SCSICommand getLastSCSICommand()
        {
            return lastCommand;
        }
    }
}