#!/usr/bin/env python3
#
# libssc: Library to expose Qualcomm Sensor Core sensors
# Copyright (C) 2022-2025 Dylan Van Assche
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
import unittest
from unittest import mock
from qmi import QMI, MessageType, ValueType
from ssc import *

QMI_SSC_CONTROL_MESSAGE = b'\x00\x01\x00 \x00;\x00\x10\x01\x00\x01\x014\x002\x00\n' + \
                          b'\x12\t\xab\xab\xab\xab\xab\xab\xab\xab\x11\xab\xab\xab' + \
                          b'\xab\xab\xab\xab\xab\x15\x00\x02\x00\x00\x1a\x04\x08\x01' + \
                          b'\x10\x00"\x11\x12\x0f\n\tproximity\x10\x00\x18\x01'

PROTOBUF_SSC_DISCOVERY_MESSAGE = b'\n\x12\t\xab\xab\xab\xab\xab\xab\xab\xab\x11\xab' + \
                                 b'\xab\xab\xab\xab\xab\xab\xab\x15\x00\x02\x00\x00' + \
                                 b'\x1a\x04\x08\x01\x10\x00"\x11\x12\x0f\n\tproximity' + \
                                 b'\x10\x00\x18\x01'


class TestQMI(unittest.TestCase):
    """QMI encoding and decoding tests"""

    def test_parse_header(self):
        message_type, transaction_id, message_id, message_length = QMI.parse_header(QMI_SSC_CONTROL_MESSAGE)
        self.assertTrue(message_type == MessageType.REQUEST)
        self.assertTrue(transaction_id == 0x1)
        self.assertTrue(message_id == 0x20)
        self.assertTrue(message_length == 0x3B)

    def test_parse_tlv_int(self):
        tlv_type, tlv_length, tlv_value = QMI.parse_tlv(QMI_SSC_CONTROL_MESSAGE,
                                                        SSC_CONTROL_MSG_REPORT_TYPE_ID,
                                                        ValueType.INT)
        self.assertTrue(tlv_type == SSC_CONTROL_MSG_REPORT_TYPE_ID)
        self.assertTrue(tlv_length == 0x1)
        self.assertTrue(tlv_value == 0x1)
        
    def test_parse_tlv_array(self):
        tlv_type, tlv_length, tlv_value = QMI.parse_tlv(QMI_SSC_CONTROL_MESSAGE,
                                                        SSC_CONTROL_MSG_DATA_ID,
                                                        ValueType.ARRAY)
        self.assertTrue(tlv_type == SSC_CONTROL_MSG_DATA_ID)
        self.assertTrue(tlv_length == 0x34)
        self.assertTrue(len(tlv_value) == 0x32)

    def test_generate_header(self):
        message = QMI.generate_header(MessageType.REQUEST, 1, SSC_CONTROL_MSG_ID)
        self.assertTrue(message == bytes([0x0, 0x1, 0x0, 0x20, 0x0, 0x7, 0x0]))

    def test_generate_tlv(self):
        message = QMI.generate_header(MessageType.REQUEST, 1, SSC_CONTROL_MSG_ID)
        value = ReportType.LARGE.value.to_bytes(1, byteorder='little')
        message = QMI.generate_tlv(message, SSC_CONTROL_MSG_REPORT_TYPE_ID,
                                   value, ValueType.INT)
        value = b'\n\x12\t\xab\xab\xab\xab\xab\xab\xab\xab\x11\xab\xab\xab\xab' + \
                b'\xab\xab\xab\xab\x15\x00\x02\x00\x00\x1a\x04\x08\x01\x10\x00"' + \
                b'\x11\x12\x0f\n\tproximity\x10\x00\x18\x01'
        message = QMI.generate_tlv(message, SSC_CONTROL_MSG_DATA_ID,
                                   value, ValueType.ARRAY)
        self.assertTrue(message == QMI_SSC_CONTROL_MESSAGE)


