#!/usr/bin/env python3
import argparse
import curses
from pathlib import Path
import subprocess
import sys
from time import sleep

# Configuration

text_album_current = "Aktuelles Album"
text_album_root = "(Alle Fotoalben)"
text_closed_pictures = "Gehe zurück ..."
text_load_pictures = "Bilder werden geladen ..."
text_no_pictures_found = "Hier sind leider noch keine Bilder drinnen."

wait_no_pictures_found = 5 # seconds

indicator = "➤"

binding_up = [ord('k'), curses.KEY_UP, curses.KEY_PPAGE]
binding_down = [ord('j'), curses.KEY_DOWN, curses.KEY_NPAGE]
binding_accept = [ord('l'), ord(' '), ord('\n'), curses.KEY_HOME]
binding_back = [ord('h'), ord('q'), curses.KEY_BACKSPACE, curses.KEY_END]
binding_exit = [ord('Q')]

# Code

def stretch(text, length, char = ' '):
    missing = length - len(text)
    if 0 < missing:
        text += char * missing
    elif missing < 0:
        text = text[0:length]
    return text

def build_description(base_dir, desc_path):
    rel_path = desc_path.relative_to(base_dir)
    parts = list(rel_path.parts[:-1])
    parts.append(rel_path.stem)
    parts = [part.replace('_', ' ') for part in parts]
    return ' - '.join(parts)

def show_pictures(files):
    files = list(files) # copy list
    files.insert(0, '/usr/bin/imvr')
    subprocess.call(files)
    return True

def search_pictures(out, path):
    files = sorted([str(f) for f in path.iterdir() if f.is_file() and f.suffix.lower() in ['.png', '.jpg', '.jpeg', '.gif']])
    if len(files) <= 0:
        out(text_no_pictures_found)
        sleep(wait_no_pictures_found)
        return True
    out(text_load_pictures)
    ret = show_pictures(files)
    out(text_closed_pictures)
    return ret

def draw_select(win, dirs, sel):
    y_size, x_size = win.getmaxyx()
    # centered scroll
    y_center = y_size // 2
    for y in range(y_size):
        i = sel + y - y_center
        if i < 0 or len(dirs) <= i:
            win.addstr(y, 1, " " * (x_size - 2), curses.A_NORMAL)
        else:
            text = " "
            text += indicator if i == sel else (" " * len(indicator))
            text += " "
            text += dirs[i].name
            text = stretch(text, x_size - 2)
            text_format = curses.A_STANDOUT if i == sel else curses.A_NORMAL
            win.addstr(y, 1, text, text_format)
    return True

def draw_header(win, base_dir, path):
    x_size = win.getmaxyx()[1]
    desc = build_description(base_dir, path)
    text = text_album_current + ": "
    text += desc if len(desc) > 0 else text_album_root
    win.addstr(1, 1, stretch(text, x_size - 2))

def show_select(win, base_dir, path):
    if not path.is_dir():
        raise Exception("'" + str(path) + "' is not a directory!")
    dirs = sorted([d for d in path.iterdir() if d.is_dir()])
    win.clear()
    if len(dirs) <= 0:
        draw_header(win, base_dir, path)
        x_size = win.getmaxyx()[1]
        def out(text):
            win.addstr(3, 1, stretch(text, x_size - 2))
            win.refresh()
        return search_pictures(out, path)
    sel = 0
    while True:
        draw_header(win, base_dir, path)
        draw_select(win.subwin(3, 0), dirs, sel)
        c = win.getch()
        if c in binding_up:
            sel = max(0, sel - 1)
        elif c in binding_down:
            sel = min(len(dirs) - 1, sel + 1)
        elif c in binding_accept:
            if not show_select(win, base_dir, dirs[sel]):
                return False
            win.clear()
        elif c in binding_back:
            return True
        elif c in binding_exit:
            return False

def select_ui(win, args):
    curses.curs_set(0)
    while show_select(win, args.dir, args.dir) and args.kiosk:
        pass
    if args.kiosk:
        subprocess.call(['systemctl', 'poweroff'])
    return True # exited as expected

def retry(fun):
    last_return = False
    while not last_return:
        try:
            last_return = fun()
        except:
            pass # TODO save log file with error

def main():
    # parse args
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument('--base-dir', type=Path, dest='dir', required=True)
    parser.add_argument('--kiosk', dest='kiosk', action='store_true')
    parser.add_argument('--build-desc-of', type=Path, dest='desc_path', default=None)
    args = parser.parse_args()
    # build description if requested
    if args.desc_path is not None:
        print(build_description(args.dir, args.desc_path))
        return
    # run menus in protective mode if kiosk mode is enabled
    def run():
        curses.wrapper(select_ui, args)
    if args.kiosk:
        retry(run)
    else:
        run()

if __name__ == '__main__':
    main()
