import argparse
import curses
import os
import re
import signal
import sys
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple

from babi.buf import Buf
from babi.file import File
from babi.perf import Perf
from babi.perf import perf_log
from babi.screen import EditResult
from babi.screen import make_stdscr
from babi.screen import Screen

CONSOLE = 'CONIN$' if sys.platform == 'win32' else '/dev/tty'
POSITION_RE = re.compile(r'^\+-?\d+$')


def _edit(screen: Screen, stdin: str) -> EditResult:
    screen.file.ensure_loaded(screen.status, screen.margin, stdin)

    while True:
        screen.status.tick(screen.margin)
        screen.draw()
        screen.file.move_cursor(screen.stdscr, screen.margin)

        key = screen.get_char()
        if key.keyname in File.DISPATCH:
            File.DISPATCH[key.keyname](screen.file, screen.margin)
        elif key.keyname in Screen.DISPATCH:
            ret = Screen.DISPATCH[key.keyname](screen)
            if isinstance(ret, EditResult):
                return ret
        elif key.keyname == b'STRING':
            assert isinstance(key.wch, str), key.wch
            screen.file.c(key.wch, screen.margin)
        else:
            screen.status.update(f'unknown key: {key}')


def c_main(
        stdscr: 'curses._CursesWindow',
        filenames: List[Optional[str]],
        positions: List[int],
        stdin: str,
        perf: Perf,
) -> int:
    screen = Screen(stdscr, filenames, positions, perf)
    with screen.history.save():
        while screen.files:
            screen.i = screen.i % len(screen.files)
            res = _edit(screen, stdin)
            if res == EditResult.EXIT:
                del screen.files[screen.i]
                # always go to the next file except at the end
                screen.i = min(screen.i, len(screen.files) - 1)
                screen.status.clear()
            elif res == EditResult.NEXT:
                screen.i += 1
                screen.status.clear()
            elif res == EditResult.PREV:
                screen.i -= 1
                screen.status.clear()
            elif res == EditResult.OPEN:
                screen.i = len(screen.files) - 1
            else:
                raise AssertionError(f'unreachable {res}')
    return 0


def _key_debug(stdscr: 'curses._CursesWindow', perf: Perf) -> int:
    screen = Screen(stdscr, ['<<key debug>>'], [0], perf)
    screen.file.buf = Buf([''])

    while True:
        screen.status.update('press q to quit')
        screen.draw()
        screen.file.move_cursor(screen.stdscr, screen.margin)

        key = screen.get_char()
        screen.file.buf.insert(-1, f'{key.wch!r} {key.keyname.decode()!r}')
        screen.file.down(screen.margin)
        if key.wch == curses.KEY_RESIZE:
            screen.resize()
        if key.wch == 'q':
            return 0


def _filenames(filenames: List[str]) -> Tuple[List[Optional[str]], List[int]]:
    if not filenames:
        return [None], [0]

    ret_filenames: List[Optional[str]] = []
    ret_positions = []

    filenames_iter = iter(filenames)
    for filename in filenames_iter:
        if POSITION_RE.match(filename):
            # in the success case we get:
            #
            # position_s = +...
            # filename = (the next thing)
            #
            # in the error case we only need to reset `position_s` as
            # `filename` is already correct
            position_s = filename
            try:
                filename = next(filenames_iter)
            except StopIteration:
                position_s = '+0'
            ret_positions.append(int(position_s[1:]))
            ret_filenames.append(filename)
        else:
            ret_positions.append(0)
            ret_filenames.append(filename)

    return ret_filenames, ret_positions


def main(argv: Optional[Sequence[str]] = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument('filenames', metavar='filename', nargs='*')
    parser.add_argument('--perf-log')
    parser.add_argument(
        '--key-debug', action='store_true', help=argparse.SUPPRESS,
    )
    args = parser.parse_args(argv)

    if '-' in args.filenames:
        print('reading stdin...', file=sys.stderr)
        stdin = sys.stdin.read()
        tty = os.open(CONSOLE, os.O_RDONLY)
        os.dup2(tty, sys.stdin.fileno())
    else:
        stdin = ''

    # ignore backgrounding signals, we'll handle those in curses
    # fixes a problem with ^Z on termination which would break the terminal
    if sys.platform != 'win32':  # pragma: win32 no cover  # pragma: no branch
        signal.signal(signal.SIGTSTP, signal.SIG_IGN)

    with perf_log(args.perf_log) as perf, make_stdscr() as stdscr:
        if args.key_debug:
            return _key_debug(stdscr, perf)
        else:
            filenames, positions = _filenames(args.filenames)
            return c_main(stdscr, filenames, positions, stdin, perf)


if __name__ == '__main__':
    exit(main())