class Application(Gtk.Application):
    def __init__(self, **kwargs):
        super().__init__(application_id="org.example.myapp", **kwargs)
        self.window = None
        self.serial_config = None
        self.thread_data = None
        self.thread_values = None
        self.thread_graph = None
        self.thread_data_test = None
        self.connect_action = None
        self.disconnect_action = None
        self.seconds_action = None
        self.minutes_action = None
        self.hours_action = None
        self.serial_port = serial.Serial()
        self.config_path = ""
        self.environmental_data_history = EnvironmentalDataHistory()
        self.decoder = SerialDecoder()
        self.config_manager = None
        self.view_mode = TimeScale.SECONDS
        self.old_history_time_scale = self.environmental_data_history.time_scale

    def do_startup(self):
        Gtk.Application.do_startup(self)

        user_config_dir = os.path.expanduser(
            "~") + os.path.sep + ".basement_monitoring"
        if not os.path.exists(user_config_dir):
            os.makedirs(user_config_dir)
        self.config_manager = ConfigManager(user_config_dir + os.path.sep +
                                            "user_config.ini")
        self.serial_config = self.config_manager.read_serial_config()

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("serialConfig", None)
        action.connect("activate", self.on_serial_config)
        self.add_action(action)

        self.connect_action = Gio.SimpleAction.new("serialConnect", None)
        self.connect_action.connect("activate", self.on_serial_connect)
        self.add_action(self.connect_action)

        self.disconnect_action = Gio.SimpleAction.new("serialDisconnect", None)
        self.disconnect_action.connect("activate", self.on_serial_disconnect)
        self.disconnect_action.set_enabled(False)
        self.add_action(self.disconnect_action)

        self.seconds_action = Gio.SimpleAction.new("viewSeconds", None)
        self.seconds_action.connect("activate", self.on_view_seconds)
        self.add_action(self.seconds_action)

        self.minutes_action = Gio.SimpleAction.new("viewMinutes", None)
        self.minutes_action.connect("activate", self.on_view_minutes)
        self.minutes_action.set_enabled(False)
        self.add_action(self.minutes_action)

        self.hours_action = Gio.SimpleAction.new("viewHours", None)
        self.hours_action.connect("activate", self.on_view_hours)
        self.hours_action.set_enabled(False)
        self.add_action(self.hours_action)

        builder = Gtk.Builder.new_from_string(MENU_XML, -1)
        self.set_menubar(builder.get_object("app-menu"))

        self.thread_data = threading.Thread(target=self.read_serial)
        self.thread_data.daemon = True

        self.thread_values = threading.Thread(target=self.update_values)
        self.thread_values.daemon = True

        self.thread_graph = threading.Thread(target=self.update_graph)
        self.thread_graph.daemon = True

        self.thread_data_test = threading.Thread(
            target=self.generate_test_data,
            args=[self.environmental_data_history])
        self.thread_data_test.daemon = True

    def do_shutdown(self):
        Gtk.Application.do_shutdown(self)

        if self.serial_port.is_open:
            self.serial_port.close()

    def do_activate(self):
        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = MainWindow(application=self, title="Monitoring")

        self.window.present()
        self.thread_data.start()
        self.thread_values.start()
        self.thread_graph.start()
        # self.thread_data_test.start()

    def on_serial_config(self, action, param):
        win = SerialWindow(self.serial_config,
                           self.config_manager.write_serial_config)
        win.show()

    def on_serial_connect(self, action, param):
        if not self.serial_port.is_open:
            self.serial_port.baudrate = self.serial_config.speed
            self.serial_port.port = self.serial_config.line
            self.serial_port.bytesize = self.serial_config.data_bits
            self.serial_port.stopbits = self.serial_config.stop_bits
            self.serial_port.xonxoff = True
            self.serial_port.rtscts = False
            self.serial_port.timeout = 1
            self.serial_port.open()
            self.connect_action.set_enabled(False)
            self.disconnect_action.set_enabled(True)

    def on_serial_disconnect(self, action, param):
        if self.serial_port.is_open:
            self.serial_port.close()
            self.connect_action.set_enabled(True)
            self.disconnect_action.set_enabled(False)

    def on_view_seconds(self, action, param):
        self.view_mode = TimeScale.SECONDS
        GLib.idle_add(
            self.window.update_graph,
            self.environmental_data_history.get_history(self.view_mode),
            self.view_mode)

    def on_view_minutes(self, action, param):
        self.view_mode = TimeScale.MINUTES
        GLib.idle_add(
            self.window.update_graph,
            self.environmental_data_history.get_history(self.view_mode),
            self.view_mode)

    def on_view_hours(self, action, param):
        self.view_mode = TimeScale.HOURS
        GLib.idle_add(
            self.window.update_graph,
            self.environmental_data_history.get_history(self.view_mode),
            self.view_mode)

    def on_quit(self, action, param):
        self.quit()

    def update_view_modes(self):
        if self.view_mode == self.old_history_time_scale and \
           self.old_history_time_scale != self.environmental_data_history.time_scale:
            self.view_mode = self.environmental_data_history.time_scale
            self.old_history_time_scale = self.environmental_data_history.time_scale

        if self.environmental_data_history.time_scale is TimeScale.HOURS:
            self.minutes_action.set_enabled(True)
            self.hours_action.set_enabled(True)
        if self.environmental_data_history.time_scale is TimeScale.MINUTES:
            self.minutes_action.set_enabled(True)

    def update_values(self):
        while True:
            if len(self.environmental_data_history.get_history(
                    self.view_mode)) > 0:
                GLib.idle_add(self.window.update_values,
                              self.environmental_data_history.last)

            self.update_view_modes()
            time.sleep(1)

    def update_graph(self):
        i = 0
        second_refresh = 2
        minute_refresh = 30
        hour_refresh = 1800
        refresh_needed = False
        while True:
            if len(self.environmental_data_history.get_history(
                    self.view_mode)) > 0:
                if self.view_mode is TimeScale.SECONDS and i >= second_refresh:
                    refresh_needed = True
                elif self.view_mode is TimeScale.MINUTES and i >= minute_refresh:
                    refresh_needed = True
                elif i == hour_refresh:
                    refresh_needed = True

                if refresh_needed:
                    GLib.idle_add(
                        self.window.update_graph,
                        self.environmental_data_history.get_history(
                            self.view_mode), self.view_mode)
                    i = 0
                    refresh_needed = False

            i = i + 2
            time.sleep(2)

    def read_serial(self):
        while True:
            if self.serial_port.is_open:
                self.decoder.decode(self.serial_port.read().decode("utf-8"))
                if self.decoder.completed and len(self.decoder.decoded) >= 2:
                    self.environmental_data_history.append(
                        EnvironmentalData(self.decoder.decoded[0],
                                          self.decoder.decoded[1]))
            time.sleep(0.1)

    @staticmethod
    def generate_test_data(stack):
        while True:
            temp = 2 * random.random_sample() + 20
            hum = 5 * random.random_sample() + 48
            env = EnvironmentalData(temp, hum)
            stack.append(env)
            time.sleep(1)