/// \brief Reads and interprets data reported by the Wii Remote.
    /// \return On success, > 0, < 0 on failure, 0 if nothing has been recieved.
    /// 
    /// Wii Remote reads function similarly to a Queue, in FIFO (first in, first out) order.
    /// For example, if two reports were sent since the last \c ReadWiimoteData() call,
    /// this call will only read and interpret the first of those two (and "pop" it off
    /// of the queue).  So, in order to make sure you don't fall behind the Wiimote's update
    /// frequency, you can do something like this (in a game loop for example):
    ///
    /// \code
    /// Wii Remote wiimote;
    /// int ret;
    /// do
    /// {
    ///     ret = wiimote.ReadWiimoteData();
    /// } while (ret > 0);
    /// \endcode
    public int ReadWiimoteData()
    {
        byte[] buf = new byte[22];
        int status = WiimoteManager.RecieveRaw(hidapi_handle, buf);
        if (status <= 0) return status; // Either there is some sort of error or we haven't recieved anything

        int typesize = GetInputDataTypeSize((InputDataType)buf[0]);
        byte[] data = new byte[typesize];
        for (int x = 0; x < data.Length; x++)
            data[x] = buf[x + 1];

        if (WiimoteManager.Debug_Messages)
            Debug.Log("Recieved: [" + buf[0].ToString("X").PadLeft(2, '0') + "] " + BitConverter.ToString(data));

        // Variable names used throughout the switch/case block
        byte[] buttons;
        byte[] accel;
        byte[] ext = null;
        byte[] ir;

        switch ((InputDataType)buf[0]) // buf[0] is the output ID byte
        {
            case InputDataType.STATUS_INFO: // done.
                buttons = new byte[] { data[0], data[1] };
                byte flags = data[2];
                byte battery_level = data[5];

                Button.InterpretData(buttons);

                bool old_ext_connected = Status.ext_connected;

                byte[] total = new byte[] { flags, battery_level };
                Status.InterpretData(total);

                if (expecting_status_report)
                {
                    expecting_status_report = false;
                }
                else                                        // We haven't requested any data report type, meaning a controller has connected.
                {
                    SendDataReportMode(last_report_type);   // If we don't update the data report mode, no updates will be sent
                }

                if (Status.ext_connected != old_ext_connected && Type != WiimoteType.PROCONTROLLER)
                {
                    if (Status.ext_connected)                // The Wii Remote doesn't allow reading from the extension identifier
                    {                                        // when nothing is connected.
                        Debug.Log("An extension has been connected.");
                        if (current_ext != ExtensionController.MOTIONPLUS)
                        {
                            ActivateExtension();
                            RequestIdentifyExtension();         // Identify what extension was connected.
                        }
                        else
                            ExpectingWiiMotionPlusSwitch = false;
                    }
                    else
                    {
                        if (!ExpectingWiiMotionPlusSwitch)
                            _current_ext = ExtensionController.NONE;
                        Debug.Log("An extension has been disconnected.");
                    }
                }
                break;
            case InputDataType.READ_MEMORY_REGISTERS: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                if (CurrentReadData == null)
                {
                    Debug.LogWarning("Recived Register Read Report when none was expected.  Ignoring.");
                    return status;
                }

                byte size = (byte)((data[2] >> 4) + 0x01);
                byte error = (byte)(data[2] & 0x0f);
                // Error 0x07 means reading from a write-only register
                // Offset 0xa600fa is for the Wii Motion Plus.  This error code can be expected behavior in this case.
                if (error == 0x07)
                {
                    if(CurrentReadData.Offset != 0xa600fa)
                        Debug.LogError("Wiimote reports Read Register error 7: Attempting to read from a write-only register ("+CurrentReadData.Offset.ToString("x")+").  Aborting read.");

                    CurrentReadData = null;
                    return status;
                }
                // lowOffset is reversed because the Wii Remote reports are in Big Endian order
                ushort lowOffset = BitConverter.ToUInt16(new byte[] { data[4], data[3] }, 0);
                ushort expected = (ushort)CurrentReadData.ExpectedOffset;
                if (expected != lowOffset)
                    Debug.LogWarning("Expected Register Read Offset (" + expected + ") does not match reported offset from Wii Remote (" + lowOffset + ")");
                byte[] read = new byte[size];
                for (int x = 0; x < size; x++)
                    read[x] = data[x + 5];

                CurrentReadData.AppendData(read);
                if (CurrentReadData.ExpectedOffset >= CurrentReadData.Offset + CurrentReadData.Size)
                    CurrentReadData = null;

                break;
            case InputDataType.ACKNOWLEDGE_OUTPUT_REPORT:
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);
                // TODO: doesn't do any actual error handling, or do any special code about acknowledging the output report.
                break;
            case InputDataType.REPORT_BUTTONS: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);
                break;
            case InputDataType.REPORT_BUTTONS_ACCEL: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                accel = new byte[] { data[0], data[1], data[2], data[3], data[4] };
                Accel.InterpretData(accel);
                break;
            case InputDataType.REPORT_BUTTONS_EXT8: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                ext = new byte[8];
                for (int x = 0; x < ext.Length; x++)
                    ext[x] = data[x + 2];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_BUTTONS_ACCEL_IR12: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                accel = new byte[] { data[0], data[1], data[2], data[3], data[4] };
                Accel.InterpretData(accel);

                ir = new byte[12];
                for (int x = 0; x < 12; x++)
                    ir[x] = data[x + 5];
                Ir.InterpretData(ir);
                break;
            case InputDataType.REPORT_BUTTONS_EXT19: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                ext = new byte[19];
                for (int x = 0; x < ext.Length; x++)
                    ext[x] = data[x + 2];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_BUTTONS_ACCEL_EXT16: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                accel = new byte[] { data[0], data[1], data[2], data[3], data[4] };
                Accel.InterpretData(accel);

                ext = new byte[16];
                for (int x = 0; x < ext.Length; x++)
                    ext[x] = data[x + 5];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_BUTTONS_IR10_EXT9: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                ir = new byte[10];
                for (int x = 0; x < 10; x++)
                    ir[x] = data[x + 2];
                Ir.InterpretData(ir);

                ext = new byte[9];
                for (int x = 0; x < 9; x++)
                    ext[x] = data[x + 12];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_BUTTONS_ACCEL_IR10_EXT6: // done.
                buttons = new byte[] { data[0], data[1] };
                Button.InterpretData(buttons);

                accel = new byte[] { data[0], data[1], data[2], data[3], data[4] };
                Accel.InterpretData(accel);

                ir = new byte[10];
                for (int x = 0; x < 10; x++)
                    ir[x] = data[x + 5];
                Ir.InterpretData(ir);

                ext = new byte[6];
                for (int x = 0; x < 6; x++)
                    ext[x] = data[x + 15];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_EXT21: // done.
                ext = new byte[21];
                for (int x = 0; x < ext.Length; x++)
                    ext[x] = data[x];

                if (_Extension != null)
                    _Extension.InterpretData(ext);
                break;
            case InputDataType.REPORT_INTERLEAVED:
                if (!ExpectingSecondInterleavedPacket)
                {
                    ExpectingSecondInterleavedPacket = true;
                    InterleavedDataBuffer = data;
                } else if(WiimoteManager.Debug_Messages) {
                    Debug.LogWarning(
                        "Recieved two REPORT_INTERLEAVED ("+InputDataType.REPORT_INTERLEAVED.ToString("x")+") reports in a row!  "
                        + "Expected REPORT_INTERLEAVED_ALT ("+InputDataType.REPORT_INTERLEAVED_ALT.ToString("x")+").  Ignoring!"
                    );
                }
                
                break;
            case InputDataType.REPORT_INTERLEAVED_ALT:
                if (ExpectingSecondInterleavedPacket)
                {
                    ExpectingSecondInterleavedPacket = false;

                    buttons = new byte[] { data[0], data[1] };
                    Button.InterpretData(buttons);

                    byte[] ir1 = new byte[18];
                    byte[] ir2 = new byte[18];

                    for (int x = 0; x < 18; x++)
                    {
                        ir1[x] = InterleavedDataBuffer[x + 3];
                        ir2[x] = data[x + 3];
                    }

                    Ir.InterpretDataInterleaved(ir1, ir2);
                    Accel.InterpretDataInterleaved(InterleavedDataBuffer, data);
                }
                else if(WiimoteManager.Debug_Messages)
                {
                    Debug.LogWarning(
                        "Recieved two REPORT_INTERLEAVED_ALT ("+InputDataType.REPORT_INTERLEAVED_ALT.ToString("x")+") reports in a row!  "
                        + "Expected REPORT_INTERLEAVED ("+InputDataType.REPORT_INTERLEAVED.ToString("x")+").  Ignoring!"
                    );
                }
                break;
        }

        if(ext == null)
            _RawExtension = null;
        else
            _RawExtension = new ReadOnlyArray<byte>(ext);

        return status;
    }
    /// \brief Requests the Wii Remote to report data from its internal registers.
    /// \param type The type of register you would like to read from
    /// \param offset The starting offset of the block of data you would like to read
    /// \param size The size of the block of data you would like to read
    /// \param Responder This will be called when the Wii Remote finishes reporting the requested data.
    /// \return On success, > 0, <= 0 on failure.
    /// \sa SendRegisterWriteRequest(RegisterType, int, byte[])
    ///
    /// \warning Do not attempt to read from the registers when another read is pending (that is, data is being
    ///          recieved by the Wii Remote).  If you attempt to do this, the new read request will be ignored.
    /// 
    /// Reading from the Wii Remote's internal registers can give important data not available through normal output reports.
    /// This can, for example, be used to read saved Mii data from the Wii Remote's EEPROM registers.  It is also used by some
    /// of WiimoteApi's setup functions.
    /// 
    /// If you use this incorrectly (for example, if you attempt to read from an invalid block of data), \c Responder will not be called.
    public int SendRegisterReadRequest(RegisterType type, int offset, int size, ReadResponder Responder)
    {
        if (CurrentReadData != null)
        {
            Debug.LogWarning("Aborting read request; There is already a read request pending!");
            return -2;
        }


        CurrentReadData = new RegisterReadData(offset, size, Responder);

        byte address_select = (byte)type;
        byte[] offsetArr = IntToBigEndian(offset, 3);
        byte[] sizeArr = IntToBigEndian(size, 2);

        byte[] total = new byte[] { address_select, offsetArr[0], offsetArr[1], offsetArr[2], 
            sizeArr[0], sizeArr[1] };

        return SendWithType(OutputDataType.READ_MEMORY_REGISTERS, total);
    }