#!/usr/bin/env python
import functools

import gtk

import BluetoothPairedDevices
from ParrotZik import BatteryStates
from ParrotZik import ParrotZikVersion1
from ParrotZik import ParrotZikVersion2
from ParrotZik import NoiseControlTypes
from ParrotZik import connect
from ParrotZik import Rooms
from SysIndicator import MenuItem
from SysIndicator import Menu
from SysIndicator import SysIndicator

REFRESH_FREQUENCY = 30000
RECONNECT_FREQUENCY = 5000


class repeat(object):
    def __init__(self, f):
        self.f = f
        self.id = None

    def __call__(self, cls):
        self.f(cls)

    def start(self, cls, frequency):
        if not self.id:
            def run():
                self.f(cls)
                return True

            self.id = gtk.timeout_add(frequency, run)

    def stop(self):
        if self.id:
            gtk.timeout_remove(self.id)
            self.id = None


class ParrotZikIndicator(SysIndicator):
    def __init__(self):
 
        self.menu = Menu()

        self.info_item = MenuItem("Parrot Zik Not connected",
                                  None, sensitive=False)
        self.menu.append(self.info_item)

        self.version_1_interface = ParrotZikVersion1Interface(self)
        self.version_2_interface = ParrotZikVersion2Interface(self)
        self.quit = MenuItem("Quit", gtk.main_quit, checkitem=True)
        self.menu.append(self.quit)

        SysIndicator.__init__(self, icon="zik-audio-headset", menu=self.menu)

        self.active_interface = None

    @repeat
    def reconnect(self):
        if self.active_interface:
            if not self.active_interface.connected:
                self.info_item.set_label("Lost connection")
                self.active_interface.deactivate()
            else:
                self.reconnect.stop()
        else:
            mac = BluetoothPairedDevices.ParrotZikMac()
            if mac:
                self.info_item.set_label("Connecting")
                resource_manager = connect(mac)
                if resource_manager.sock:
                    if resource_manager.api_version.startswith('1'):
                        self.version_1_interface.activate(resource_manager)
                    else:
                        self.version_2_interface.activate(resource_manager)
                    self.autorefresh(self)
                    self.autorefresh.start(self, REFRESH_FREQUENCY)
                    self.reconnect.stop()
                else:
                    self.info_item.set_label("Failed to connect")
            else:
                self.info_item.set_label("Parrot Zik Not connected")

    @repeat
    def autorefresh(self):
        if self.active_interface:
            self.active_interface.refresh()
        else:
            self.reconnect.start(self, RECONNECT_FREQUENCY)
            self.autorefresh.stop()

    def main(self):
        self.reconnect.start(self, RECONNECT_FREQUENCY)
        SysIndicator.main(self)

class ParrotZikBaseInterface(object):
    def __init__(self, indicator):
        self.indicator = indicator
        self.parrot = None
        self.battery_level = MenuItem("Battery Level:", None, sensitive=False,
                                      visible=False)
        self.battery_state = MenuItem("Battery State:", None, sensitive=False,
                                      visible=False)
        self.firmware_version = MenuItem("Firmware Version:", None,
                                         sensitive=False, visible=False)
        self.auto_connection = MenuItem("Auto Connection", self.toggle_auto_connection,
                                        checkitem=True, visible=False)
        self.indicator.menu.append(self.battery_level)
        self.indicator.menu.append(self.battery_state)
        self.indicator.menu.append(self.firmware_version)
        self.indicator.menu.append(self.auto_connection)

    @property
    def connected(self):
        if self.parrot:
            return self.parrot.resource_manager.sock
        else:
            return False

    def activate(self, resource_manager):
        self.parrot = self.parrot_class(resource_manager)
        self.read_battery()
        self.indicator.info_item.set_label("Connected to: "
                                           + self.parrot.friendly_name)
        self.firmware_version.set_label(
            "Firmware version: " + self.parrot.version)
        self.auto_connection.set_active(self.parrot.auto_connect)

        self.battery_level.show()
        self.battery_state.show()
        self.firmware_version.show()
        self.auto_connection.show()
        self.indicator.active_interface = self
        self.indicator.menu.reposition()

    @property
    def parrot_class(self):
        raise NotImplementedError

    def deactivate(self):
        self.parrot = None
        self.battery_level.hide()
        self.battery_state.hide()
        self.firmware_version.hide()
        self.auto_connection.hide()
        self.indicator.setIcon("zik-audio-headset")
        self.indicator.info_item.set_label("Parrot Zik Not connected..")
        self.indicator.menu.reposition()
        self.indicator.active_interface = None

    def toggle_auto_connection(self, widget):
        if self.connected:
            self.parrot.auto_connect = self.auto_connection.get_active()
            self.auto_connection.set_active(self.parrot.auto_connect)
        else:
            self.deactivate()

    def refresh(self):
        self.read_battery()

    def read_battery(self):
        if self.connected:
            self.parrot.refresh_battery()
            battery_level = self.parrot.battery_level
            battery_state = self.parrot.battery_state
            if battery_state == BatteryStates.CHARGING:
                self.indicator.setIcon("zik-battery-charging")
            elif battery_level > 80:
                self.indicator.setIcon("zik-battery-100")
            elif battery_level > 60:
                self.indicator.setIcon("zik-battery-080")
            elif battery_level > 40:
                self.indicator.setIcon("zik-battery-060")
            elif battery_level > 20:
                self.indicator.setIcon("zik-battery-040")
            else:
                self.indicator.setIcon("zik-battery-low")

            self.battery_state.set_label(
                "State: " + BatteryStates.representation[battery_state])
            self.battery_level.set_label(
                "Battery Level: " + str(battery_level))
        else:
            self.deactivate()


