from gi.repository import Gdk, GLib, GObject
from notorious.confManager import ConfManager
from notorious.files_listbox_row import FileListboxRow
from os import listdir
from os.path import isfile, splitext, getmtime
from datetime import datetime


class FileManager(GObject.Object):

    __gsignals__ = {
        'note_changed': (
            GObject.SignalFlags.RUN_FIRST,
            None,
            (str,)
        ),
    }

    def __init__(self, search_entry, results_listbox, source_buffer,
                 source_view, results_listbox_revealer):
        super().__init__()
        self.currently_open_file = None
        self.search_entry = search_entry
        self.results_listbox = results_listbox
        self.results_listbox_revealer = results_listbox_revealer
        self.source_buffer = source_buffer
        self.source_view = source_view
        self.confman = ConfManager()
        self.results_listbox.set_filter_func(
            self.results_filter_func, None, False
        )
        self.results_listbox.connect(
            'row_activated',
            self.on_results_listbox_row_activated
        )
        self.results_listbox.connect(
            'row_selected',
            self.on_results_listbox_row_selected
        )

        self.search_entry.connect('changed', self.on_search_changed)
        # activate called on Enter pressed
        self.search_entry.connect(
            'activate',
            self.on_search_entry_activate
        )
        self.search_entry.connect(
            'key-press-event',
            self.on_search_entry_key_press_event
        )
        self.search_entry.connect(
            'icon-press',
            self.on_icon_press
        )
        self.confman.connect(
            'notes_dir_changed',
            self.populate_listbox
        )
        self.source_view.connect(
            'focus-out-event',
            lambda *args: self.results_listbox_revealer.set_reveal_child(True)
        )
        self.source_view.connect(
            'focus-in-event',
            lambda *args: self.results_listbox_revealer.set_reveal_child(False)
        )
        self.populate_listbox()
        self.source_view.set_sensitive(False)
        self.confman.connect(
            'sorting_method_changed',
            self.on_sorting_method_changed
        )
        self.on_sorting_method_changed()
        # auto save
        GLib.timeout_add(
            5000,
            self.save_current_file
        )
        self.last_save = {'file': None, 'time': None}

    def on_icon_press(self, entry, icon_pos, event):
        # only pressable icon is the clear icon)
        entry.set_text('')

    def on_sorting_method_changed(self, *args):
        self.results_listbox.set_sort_func(
            self.results_sort_func_by_name
            if self.confman.conf['sorting_method'] == 'name'
            else self.results_sort_func_by_last_modified,
            None, False
        )

    def results_sort_func_by_name(self, row1, row2, data, notify_destroy):
        return row1.name > row2.name

    def results_sort_func_by_last_modified(self, row1, row2, data,
                                           notify_destroy):
        return row1.last_modified < row2.last_modified

    def results_filter_func(self, row, data, notify_destroy):
        return self.search_entry.get_text().lower().strip() in row.name.lower()

    def listdir_recursive(self, path):
        return [
            e for li in
            [
                [fp] if isfile(fp) else self.listdir_recursive(fp)
                for fp in [path+'/'+f for f in listdir(path)]
            ] for e in li
        ]

    def populate_listbox(self, *args):
        while True:
            row = self.results_listbox.get_row_at_index(0)
            if row:
                self.results_listbox.remove(row)
            else:
                break
        listdir_func = self.listdir_recursive if \
            self.confman.conf['recurse_subfolders'] else listdir
        for f in listdir_func(self.confman.conf['notes_dir']):
            if self.confman.conf['recurse_subfolders']:
                f = f[len(self.confman.conf['notes_dir']):]
                while (f[0] == '/'):
                    f = f[1:]
            file_path = '{0}/{1}'.format(
                self.confman.conf['notes_dir'], f
            )
            if (
                    f[0] != '.' and isfile(file_path) and
                    (
                        not self.confman.conf['use_file_extension'] or
                        splitext(file_path)[1].lower() == '.md'
                    )
            ):
                self.results_listbox.add(
                    FileListboxRow(
                        splitext(f)[0]
                        if self.confman.conf['use_file_extension']
                        else f,
                        file_path,
                        self
                    )
                )
        self.results_listbox.show_all()

    def on_search_changed(self, entry):
        # TODO: hide clear icon when no text
        self.results_listbox.invalidate_filter()

    def on_search_entry_activate(self, *args):
        entry_text = self.search_entry.get_text().strip()
        if not entry_text:
            return
        file_path = '{0}/{1}'.format(
            self.confman.conf['notes_dir'],
            entry_text + (
                '.md'
                if self.confman.conf['use_file_extension']
                and splitext(entry_text)[1].lower() != '.md'
                else ''
            )
        )
        if splitext(entry_text)[1].lower() == '.md' and \
                self.confman.conf['use_file_extension']:
            self.search_entry.set_text(splitext(entry_text)[0])
        self.open_file(file_path)

    def on_search_entry_key_press_event(self, entry, event):
        row = None
        if event.keyval == Gdk.KEY_Down or (
                (
                    event.state & Gdk.ModifierType.CONTROL_MASK
                ) == Gdk.ModifierType.CONTROL_MASK and
                event.keyval == Gdk.KEY_j
        ):
            self.results_listbox.set_sensitive(False)
            row = self.results_listbox.get_row_at_index(0)
        elif event.keyval == Gdk.KEY_Up or (
                (
                    event.state & Gdk.ModifierType.CONTROL_MASK
                ) == Gdk.ModifierType.CONTROL_MASK and
                event.keyval == Gdk.KEY_k
        ):
            self.results_listbox.set_sensitive(False)
            row = self.results_listbox.get_row_at_index(
                len(self.results_listbox.get_children()) - 1
            )
        elif event.keyval == Gdk.KEY_Escape:
            # weird way to select all
            self.search_entry.grab_focus()
            return
        if row is not None:
            self.results_listbox.select_row(row)
            self.results_listbox.set_sensitive(True)
            GLib.idle_add(lambda *args: row.grab_focus())

    def activate_row(self, listbox, row, focus_editor=True):
        if not row:
            return
        self.open_file(row.file_path, focus_editor)

    def on_results_listbox_row_activated(self, listbox, row):
        self.activate_row(listbox, row)

    def on_results_listbox_row_selected(self, listbox, row):
        if self.confman.conf['activate_row_on_select']:
            self.activate_row(listbox, row, False)

    def reload_note(self):
        self.open_file(self.currently_open_file, True, True)
        self.source_view.grab_focus()

    def overwrite_note(self):
        self.save_current_file(overwrite=True)
        self.source_view.grab_focus()

    def save_current_file(self, *args, overwrite=False):
        if (
                self.currently_open_file is not None and
                isfile(self.currently_open_file)
        ):
            text_to_save = self.source_buffer.get_text(
                self.source_buffer.get_start_iter(),
                self.source_buffer.get_end_iter(),
                True
            )
            if not overwrite:
                # Has anything changed?
                with open(self.currently_open_file, 'r') as fd:
                    if fd.read() == text_to_save:
                        return True
                # Has the file been changed externally?
                if self.last_save['file'] == self.currently_open_file:
                    if self.last_save['time'] < datetime.fromtimestamp(
                            getmtime(self.currently_open_file)
                    ):
                        self.emit('note_changed', '')
                        return True
            with open(self.currently_open_file, 'w') as fd:
                fd.write(text_to_save)
                self.last_save = {
                    'file': self.currently_open_file,
                    'time': datetime.now()
                }
        return True

    def open_file(self, file_path, focus_editor=True, no_save=False):
        self.last_save = {
            'file': self.currently_open_file,
            'time': datetime.now()
        }
        self.source_view.set_sensitive(True)
        if not no_save:
            self.save_current_file()
        self.currently_open_file = file_path
        if isfile(file_path):
            with open(file_path, 'r') as fd:
                self.source_buffer.set_text(fd.read())
        else:
            with open(file_path, 'w') as fd:
                fd.write('')
            self.populate_listbox()
            self.source_buffer.set_text('')
        if focus_editor:
            self.source_view.grab_focus()
            file_name = None
            rows = self.results_listbox.get_children()
            for row in rows:
                if row.file_path == file_path:
                    file_name = row.name
                    break
            if file_name is None:
                file_name = file_path.split('/')[-1]
            self.search_entry.set_text(
                splitext(file_name)[0]
                if self.confman.conf['use_file_extension']
                else file_name
            )
