# Test REST client.
# Author: Tony DiCola (tdicola@adafruit.com)
import time
import unittest

from Adafruit_IO import Client, Data, Feed, Group, Dashboard, Block, Layout, RequestError

import base


# Default config for tests to run against real Adafruit IO service with no proxy.
BASE_URL  = 'https://io.adafruit.com/'
PROXIES   = None

# Config to run tests against real Adafruit IO service over non-SSL and with a
# a proxy running on localhost 8888 (good for getting traces with fiddler).
#BASE_URL  = 'http://io.adafruit.vm/'
#PROXIES   = {'http': 'http://localhost:8888/'}


class TestClient(base.IOTestCase):

    # If your IP isn't put on the list of non-throttled IPs, uncomment the
    # function below to waste time between tests to prevent throttling.
    #def tearDown(self):
    #    time.sleep(30.0)

    # Helper Methods
    def get_client(self):
        # Construct an Adafruit IO REST client and return it.
        return Client(self.get_test_username(), self.get_test_key(), proxies=PROXIES, base_url=BASE_URL)

    def ensure_feed_deleted(self, client, feed):
        # Delete the specified feed if it exists.
        try:
            client.delete_feed(feed)
        except RequestError:
            # Swallow the error if the feed doesn't exist.
            pass

    def ensure_group_deleted(self, client, group):
        # Delete the specified group if it exists.
        try:
            client.delete_group(group)
        except RequestError:
            # Swallow the error if the group doesn't exist.
            pass

    def ensure_dashboard_deleted(self, client, dashboard):
        # Delete the specified dashboard if it exists.
        try:
            client.delete_dashboard(dashboard)
        except RequestError:
            # Swallow the error if the dashboard doesn't exist.
            pass

    def ensure_block_deleted(self, client, dashboard, block):
        # Delete the specified block if it exists.
        try:
            client.delete_block(dashboard, block)
        except RequestError:
            # Swallow the error if the block doesn't exist.
            pass

    def empty_feed(self, client, feed):
        # Remove all the data from a specified feed (but don't delete the feed).
        data = client.data(feed, max_results=None)
        for d in data:
            client.delete(feed, d.id)

    # Test Adafruit IO Key Functionality
    def test_set_key_and_username(self):
        username = "unique_username"
        key = "unique_key_id"
        io = Client(username, key)
        self.assertEqual(username, io.username)
        self.assertEqual(key, io.key)

    # Test Data Functionality
    def test_send_and_receive(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        response = io.send_data('testfeed', 'foo')
        self.assertEqual(response.value, 'foo')
        data = io.receive('testfeed')
        self.assertEqual(data.value, 'foo')

    def test_send_batch_data(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        data_list = [Data(value=42), Data(value=42)]
        io.send_batch_data(test_feed.key, data_list)
        data = io.receive(test_feed.key)
        self.assertEqual(int(data.value), 42)

    def test_receive_next(self):
        """receive_next
        """
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        io.send_data('testfeed', 1)
        data = io.receive_next('testfeed')
        self.assertEqual(int(data.value), 1)

    def test_receive_previous(self):
        """receive_previous
        """
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        io.send_data(test_feed.key, 1)
        io.receive_next(test_feed.key)  # Receive 1
        data = io.receive_previous(test_feed.key)
        self.assertEqual(int(data.value), 1)
        io.send_data(test_feed.key, 2)
        io.receive_next(test_feed.key)  # Receive 2
        data = io.receive_previous(test_feed.key)
        self.assertEqual(int(data.value), 2)

    def test_data_on_feed_returns_all_data(self):
        """send_data
        """
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        io.send_data('testfeed', 1)
        io.send_data('testfeed', 2)
        result = io.data('testfeed')
        self.assertEqual(len(result), 2)
        self.assertEqual(int(result[0].value), 2)
        self.assertEqual(int(result[1].value), 1)

    def test_data_on_feed_and_data_id_returns_data(self):
        """send_data
        """
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        test_feed = io.create_feed(Feed(name="testfeed"))
        data = io.send_data('testfeed', 1)
        result = io.data('testfeed', data.id)
        self.assertEqual(data.id, result.id)
        self.assertEqual(int(data.value), int(result.value))

    def test_create_data(self):
        """create_data
        """
        aio = self.get_client()
        self.ensure_feed_deleted(aio, 'testfeed')
        test_feed = aio.create_feed(Feed(name="testfeed"))
        aio.send_data('testfeed', 1)  # Make sure TestFeed exists.
        data = Data(value=42)
        result = aio.create_data('testfeed', data)
        self.assertEqual(int(result.value), 42)

    def test_location_data(self):
        """receive_location
        """
        aio = self.get_client()
        self.ensure_feed_deleted(aio, 'testlocfeed')
        test_feed = aio.create_feed(Feed(name='testlocfeed'))
        metadata = {'lat': 40.726190,
                    'lon': -74.005334,
                    'ele': -6,
                    'created_at': None}
        aio.send_data(test_feed.key, 40, metadata)
        data = aio.receive(test_feed.key)
        self.assertEqual(int(data.value), 40)
        self.assertEqual(float(data.lat), 40.726190)
        self.assertEqual(float(data.lon), -74.005334)
        self.assertEqual(float(data.ele), -6.0)

    def test_time_data(self):
        """receive_time
        """
        aio = self.get_client()
        server_time = aio.receive_time(timezone='UTC')
        # Check that each value is rx'd properly
        # (should never be None type)
        for time_data in server_time:
            self.assertIsNotNone(time_data)
        # Check that the week day was interpreted properly
        adjusted_time = time.localtime(time.mktime(server_time))
        self.assertEqual(server_time.tm_wday, adjusted_time.tm_wday)

    def test_parse_time_struct(self):
        """Ensure the _parse_time_struct method properly handles all 7
        week days. Particularly important to make sure Sunday is 6,
        not -1"""
        # Zero time is a dictionary as would be provided by server
        # (wday is one higher than it should be)
        zero_time = {'year': 1970,
                     'mon': 1,
                     'mday': 1,
                     'hour': 0,
                     'min': 0,
                     'sec': 0,
                     'wday': 4,
                     'yday': 1,
                     'isdst': 0}

        # Create a good struct for each day of the week and make sure
        # the server-style dictionary is parsed correctly
        for k in range(7):
            real_struct = time.gmtime(k * 86400)
            d = zero_time.copy()
            d['mday'] += k
            d['wday'] += k
            d['yday'] += k
            newd = Client._parse_time_struct(d)
            self.assertEqual(newd.tm_wday, real_struct.tm_wday)

    # Test Feed Functionality
    def test_append_by_feed_name(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        feed = io.create_feed(Feed(name='testfeed'))
        result = io.append('testfeed', 42)
        self.assertEqual(int(result.value), 42)

    def test_append_by_feed_key(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        feed = io.create_feed(Feed(name='testfeed'))
        result = io.append(feed.key, 42)
        self.assertEqual(int(result.value), 42)

    def test_create_feed(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        feed = Feed(name='testfeed')
        result = io.create_feed(feed)
        self.assertEqual(result.name, 'testfeed')

    def test_create_feed_in_group(self):
        """Tests creating a feed within a group.

        """
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        self.ensure_group_deleted(io, 'testgroup')

        group = io.create_group(Group(name='testgroup'))
        feed = Feed(name='testfeed')
        result = io.create_feed(feed, "testgroup")
        self.assertEqual(result.key, "testgroup.testfeed")

        io.delete_feed(result.key)
        io.delete_group('testgroup')

    def test_feeds_returns_all_feeds(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        feed = io.create_feed(Feed(name='testfeed'))
        io.send_data('testfeed', 1)  # Make sure TestFeed exists.
        feeds = io.feeds()
        self.assertGreaterEqual(len(feeds), 1)
        names = set(map(lambda x: x.name, feeds))
        self.assertTrue('testfeed' in names)

    def test_feeds_returns_requested_feed(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        feed = io.create_feed(Feed(name='testfeed'))
        io.send_data('testfeed', 1)  # Make sure TestFeed exists.
        result = io.feeds('testfeed')
        self.assertEqual(result.name, 'testfeed')

    def test_delete_feed(self):
        io = self.get_client()
        io.send_data('testfeed', 'foo')  # Make sure a feed called TestFeed exists.
        io.delete_feed('testfeed')
        self.assertRaises(RequestError, io.receive, 'testfeed')

    def test_delete_nonexistant_feed_fails(self):
        io = self.get_client()
        self.ensure_feed_deleted(io, 'testfeed')
        self.assertRaises(RequestError, io.delete_feed, 'testfeed')


    # Test Group Functionality
    def test_groups_returns_all_groups(self):
        io = self.get_client()
        groups = io.groups()
        self.assertGreaterEqual(len(groups), 1)
        names = set(map(lambda x: x.name, groups))
        self.assertTrue('grouptest' in names)

    def test_groups_retrieves_requested_group(self):
        io = self.get_client()
        self.ensure_group_deleted(io, 'grouptest')
        response = io.create_group(Group(name='grouptest'))
        self.assertEqual(response.name, 'grouptest')
        self.assertEqual(response.key, 'grouptest')

    def test_delete_group(self):
        io = self.get_client()
        self.ensure_group_deleted(io, 'groupdeletetest')
        group = io.create_group(Group(name='groupdeletetest'))
        io.delete_group('groupdeletetest')
        self.assertRaises(RequestError, io.groups, 'groupdeletetest')

    def test_receive_group_by_name(self):
        io = self.get_client()
        self.ensure_group_deleted(io, 'grouprx')
        group = io.create_group(Group(name='grouprx'))
        response = io.groups(group.name)
        self.assertEqual(response.name, 'grouprx')

    def test_receive_group_by_key(self):
        io = self.get_client()
        self.ensure_group_deleted(io, 'grouprx')
        group = io.create_group(Group(name='grouprx'))
        response = io.groups(group.key)
        self.assertEqual(response.key, 'grouprx')


    # Test Dashboard Functionality
    def test_dashboard_create_dashboard(self):
        io = self.get_client()
        self.ensure_dashboard_deleted(io, 'dashtest')
        response = io.create_dashboard(Dashboard(name='dashtest'))
        self.assertEqual(response.name, 'dashtest')

    def test_dashboard_returns_all_dashboards(self):
        io = self.get_client()
        self.ensure_dashboard_deleted(io, 'dashtest')
        dashboard = io.create_dashboard(Dashboard(name='dashtest'))
        response = io.dashboards()
        self.assertGreaterEqual(len(response), 1)

    def test_dashboard_returns_requested_feed(self):
        io = self.get_client()
        self.ensure_dashboard_deleted(io, 'dashtest')
        dashboard = io.create_dashboard(Dashboard(name='dashtest'))
        response = io.dashboards('dashtest')
        self.assertEqual(response.name, 'dashtest')

    # Test Block Functionality
    def test_block_create_block(self):
        io = self.get_client()
        self.ensure_block_deleted(io, 'dashtest', 'blocktest')
        self.ensure_dashboard_deleted(io, 'dashtest')
        dash = io.create_dashboard(Dashboard(name='dashtest'))
        block = io.create_block(dash.key, Block(name='blocktest',
                                                visual_type = 'line_chart'))
        self.assertEqual(block.name, 'blocktest')
        io.delete_block(dash.key, block.id)
        io.delete_dashboard(dash.key)

    def test_dashboard_returns_all_blocks(self):
        io = self.get_client()
        self.ensure_block_deleted(io, 'dashtest', 'blocktest')
        self.ensure_dashboard_deleted(io, 'dashtest')
        dash = io.create_dashboard(Dashboard(name='dashtest'))
        block = io.create_block(dash.key, Block(name='blocktest',
                                                visual_type = 'line_chart'))
        response = io.blocks(dash.key)
        self.assertEqual(len(response), 1)
        io.delete_block(dash.key, block.id)
        io.delete_dashboard(dash.key)

    def test_dashboard_returns_requested_block(self):
        io = self.get_client()
        self.ensure_block_deleted(io, 'dashtest', 'blocktest')
        self.ensure_dashboard_deleted(io, 'dashtest')
        dash = io.create_dashboard(Dashboard(name='dashtest'))
        block = io.create_block(dash.key, Block(name='blocktest',
                                                visual_type = 'line_chart'))
        response = io.blocks(dash.key, block.id)
        self.assertEqual(response.name, 'blocktest')
        io.delete_block(dash.key, block.id)
        io.delete_dashboard(dash.key)

    # Test Layout Functionality
    def test_layout_returns_all_layouts(self):
        io = self.get_client()
        self.ensure_block_deleted(io, 'dashtest', 'blocktest')
        self.ensure_dashboard_deleted(io, 'dashtest')
        dash = io.create_dashboard(Dashboard(name='dashtest'))
        block = io.create_block(dash.key, Block(name='blocktest',
                                                visual_type = 'line_chart'))
        response = io.layouts(dash.key)
        self.assertEqual(len(response), 5) # 5 layouts: xs, sm, md, lg, xl
        self.assertEqual(len(response.lg), 1)
        io.delete_block(dash.key, block.id)
        io.delete_dashboard(dash.key)

    def test_layout_update_layout(self):
        io = self.get_client()
        self.ensure_block_deleted(io, 'dashtest', 'blocktest')
        self.ensure_dashboard_deleted(io, 'dashtest')
        dash = io.create_dashboard(Dashboard(name='dashtest'))
        block = io.create_block(dash.key, Block(name='blocktest',
                                                visual_type = 'line_chart'))
        layout = Layout(lg = [
                   {'x': 0, 'y': 0, 'w': 16, 'h': 4, 'i': str(block.id)}])
        io.update_layout(dash.key, layout)
        response = io.layouts(dash.key)
        self.assertEqual(len(response.lg), 1)
        self.assertEqual(response.lg[0]['w'], 16)
        io.delete_block(dash.key, block.id)
        io.delete_dashboard(dash.key)


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