class ParrotZikVersion1Interface(ParrotZikBaseInterface):
    parrot_class = ParrotZikVersion1

    def __init__(self, indicator):
        super(ParrotZikVersion1Interface, self).__init__(indicator)
        self.noise_cancelation = MenuItem(
            "Noise Cancellation", self.toggle_noise_cancelation,
            checkitem=True, visible=False)
        self.lou_reed_mode = MenuItem("Lou Reed Mode", self.toggle_lou_reed_mode,
                                      checkitem=True, visible=False)
        self.concert_hall_mode = MenuItem(
            "Concert Hall Mode", self.toggle_parrot_concert_hall,
            checkitem=True, visible=False)
        self.indicator.menu.append(self.noise_cancelation)
        self.indicator.menu.append(self.lou_reed_mode)
        self.indicator.menu.append(self.concert_hall_mode)

    def activate(self, resource_manager):
        self.noise_cancelation.show()
        self.lou_reed_mode.show()
        self.concert_hall_mode.show()
        super(ParrotZikVersion1Interface, self).activate(resource_manager)

        self.noise_cancelation.set_active(self.parrot.cancel_noise)
        self.lou_reed_mode.set_active(self.parrot.lou_reed_mode)
        self.concert_hall_mode.set_active(self.parrot.concert_hall)

    def deactivate(self):
        self.noise_cancelation.hide()
        self.lou_reed_mode.hide()
        self.concert_hall_mode.hide()
        super(ParrotZikVersion1Interface, self).deactivate()

    def toggle_noise_cancelation(self, widget):
        if self.connected:
            self.parrot.cancel_noise = self.noise_cancelation.get_active()
            self.noise_cancelation.set_active(self.parrot.cancel_noise)
        else:
            self.deactivate()

    def toggle_lou_reed_mode(self, widget):
        if self.connected:
            self.parrot.lou_reed_mode = self.lou_reed_mode.get_active()
            self.lou_reed_mode.set_active(self.parrot.lou_reed_mode)
            self.concert_hall_mode.set_active(self.parrot.concert_hall)
            self.concert_hall_mode.set_sensitive(
                not self.lou_reed_mode.get_active())
        else:
            self.deactivate()

    def toggle_parrot_concert_hall(self, widget):
        if self.connected:
            self.parrot.concert_hall = self.concert_hall_mode.get_active()
            self.concert_hall_mode.set_active(self.parrot.concert_hall)
        else:
            self.deactivate()


