# channel_configuration_page.py
#
# Copyright 2024 Christopher Talbot
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later

from gi.repository import Adw
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gio
import json
import gettext
import pyqrcode
import meshtastic.serial_interface
import meshtastic.tcp_interface
import meshtastic.mesh_interface
import meshtastic.ble_interface
import meshtastic
from meshtastic.protobuf import channel_pb2
from meshtastic.util import fromPSK
from pubsub import pub
import os
import base64

import gtk_meshtastic_client.bluetooth_device_row as bluetooth_device_row
import gtk_meshtastic_client.message_parser as message_parser
import gtk_meshtastic_client.message_storage as message_storage
import gtk_meshtastic_client.nearby_nodes_page as nearby_nodes_page
import gtk_meshtastic_client.channel_configuration_row as channel_configuration_row
import gtk_meshtastic_client.utils as utils

@Gtk.Template(resource_path='/org/kop316/meshtastic/ui/channel_configuration_page.ui')
class ChannelConfigurationPageBin(Adw.Bin):
    __gtype_name__ = 'ChannelConfigurationPageBin'

    channel_config_list_box = Gtk.Template.Child()
    channel_config_list_box_children = 0
    add_channels_button = Gtk.Template.Child()
    reset_button = Gtk.Template.Child()
    configure_channels_button = Gtk.Template.Child()

    def reorder_channels(self):
        for x in range(self.channel_config_list_box_children):
            row_to_update = self.channel_config_list_box.get_row_at_index(x)
            row_to_update.set_channel_index(x)

    def reset_channels(self):
        app = Gtk.Application.get_default()
        win = Gtk.Application.get_active_window(app)
        interface = win.connection_page_bin.interface

        node = interface.getNode('^local')
        channels = node.channels

        self.channel_config_list_box.remove_all()
        self.channel_config_list_box_children = 0

        if channels:
            for channel in channels:
                if channel.role:
                    new_channel = channel_configuration_row.ChannelConfigurationRow()

                    new_channel.set_channel_index(channel.index)
                    new_channel.set_channel_name(channel.settings.name)
                    new_channel.set_channel_psk(channel.settings.psk)
                    if hasattr(channel.settings, 'uplink_enabled'):
                        new_channel.set_channel_uplink(channel.settings.uplink_enabled)
                    else:
                        new_channel.set_channel_uplink(False)

                    if hasattr(channel.settings, 'downlink_enabled'):
                        new_channel.set_channel_downlink(channel.settings.downlink_enabled)
                    else:
                        new_channel.set_channel_downlink(False)

                    if hasattr(channel.settings, 'module_settings'):
                        if hasattr(channel.settings.module_settings, 'position_precision'):
                            new_channel.set_position(channel.settings.module_settings.position_precision)
                        else:
                            new_channel.set_position(0)
                    else:
                        new_channel.set_position(0)

                    self.channel_config_list_box.append(new_channel)
                    self.channel_config_list_box_children += 1

    @Gtk.Template.Callback()
    def _add_channel_button_clicked_cb(self, button):
        if self.channel_config_list_box_children == 8:
            app = Gtk.Application.get_default()
            win = Gtk.Application.get_active_window(app)
            dialog = Adw.AlertDialog.new(_("Alert"),
                                         _("You can only have up to 8 Channels"))
            dialog.add_response ("close",  _("_Close"))

            dialog.present (win)
            return

        new_channel = channel_configuration_row.ChannelConfigurationRow()
        new_channel.psk_entry.set_text("AQ==")
        new_channel.set_channel_name("LongFast")
        new_channel.set_channel_index(self.channel_config_list_box_children)
        new_channel.set_channel_uplink(False)
        new_channel.set_channel_downlink(False)
        new_channel.set_position(0)
        self.channel_config_list_box.append(new_channel)
        self.channel_config_list_box_children += 1

    @Gtk.Template.Callback()
    def _reset_button_clicked_cb(self, button):
        self.reset_channels()

    def _configure_channels_sync(self):
        app = Gtk.Application.get_default()
        win = Gtk.Application.get_active_window(app)
        interface = win.connection_page_bin.interface

        node = interface.getNode('^local')
        app = Gtk.Application.get_default()
        win = Gtk.Application.get_active_window(app)

        for x in range(self.channel_config_list_box_children):
            new_channel = self.channel_config_list_box.get_row_at_index(x)

            ch = node.channels[new_channel.index]
            if new_channel.psk_entry.get_text() != "":
                psk_str = new_channel.psk_entry.get_text()
                ch.settings.psk = base64.decodebytes(str.encode(psk_str))
            else:
                self.logger.debug("Disabling encryption on Channel: " + str(new_channel.index))
                ch.settings.psk = fromPSK("none")

            """
            This seems to be blank if the channel PSK is AQ==
            """
            if new_channel.psk_entry.get_text() == "AQ==":
                ch.settings.name = ""
            else:
                ch.settings.name = new_channel.channel_name_entry.get_text()

            ch.settings.downlink_enabled = new_channel.get_channel_downlink()
            ch.settings.uplink_enabled = new_channel.get_channel_uplink()
            ch.settings.module_settings.position_precision = new_channel.get_position()

            if new_channel.index == 0:
                ch.role = channel_pb2.Channel.Role.PRIMARY
            else:
                ch.role = channel_pb2.Channel.Role.SECONDARY

            node.writeChannel(new_channel.index)

        if self.channel_config_list_box_children < 8:
            for x in reversed(range(self.channel_config_list_box_children, 8)):
                ch = node.channels[x]
                if ch.role == channel_pb2.Channel.Role.DISABLED:
                   self.logger.debug("disabled, no need to delete")
                else:
                    node.deleteChannel(x)

        """
        Reset UI for chats and here
        """

        self.reset_channels()
        win.channels_page_bin.reset_all_channels_and_dms()
        win.channels_page_bin.populate_all_channels_and_dms(interface)

        win.window_toast_overlay.add_toast(Adw.Toast(title="Chanel Configuration Complete"))

    def _configure_channels_finish(self, self_object, result: Gio.AsyncResult):
        app = Gtk.Application.get_default()
        win = Gtk.Application.get_active_window(app)

        self.add_channels_button.set_sensitive(True)
        self.reset_button.set_sensitive(True)
        self.configure_channels_button.set_sensitive(True)
        for x in range(self.channel_config_list_box_children):
            row_to_update = self.channel_config_list_box.get_row_at_index(x)
            row_to_update.set_sensitive(True)

        if not Gio.Task.is_valid(result, self_object):
            self.logger.warning("Not valid GTask")
            return

        success = result.propagate_boolean()
        if not success:
            self.logger.warning("Configure Channels failed!")
            win.window_toast_overlay.add_toast(Adw.Toast(title="Configure Channels Failed"))
            return

    def _configure_channels_async(self, callback) -> None:
        def _configure_channels(task, _source_object, _task_data, _cancellable):
            try:
                self._configure_channels_sync()
            except:
                task.return_boolean(False)
                return

            task.return_boolean(True)

        task = Gio.Task.new(self, None, callback)
        task.run_in_thread(_configure_channels)

        self.add_channels_button.set_sensitive(False)
        self.reset_button.set_sensitive(False)
        self.configure_channels_button.set_sensitive(False)
        for x in range(self.channel_config_list_box_children):
            row_to_update = self.channel_config_list_box.get_row_at_index(x)
            row_to_update.set_sensitive(False)

    def _configure_channels_dialog_cb(self, dialog, response, user):
        if "continue" != response:
            return

        self._configure_channels_async(callback=self._configure_channels_finish)

    @Gtk.Template.Callback()
    def _configure_channels_button_clicked_cb(self, button):
        app = Gtk.Application.get_default()
        win = Gtk.Application.get_active_window(app)
        if self.channel_config_list_box_children == 0:
            dialog = Adw.AlertDialog.new(_("Alert"),
                                         _("You must have at least one channel"))
            dialog.add_response ("close",  _("_Close"))

            dialog.present (win)
            return

        dialog = Adw.AlertDialog.new(_("Alert"),
                                     _("This will change your channel configuration "
                                       "continue?"))
        dialog.add_response ("close",  _("_Close"))
        dialog.add_response ("continue",  _("_Continue"))

        dialog.set_response_appearance ("continue",
                                        Adw.ResponseAppearance.DESTRUCTIVE)

        dialog.connect("response", self._configure_channels_dialog_cb, self)

        dialog.present (win)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        app = Gtk.Application.get_default()
        self.logger = app.logger
