#!/usr/bin/python3


import signal, sys, re
import tarfile, io, os
import getpass

def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)


try:
    import requests
    pass
except ImportError:
    print('Vous devez installer le package requests pour faire fonctionner ce programme:\n')
    print('   pip3 install requests')
    exit(1)


def safe_get(url, *args, **kwargs):
    try:
        r = requests.get(url, *args, **kwargs, timeout=8)
        if not r:
            print("An error occurred (are you connected to the Internet?)")
            exit(1)
        return r
    except:
        print("An error occurred (are you connected to the Internet?)")
        exit(1)


if '--help' in sys.argv:
    print("Options:")
    print("  --help: display this message")
    print("  --update: update this script")
    print("  --remove-key: remove the current key from the keyring")
    print()
    print("""To start the program with a specific key:
  MOODLE_API_KEY=your_key pyhon3 prepare.py
          
To store the key in you .bashrc file:
    echo 'export MOODLE_API_KEY=your_key' >> ~/.bashrc
    source ~/.bashrc
          
and then simply run:
    python3 prepare.py
    """)
    exit(0)


if '--update' in sys.argv:
    print("Updating 📑")
    r = safe_get('https://git.unistra.fr/-/snippets/132/raw')
    with open(__file__, 'w') as f:
        f.write(r.text)
        print("Done 🎉")

    exit(0)


from abc import ABC, abstractmethod
from datetime import datetime, timezone


'''
classes from
https://github.com/lukewarlow/PythonConsoleMenu
'''

class OperationError(Exception):
    def __init__(self):
        super().__init__("Invalid operation")


class AbstractMenu(ABC):
    def __init__(self, title: str):
        self.title = title
        self.menu_items = []
        self.initialise()

    @abstractmethod
    def initialise(self):
        pass

    def update_menu_items(self):
        pass

    def item_text(self, item: 'MenuItem'):
        return "%s" % item.description

    def item_line(self, index: int, item: 'MenuItem'):
        return "%s. %s" % (index, self.item_text(item))

    def display(self):
        repeat: bool = True
        while repeat:
            self.update_menu_items()
            print()
            print(self.title)
            for i in range(0, len(self.menu_items)):
                if self.menu_items[i].isVisible:
                    print(self.item_line(i, self.menu_items[i]))

            inp = input("Select Option: ")
            try:
                menu_item = self.menu_items[int(inp)]
                if menu_item.isVisible:
                    repeat = menu_item.run()
                else:
                    raise OperationError()
            except ValueError:
                print("Invalid option, you need to enter a number.", inp)
                repeat = True
            except IndexError:
                print("Invalid option. Option {0} doesn't exist.".format(inp))
                repeat = True
            except OperationError:
                print("Invalid option. Option at {0} is hidden.".format(inp))
                repeat = True

    def add_menu_item(self, menu_item: 'MenuItem'):
        if not self.menu_items.__contains__(menu_item):
            self.menu_items.append(menu_item)
        else:
            raise ValueError("Menu item with id {0} already exists!.".format(menu_item.id))

    def add_hidden_menu_item(self, menu_item: 'MenuItem'):
        self.add_menu_item(menu_item.hide())

    def show_menu_item(self, item_id: int):
        try:
            menu_item = MenuItem(item_id)
            index = self.menu_items.index(menu_item)
            self.menu_items[index].show()
        except ValueError:
            print("Error showing menu item. Menu item with ID {0} hasn't been added to this menu.".format(item_id))

    def hide_menu_item(self, item_id: int):
        try:
            menu_item = MenuItem(item_id)
            index = self.menu_items.index(menu_item)
            self.menu_items[index].hide()
        except ValueError:
            print("Error hiding menu item. Menu item with ID {0} hasn't been added to this menu.".format(item_id))