class TestSSC(unittest.TestCase):
    """Snapdragon Sensor Core QMI messages tests"""

    def test_parse_qmi_message_control_input(self):
        report_type, data, transaction_id = SSC.parse_message_control_input(QMI_SSC_CONTROL_MESSAGE)
        self.assertTrue(report_type == ReportType.LARGE)
        self.assertTrue(len(data) == 0x32)
        self.assertTrue(transaction_id == 0x1)

    def test_generate_qmi_report_large_indication(self):
        expected = b'\x04\x01\x00"\x00\x10\x00\x01\x08\x00\x01\x00\x00\x00\x00\x00\x00' + \
                   b'\x00\x02\x02\x00\x00\x00'
        buf = SSC.generate_report_large_indication(bytes(), 1)
        self.assertTrue(buf == expected)

    def test_generate_qmi_report_small_indication(self):
        expected = b'\x04\x01\x00!\x00\x0c\x00\x01\x04\x00\x01\x00\x00\x00\x02\x02\x00' + \
                   b'\x00\x00'
        buf = SSC.generate_report_small_indication(bytes(), 1)
        self.assertTrue(buf == expected)

    def test_parse_protobuf_client_request(self):
        message_id, uid_high, uid_low = SSC.parse_protobuf_client_request(PROTOBUF_SSC_DISCOVERY_MESSAGE)
        self.assertTrue(message_id == 512)
        self.assertTrue(uid_high == SSC_SUID_SENSOR_UID_HIGH)
        self.assertTrue(uid_low == SSC_SUID_SENSOR_UID_LOW)

    def test_parse_protobuf_discovery_request(self):
        data_type = SSC.parse_protobuf_discovery_request(PROTOBUF_SSC_DISCOVERY_MESSAGE)
        self.assertTrue(data_type == 'proximity')

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_discovery_response(self):
        expected = b'\n\x12\t\xab\xab\xab\xab\xab\xab\xab\xab\x11\xab\xab\xab\xab' + \
                   b'\xab\xab\xab\xab\x12/\r\x00\x03\x00\x00\x1190\x00\x00\x00\x00' + \
                   b'\x00\x00\x1a\x1f\n\tproximity\x12\x12\t\x01\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x11\x01\x00\x00\x00\x00\x00\x00\x00'
        buf = SSC.generate_protobuf_discovery_response('proximity')
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_attributes_response(self):
        expected = b'\n\x12\t\x01\x00\x00\x00\x00\x00\x00\x00\x11\x01\x00\x00\x00' + \
                   b'\x00\x00\x00\x00\x12`\r\x80\x00\x00\x00\x1190\x00\x00\x00\x00' + \
                   b'\x00\x00\x1aP\n\x16\x08\x00\x12\x12\n\x10\x12\x0eproximity-mock' + \
                   b'\n\x0e\x08\x01\x12\n\n\x08\x12\x06libssc\n\x08\x08\x03\x12\x04' + \
                   b'\n\x02(\x01\n\x0b\x08\x06\x12\x07\n\x05\x1d\x00\x00\xa0@\n\x0f' + \
                   b'\x08\x10\x12\x0b\n\t!\x01\x00\x00\x00\x00\x00\x00\x00'
        buf = SSC.generate_protobuf_attributes_response(0x1, 0x1)
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_sensor_measurement_proximity(self):
        expected = b'\n\x12\t\x01\x00\x00\x00\x00\x00\x00\x00\x11\x01\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x12\x16\r\x01\x03\x00\x00\x1190\x00\x00\x00\x00\x00' + \
                   b'\x00\x1a\x06\x08\x00\x10\n\x18\x03'
        buf = SSC.generate_protobuf_sensor_measurement(0x1, 0x1, 0)
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_sensor_measurement_accelerometer(self):
        expected = b'\n\x12\t\x02\x00\x00\x00\x00\x00\x00\x00\x11\x02\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x12!\r\x01\x04\x00\x00\x1190\x00\x00\x00\x00\x00\x00' + \
                   b'\x1a\x11\r\x00\x00\x00\x00\r\x00\x00\x00\x00\r\xc3\xf5\x1c\xc1\x10\x03'
        buf = SSC.generate_protobuf_sensor_measurement(0x2, 0x2, 0)
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_sensor_measurement_magnetometer(self):
        expected = b'\n\x12\t\x03\x00\x00\x00\x00\x00\x00\x00\x11\x03\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x12!\r\x01\x04\x00\x00\x1190\x00\x00\x00\x00\x00\x00' + \
                   b'\x1a\x11\r\xcd\xcc\xcc=\r\xcd\xccL>\r\x9a\x99\x99>\x10\x03'
        buf = SSC.generate_protobuf_sensor_measurement(0x3, 0x3, 0)
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_sensor_measurement_light(self):
        expected = b'\n\x12\t\x04\x00\x00\x00\x00\x00\x00\x00\x11\x04\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x12\x1c\r\x01\x04\x00\x00\x1190\x00\x00\x00\x00\x00' + \
                   b'\x00\x1a\x0c\r\x00\x00\xa0@\r\x00\x00(B\x10\x03'
        buf = SSC.generate_protobuf_sensor_measurement(0x4, 0x4, 0)
        self.assertTrue(buf == expected)

    @mock.patch('time.time', mock.MagicMock(return_value=12345))
    def test_generate_protobuf_sensor_measurement_rotationvector(self):
        expected = b'\n\x12\t\x05\x00\x00\x00\x00\x00\x00\x00\x11\x05\x00\x00\x00\x00' + \
                   b'\x00\x00\x00\x12&\r\x01\x04\x00\x00\x1190\x00\x00\x00\x00\x00\x00' + \
                   b'\x1a\x16\r\x00\x00\x80?\r\x00\x00\x00@\r\x00\x00@@\r\x00\x00\x80@' + \
                   b'\x10\x03'
        buf = SSC.generate_protobuf_sensor_measurement(0x5, 0x5, 0)
        self.assertTrue(buf == expected)


if __name__ == '__main__':
    unittest.main()
