# script.py
# Runs a selection script

# Copyright (c) 2005 by Matthias Urlichs <smurf@smurf.noris.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of Version 2 of the GNU General Public License as
# published by the Free Software Foundation. See the file COPYING.txt
# or (on Debian systems) /usr/share/common-licenses/GPL-2 for details.

"""\
The Script class encapsulates the interpreter for the keymap selection
script generated by FileReporter.

The .run method will either return the keymap's name, or an error
exception will be raised.

"""

from __future__ import print_function


def osym(s):
    if s == "<del>":
        return u"\x7F"
    elif s == "<spc>":
        return u" "
    elif s == "<lf>":
        return u"\x0A"
    elif s == "<cr>":
        return u"\x0D"
    elif s == "<bs>":
        return u"\x08"
    elif s == "<tab>":
        return u"\x09"
    else:
        return s


class UnknownLine(Exception):
    """The script file contains a line that's not parseable"""

    pass


class BadFile(Exception):
    """The script file isn't well-formed"""

    pass


class NoMap(Exception):
    """No result!"""

    pass


class DupMap(Exception):
    """Inconclusive result!"""

    pass


class Script(object):
    """Run this script with that query. Return the result."""

    def __init__(self, file, verbose=1):
        self.file = file
        self.in_step = -1
        self.verbose = verbose

    def get_step(self, nrs):
        """Return the data for step NR."""
        reported = False
        if self.verbose > 1:
            print("want", sorted(nrs), ", have", self.in_step)
        for f in self.file:
            f = f.strip()
            if f == "" or f.startswith("#"):
                continue
            if f.startswith("STEP"):
                if self.verbose > 1:
                    print(f)
                f = f[4:]
                f = int(f.strip())
                if self.in_step in nrs:
                    nrs.remove(self.in_step)  # done with this one
                    self.in_step = f
                    return
                self.in_step = f
            elif self.in_step in nrs:
                if self.verbose:
                    if not reported:
                        print("Step", self.in_step)
                    reported = True
                if self.verbose > 1:
                    print("have", f)
                yield f
            elif self.verbose > 1:
                print("skip", f)

        # ran off the end of the file?
        if self.in_step not in nrs:
            raise BadFile(nrs)
        nrs.remove(self.in_step)  # done, at EOF

    def run_step(self, query, step, res):
        """Run a singe step.
        Returns the next step's number, or raise an exception."""
        what = None
        syms = []
        keys = {}

        # First, collect the data
        for line in self.get_step(step):
            if line.startswith("PRESS") and what in (None, "press"):
                what = "press"
                syms.append(osym(line[5:].strip()))
            elif line.startswith("CODE") and what in (None, "press"):
                what = "press"
                try:
                    code, goal = line[4:].strip().split()
                except ValueError:
                    print("BAD LINE <" + line.strip() + ">")
                    continue
                keys[int(code)] = int(goal)
            elif line.startswith("MAP") and what in (None,):
                res.add(line[3:].strip())
            elif line.startswith("FIND") and what in (None,):
                what = "find1"
                find = line[4:].strip().split()
            elif line.startswith("YES") and what in ("find1",):
                what = "find2"
                dest_yes = int(line[4:].strip())
            elif line.startswith("NO") and what in ("find2",):
                what = "find3"
                dest_no = int(line[3:].strip())
            else:
                raise UnknownLine(line.strip())

                # Second, decide what to do
        if what == "press" and syms and keys:
            codes = query.press(syms)
            for code in codes:
                if code in keys:
                    step.add(keys[code])
                    if self.verbose:
                        print("Goto step step", keys[code])
                else:
                    query.message("Unexpected key code: <%d>" % code)
        elif what == "find3":
            if query.ask([osym(f) for f in find]):
                step.add(dest_yes)
                if self.verbose:
                    print("Goto step step", dest_yes)
            else:
                step.add(dest_no)
                if self.verbose:
                    print("Goto step step", dest_no)

    def run(self, query):
        """Run this script, starting with step 0."""
        self.file.seek(0, 1)  ## Python 2.4 regression workaround:
        self.file.read()  ## flush codec's input buffers
        self.file.seek(0, 0)
        self.in_step = -1
        step = set((0,))
        maps = set()
        while step:
            self.run_step(query, step, maps)
        if not maps:
            raise NoMap
        if len(maps) != 1:
            raise DupMap(list(maps))
        return sorted(maps)[0]