class MenuItem:
    def __init__(self, id: int, description: str = "", action: callable(None) = None, menu: AbstractMenu = None, args: list = []):
        self.id: int = id
        self.description: str = description
        self.action = action
        self.menu: AbstractMenu = menu
        self.isExitOption: bool = False
        self.isVisible: bool = True
        self.args: list = args

    def hide(self) -> 'MenuItem':
        self.isVisible = False
        return self

    def show(self) -> 'MenuItem':
        self.isVisible = True
        return self

    def set_as_exit_option(self) -> 'MenuItem':
        self.isExitOption = True
        return self

    def run(self) -> bool:
        if self.action is not None:
            self.action(*self.args)
        elif self.menu is not None:
            self.menu.display()

        return not self.isExitOption

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.id == other.id
        else:
            return False

    def __ne__(self, other):
        return not self.__eq__(other)




url_args = []
for i in range(len(sys.argv) - 1):
    if sys.argv[i] == '--code':
        url_args.append('code='+sys.argv[i+1])

for i in range(len(sys.argv)):
    if sys.argv[i] == '--force':
        url_args.append('force=1')

url_args = "&".join(url_args)


print('Téléchargement de la liste des problèmes ...')
#r = safe_get('https://api.tps.bramas.fr/api/problems'+code)


r = safe_get('https://tps.bramas.fr/problems?'+url_args)
problems = r.json()


problemsGrouped = {}

for p in problems:
    s = p['name'].split('/')
    if s[0] not in problemsGrouped:
        problemsGrouped[s[0]] = []
    problemsGrouped[s[0]].append(p)


def download(path, url):
    print('Téléchargement de '+path+' ...', end='')
    z_r = safe_get(url, stream=True)
    t = tarfile.open(fileobj=z_r.raw, mode='r:gz')

    if os.path.exists(path):
        print('Attention, le dossier existe, certains fichiers risquent d\être remplacés.\n')
        print('Voulez vous écraser les fichiers existant (sans risque si vous avez commit vos modifications)\n(y/n)')
        if input() != 'y':
            print('Annulé!!')
            input('appuyer sur Entrée pour continuer')
            return

    t.extractall(path)
    print('OK\n')
    input('appuyer sur Entrée pour continuer')

def remainingTime(dt):
    if not dt: return ''
    dat = datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S%z")
    #dat = datetime.fromisoformat(dt)
    #dat = dat.replace(tzinfo=timezone.utc)
    diff = dat - datetime.now(dat.tzinfo)
    if diff.total_seconds() < 0:
        return 'Passé'
    return str(diff).split('.')[0]


class GroupMenuSubMenu(AbstractMenu):
    group = None
    _hideExisting = True
    _toggleExistingVisibilityItem = None

    def __init__(self, group):
        self.group = group
        super().__init__("Liste "+str(group))

    
    def toggleExistingVisibility(self):
        if self._hideExisting:
            self.showExisting()
        else:
            self.hideExisting()

    def hideExisting(self):
        self._hideExisting = True
        self._toggleExistingVisibilityItem.description = "Show existing problems"
        for p in problemsGrouped[self.group]:
            if  os.path.exists(p['name']):
                self.hide_menu_item(p['menu_id'])

    def showExisting(self):
        self._hideExisting = False
        self._toggleExistingVisibilityItem.description = "Hide existing problems"
        for p in problemsGrouped[self.group]:
            self.show_menu_item(p['menu_id'])

    def initialise(self):
        self.add_menu_item(MenuItem(0, "<< Back").set_as_exit_option())
        self._toggleExistingVisibilityItem = MenuItem(1, "", action=self.toggleExistingVisibility)
        self.add_menu_item(self._toggleExistingVisibilityItem)
        idx = 1
        for p in problemsGrouped[self.group]:
            idx += 1
            text = ("*"*p['difficulty']).ljust(6,' ') +' '+p['name']
            if p['deadline']:
                text += ' ('+remainingTime(p['deadline'])+')'
            m = MenuItem(idx, text, lambda: download(p['name'], p['url']))
            self.add_menu_item(m)
            p['menu_id'] = idx

        self.hideExisting()