class ParrotZikVersion2Interface(ParrotZikBaseInterface):
    parrot_class = ParrotZikVersion2

    def __init__(self, indicator):
        self.room_dirty = False
        self.angle_dirty = False
        self.noise_cancelation_dirty = False
        super(ParrotZikVersion2Interface, self).__init__(indicator)
        self.noise_cancelation = MenuItem("Noise Control", None, visible=False)
        self.noise_cancelation_submenu = Menu()
        self.noise_cancelation.set_submenu(self.noise_cancelation_submenu)

        self.noise_control_cancelation_max = MenuItem(
            "Max Calcelation", functools.partial(
                self.toggle_noise_cancelation,
                NoiseControlTypes.NOISE_CONTROL_MAX), checkitem=True)
        self.noise_control_cancelation_on = MenuItem(
            "Normal Cancelation", functools.partial(
                self.toggle_noise_cancelation,
                NoiseControlTypes.NOISE_CONTROL_ON), checkitem=True)
        self.noise_control_off = MenuItem(
            "Off", functools.partial(
                self.toggle_noise_cancelation,
                NoiseControlTypes.NOISE_CONTROL_OFF), checkitem=True)
        self.noise_control_street_mode = MenuItem(
            "Street Mode", functools.partial(
                self.toggle_noise_cancelation,
                NoiseControlTypes.STREET_MODE), checkitem=True)
        self.noise_control_street_mode_max = MenuItem(
            "Street Mode Max", functools.partial(
                self.toggle_noise_cancelation,
                NoiseControlTypes.STREET_MODE_MAX), checkitem=True)
        self.noise_cancelation_submenu.append(self.noise_control_cancelation_max)
        self.noise_cancelation_submenu.append(self.noise_control_cancelation_on)
        self.noise_cancelation_submenu.append(self.noise_control_off)
        self.noise_cancelation_submenu.append(self.noise_control_street_mode)
        self.noise_cancelation_submenu.append(self.noise_control_street_mode_max)

        self.room_sound_effect = MenuItem(
            "Room Sound Effect", None, visible=False)
        self.room_sound_effect_submenu = Menu()
        self.room_sound_effect.set_submenu(self.room_sound_effect_submenu)

        self.room_sound_effect_enabled = MenuItem(
            "Enabled", self.toggle_room_sound_effect, checkitem=True)
        self.rooms = MenuItem("Rooms", None, checkitem=False)
        self.angle = MenuItem("Angle", None, checkitem=False)
        self.room_sound_effect_submenu.append(self.room_sound_effect_enabled)
        self.room_sound_effect_submenu.append(self.rooms)
        self.room_sound_effect_submenu.append(self.angle)

        self.rooms_submenu = Menu()
        self.rooms.set_submenu(self.rooms_submenu)

        self.concert_hall_mode = MenuItem(
            "Concert Hall", functools.partial(self.toggle_room, Rooms.CONCERT_HALL), checkitem=True)
        self.jazz_mode = MenuItem(
            "Jazz Club", functools.partial(self.toggle_room, Rooms.JAZZ_CLUB), checkitem=True)
        self.living_mode = MenuItem(
            "Living Room", functools.partial(self.toggle_room, Rooms.LIVING_ROOM), checkitem=True)
        self.silent_mode = MenuItem(
            "Silent Room", functools.partial(self.toggle_room, Rooms.SILENT_ROOM), checkitem=True)
        self.rooms_submenu.append(self.concert_hall_mode)
        self.rooms_submenu.append(self.jazz_mode)
        self.rooms_submenu.append(self.living_mode)
        self.rooms_submenu.append(self.silent_mode)

        self.angle_submenu = Menu()
        self.angle.set_submenu(self.angle_submenu)
        self.angle_30 = MenuItem(
            "30", functools.partial(self.toggle_angle, 30), checkitem=True)
        self.angle_60 = MenuItem(
            "60", functools.partial(self.toggle_angle, 60), checkitem=True)
        self.angle_90 = MenuItem(
            "90", functools.partial(self.toggle_angle, 90), checkitem=True)
        self.angle_120 = MenuItem(
            "120", functools.partial(self.toggle_angle, 120), checkitem=True)
        self.angle_150 = MenuItem(
            "150", functools.partial(self.toggle_angle, 150), checkitem=True)
        self.angle_180 = MenuItem(
            "180", functools.partial(self.toggle_angle, 180), checkitem=True)
        self.angle_submenu.append(self.angle_30)
        self.angle_submenu.append(self.angle_60)
        self.angle_submenu.append(self.angle_90)
        self.angle_submenu.append(self.angle_120)
        self.angle_submenu.append(self.angle_150)
        self.angle_submenu.append(self.angle_180)

        self.flight_mode = MenuItem("Flight Mode", self.toggle_flight_mode,
                                    checkitem=True, visible=False)
        self.indicator.menu.append(self.room_sound_effect)
        self.indicator.menu.append(self.noise_cancelation)
        self.indicator.menu.append(self.flight_mode)

    def activate(self, resource_manager):
        self.noise_cancelation.show()
        self.flight_mode.show()
        self.room_sound_effect.show()
        super(ParrotZikVersion2Interface, self).activate(resource_manager)
        self.read_noise_cancelation()
        self.flight_mode.set_active(self.parrot.flight_mode)
        self.read_sound_effect_room()
        self.read_sound_effect_angle()

        sound_effect = self.parrot.sound_effect
        self.room_sound_effect_enabled.set_active(sound_effect)
        self.concert_hall_mode.set_sensitive(sound_effect)
        self.jazz_mode.set_sensitive(sound_effect)
        self.living_mode.set_sensitive(sound_effect)
        self.silent_mode.set_sensitive(sound_effect)

        self.angle_30.set_sensitive(sound_effect)
        self.angle_60.set_sensitive(sound_effect)
        self.angle_90.set_sensitive(sound_effect)
        self.angle_120.set_sensitive(sound_effect)
        self.angle_150.set_sensitive(sound_effect)
        self.angle_180.set_sensitive(sound_effect)

    def deactivate(self):
        self.noise_cancelation.hide()
        self.concert_hall_mode.hide()
        self.flight_mode.hide()
        self.room_sound_effect.hide()
        super(ParrotZikVersion2Interface, self).deactivate()

    def toggle_flight_mode(self, widget):
        if self.connected:
            self.parrot.flight_mode = self.flight_mode.get_active()
            self.flight_mode.set_active(self.parrot.flight_mode)
        else:
            self.deactivate()

    def toggledummy(self, widget):
        print(widget.get_name())

    def toggle_room(self, room, widget):
        if self.connected:
            if not self.room_dirty:
                self.parrot.room = room
                self.room_dirty = True
                self.read_sound_effect_room()
                self.room_dirty = False
        else:
            self.deactivate()

    def read_sound_effect_room(self):
        active_room = self.parrot.room
        room_to_menuitem_map = (
            (Rooms.CONCERT_HALL, self.concert_hall_mode),
            (Rooms.JAZZ_CLUB, self.jazz_mode),
            (Rooms.LIVING_ROOM, self.living_mode),
            (Rooms.SILENT_ROOM, self.silent_mode),
        )
        for room, menu_item in room_to_menuitem_map:
            menu_item.set_active(room == active_room)

    def toggle_room_sound_effect(self, widget):
        if self.connected:
            self.parrot.sound_effect = self.room_sound_effect_enabled.get_active()
            sound_effect = self.parrot.sound_effect
            self.room_sound_effect_enabled.set_active(sound_effect)
            self.concert_hall_mode.set_sensitive(sound_effect)
            self.jazz_mode.set_sensitive(sound_effect)
            self.living_mode.set_sensitive(sound_effect)
            self.silent_mode.set_sensitive(sound_effect)
            self.angle_30.set_sensitive(sound_effect)
            self.angle_60.set_sensitive(sound_effect)
            self.angle_90.set_sensitive(sound_effect)
            self.angle_120.set_sensitive(sound_effect)
            self.angle_150.set_sensitive(sound_effect)
            self.angle_180.set_sensitive(sound_effect)
        else:
            self.deactivate()

    def toggle_angle(self, angle, widget):
        if self.connected:
            if not self.angle_dirty:
                self.parrot.angle = angle
                self.angle_dirty = True
                self.read_sound_effect_angle()
                self.angle_dirty = False
        else:
            self.deactivate()

    def read_sound_effect_angle(self):
        active_angle = self.parrot.angle
        angle_to_menuitem_map = (
            (30, self.angle_30),
            (60, self.angle_60),
            (90, self.angle_90),
            (120, self.angle_120),
            (150, self.angle_150),
            (180, self.angle_180),
        )
        for angle, menu_item in angle_to_menuitem_map:
            menu_item.set_active(angle == active_angle)

    def toggle_noise_cancelation(self, noise_calcelation, widget):
        if self.connected:
            if not self.noise_cancelation_dirty:
                self.parrot.noise_control = noise_calcelation
                self.noise_cancelation_dirty = True
                self.read_noise_cancelation()
                self.noise_cancelation_dirty = False
        else:
            self.deactivate()

    def read_noise_cancelation(self):
        active_noise_control = self.parrot.noise_control
        noise_control_to_menuitem_map = (
            (NoiseControlTypes.NOISE_CONTROL_MAX, self.noise_control_cancelation_max),
            (NoiseControlTypes.NOISE_CONTROL_ON, self.noise_control_cancelation_on),
            (NoiseControlTypes.NOISE_CONTROL_OFF, self.noise_control_off),
            (NoiseControlTypes.STREET_MODE, self.noise_control_street_mode),
            (NoiseControlTypes.STREET_MODE_MAX, self.noise_control_street_mode_max),
        )
        for noise_control, menu_item in noise_control_to_menuitem_map:
            menu_item.set_active(active_noise_control == noise_control)


if __name__ == "__main__":
    try:
        indicator = ParrotZikIndicator()
        indicator.main()
    except KeyboardInterrupt:
        pass