# -*- coding: utf-8 -*-

import unittest
from mock import Mock
import cloud4rpi
from cloud4rpi.errors import InvalidConfigError
from cloud4rpi.errors import UnexpectedVariableTypeError
from cloud4rpi.errors import UnexpectedVariableValueTypeError


class ApiClientMock(object):
    def __init__(self):
        def noop_on_command(cmd):
            pass

        self.publish_config = Mock()
        self.publish_data = Mock()
        self.publish_diag = Mock()
        self.on_command = noop_on_command

    def assert_publish_data_called_with(self, expected):
        return self.publish_data.assert_called_with(expected, data_type='cr')

    def raise_on_command(self, cmd):
        self.on_command(cmd)


class MockSensor(object):
    def __init__(self, value=42):
        self.read = Mock(return_value=value)
        self.__innerValue__ = value

    def get_state(self):
        return self.__innerValue__

    def get_updated_state(self, value):
        self.__innerValue__ = value
        return self.__innerValue__

    def get_incremented_state(self, value):
        return self.__innerValue__ + value


class TestDevice(unittest.TestCase):
    def testDeclareVariables(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare({
            'CPUTemp': {
                'type': 'numeric',
                'bind': MockSensor()
            }
        })
        cfg = device.read_config()
        self.assertEqual(cfg, [{'name': 'CPUTemp', 'type': 'numeric'}])

    def testDeclareVariablesValidation(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        with self.assertRaises(UnexpectedVariableTypeError):
            device.declare({
                'CPUTemp': {
                    'type': 'number',
                    'bind': MockSensor()
                }
            })

    def testDeclareDiag(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare_diag({
            'IPAddress': '8.8.8.8',
            'Host': 'hostname',
        })
        diag = device.read_diag()
        self.assertEqual(diag, {'IPAddress': '8.8.8.8', 'Host': 'hostname'})

    def testReadConfig(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare({
            'SomeVar': {
                'type': 'string'
            }
        })
        device.declare({
            'CPUTemp': {
                'type': 'numeric',
                'bind': MockSensor()
            }
        })
        cfg = device.read_config()
        self.assertEqual(cfg, [{'name': 'CPUTemp', 'type': 'numeric'}])

    def testReadConfigIfNotDeclared(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        self.assertEqual(device.read_config(), [])

    def testReadVariables(self):
        handler = {}
        temperature_sensor = MockSensor(73)
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare({
            'LEDOn': {
                'type': 'bool',
                'value': False,
                'bind': handler
            },
            'Temperature': {
                'type': 'numeric',
                'value': True,
                'bind': temperature_sensor
            }
        })
        data = device.read_data()
        self.assertEqual(data, {
            'LEDOn': False,
            'Temperature': 73
        })

    def testReadVariablesDoesNotContainsEmptyVars(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        self.assertEqual(device.read_data(), {})

    def testReadVariablesFromClassMethod(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        sensor = MockSensor(10)

        device.declare({
            'MyParam': {
                'type': 'numeric',
                'bind': sensor.get_state
            },
        })
        data = device.read_data()
        self.assertEqual(data, {
            'MyParam': 10,
        })

    def testReadVariablesFromClassMethodWithCurrent(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        sensor = MockSensor(10)

        device.declare({
            'MyParam': {
                'type': 'numeric',
                'value': 1,
                'bind': sensor.get_incremented_state
            },
        })
        data = device.read_data()
        self.assertEqual(data, {
            'MyParam': 11,
        })

    def testReadDiag(self):
        temperature_sensor = MockSensor(73)
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare_diag({
            'CPUTemperature': temperature_sensor,
            'IPAddress': lambda x: '8.8.8.8',
            'OSName': lambda x: 'Linux',
            'Host': 'weather_station'
        })
        diag = device.read_diag()
        self.assertEqual(diag, {
            'CPUTemperature': 73,
            'IPAddress': '8.8.8.8',
            'OSName': 'Linux',
            'Host': 'weather_station'
        })

    def testPublishConfig(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        cfg = [
            {'name': 'CPUTemp', 'type': 'numeric'},
            {'name': 'Cooler', 'type': 'bool'}
        ]
        device.publish_config(cfg)
        api.publish_config.assert_called_with(cfg)

    def testReadBeforePublishConfig(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare({
            'CPUTemp': {
                'type': 'numeric',
                'bind': MockSensor()
            }
        })
        device.publish_config()
        cfg = [{'name': 'CPUTemp', 'type': 'numeric'}]
        api.publish_config.assert_called_with(cfg)

    def testPublishConfigFail_NotAnArray(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)

        cfg = {'name': 'CPUTemp', 'type': 'numeric'}
        with self.assertRaises(InvalidConfigError):
            device.publish_config(cfg)

        api.publish_config.assert_not_called()

    def testPublishConfigFail_UnexpectedVariableType(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)

        cfg = [{'name': 'CPUTemp', 'type': 'number'}]
        with self.assertRaises(UnexpectedVariableTypeError):
            device.publish_config(cfg)

        api.publish_config.assert_not_called()

    def testPublishDiag(self):
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        diag = {
            'IPAddress': '8.8.8.8',
            'Host': 'hostname'
        }
        device.publish_diag(diag)
        api.publish_diag.assert_called_with(diag)

    def testReadBeforePublishDiag(self):
        temperature_sensor = MockSensor(24)
        api = ApiClientMock()
        device = cloud4rpi.Device(api)
        device.declare_diag({
            'CPUTemperature': temperature_sensor,
            'IPAddress': lambda x: '8.8.8.8',
        })
        device.publish_diag()
        diag = {'IPAddress': '8.8.8.8', 'CPUTemperature': 24}
        api.publish_diag.assert_called_with(diag)