class GroupMenu(AbstractMenu):
    subMenus = []
    _hideExisting = True
    _toggleExistingVisibilityItem = None

    def __init__(self):
        super().__init__("Welcome to the test menu.")

    def showGroup(self, group):
        if group in self.groupVisible:
            self.groupVisible[group] = False
        self.groupVisible[group] = True
        for p in problemsGrouped[group]:
            self.hide_menu_item(p['menu_id'])
        print('show', group)
    
    def hideExisting(self):
        self._hideExisting = True
        self._toggleExistingVisibilityItem.description = "Show existing problems"
        for m in self.subMenus:
            m.menu.hideExisting()

    def showExisting(self):
        self._hideExisting = False
        self._toggleExistingVisibilityItem.description = "Hide existing problems"
        for m in self.subMenus:
            m.menu.showExisting()

    def toggleExistingVisibility(self):
        if self._hideExisting:
            self.showExisting()
        else:
            self.hideExisting()


    def initialise(self):
        self.add_menu_item(MenuItem(0, "Exit menu").set_as_exit_option())
        self._toggleExistingVisibilityItem = MenuItem(1, "", action=self.toggleExistingVisibility)
        self.add_menu_item(self._toggleExistingVisibilityItem)
        idx = 1
        for group, problems in problemsGrouped.items():

            #if not displayAll and os.path.exists(p['name']) and not self.seeAll:
            #    continue
            

            idx += 1
            m = MenuItem(idx, group, menu=GroupMenuSubMenu(group))
            self.add_menu_item(m)
            self.subMenus.append(m)
            
        self.hideExisting()


class ListMenu(AbstractMenu):
    _hideExisting = True
    _toggleExistingVisibilityItem = None

    def __init__(self):
        super().__init__("Bienvenu dans le gestionnaire de TP")

    def updateVisibility(self):
        if self._hideExisting:
            for p in problems:
                if  os.path.exists(p['name']):
                    self.hide_menu_item(p['menu_id'])
        else:
            for p in problems:
                self.show_menu_item(p['menu_id'])
    
    def hideExisting(self):
        self._hideExisting = True
        self._toggleExistingVisibilityItem.description = \
            "\t        🙉 Montrer les problèmes existants"
        self.updateVisibility()

    def showExisting(self):
        self._hideExisting = False
        self._toggleExistingVisibilityItem.description = \
            "\t        🙈 Cacher les problèmes existants"
        self.updateVisibility()

    def toggleExistingVisibility(self):
        if self._hideExisting:
            self.showExisting()
        else:
            self.hideExisting()

    def selectProblem(self, name, url):
        download(name, url)
        self.updateVisibility()

    def initialise(self):
        self.add_menu_item(MenuItem(0, "\t        ❌ Quitter").set_as_exit_option())
        self._toggleExistingVisibilityItem = MenuItem(1, "", action=self.toggleExistingVisibility)
        self.add_menu_item(self._toggleExistingVisibilityItem)
        
        idx = 1
        for p in problems:
            idx += 1
            text = ("⭐"*p['difficulty']).ljust(6,' ') +'\t'
            rtime = remainingTime(p['deadline'])
            if rtime != '':
                text += '🕓 '
            else:
                text += '   '
            text += p['name']

            if p['deadline']:
                text += ' ('+rtime+')'
            m = MenuItem(idx, text, action=self.selectProblem, args=[p['name'], p['url']])
            self.add_menu_item(m)
            p['menu_id'] = idx
            
        self.hideExisting()



if '--download' in sys.argv:
    name = 'TP-sans-nom'
    if '--name' in sys.argv:
        name = sys.argv[sys.argv.index('--name')+1]
    url = sys.argv[sys.argv.index('--download')+1]
    download(name, url)
    exit(0)


demoMenu = ListMenu()
demoMenu.display()