import sys
import re

syntax_description = """

A "span" is an integer (the "index") followed by an optional description of one
or more sequence subsets.  A subset is defined by a series of subset
descriptions, separated by a comma.  A subset description is either an integer,
two integers separated by a hypen to indicate a range, or an integer followed
only by a hyphen, which will include all the following elements of the span to
the end. No spaces are allowed in a span.  All indices are zero-origin.

Span examples for an index of "3":
   3 
   3(1)
   3(0-4)
   3(1-4,6-9)
   3(5-)

Note that some sequence subsets must include two numbers, for example, border
lines in a table.

""".strip()


class Span:
    def __init__(self, count, spec):
        def parse_range(match):
            start, hyphen, end = match.groups()
            if hyphen is None and end is None:
                end = start
            elif end is None:
                end = count - 1
            return [int(start), int(end)]
        self.all = True
        subset_pat = "[-\\d,]+"
        span_rgx = re.compile(f"(\d+)(\({subset_pat}\))*")
        match = span_rgx.match(spec)
        self.spec = spec
        if match and match.group(0) == spec:
            range_rgx = re.compile("(\d+)(-)?(\d+)?")
            self.index = int(match.group(1))
            if (match.group(2)):
                self.subsets = match.group(2).strip("()").split(",")
                matches = [range_rgx.match(e) for e in self.subsets]
                self.ranges = [parse_range(e) if e else None for e in matches]
                self.all = False
            else:
                self.ranges = [[0, count-1]]
        else:
            print(f'The span specification "{spec}" is incorrect.\n\n{syntax_description}\n')
            sys.exit(1)

    def __str__(self):
        subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
        return f"{self.index}[{subsets}]"

    def __repr__(self):
        return self.__str__()



class Spanset:
    def __init__(self, count, span_specs):
        self.count = count
        self.spans = {}
        for specs in span_specs.split():
            for spec in self.parse_spec(specs):
                print("Spanset spec:", spec)
                self.spans[spec.index] = spec


    def parse_spec(self, spec):
        named_spec = {"top" : ["0"],
                      "bottom" : [str(self.count-1)],
                      "head"   : ["1"],
                      "outer"  : ["0", str(self.count-1)],
                      "inner"  : [str(e) for e in range(1,self.count-1)],
                      "all"    : [str(e) for e in range(0,self.count+1)]
                      }.get(spec)
        if named_spec is None:
            return [Span(self.count, spec)]
        else:
            return [Span(self.count, e) for e in named_spec]


if __name__ == "__main__":
    import pprint
    for s in [
            Span(10, "0"),
            Span(10, "1"),
            Span(10, "2(2)"),
            Span(10, "3(2-)"),
            Span(10, "4(2-4)"),
            Span(10, "5(2-4)"),
            Span(10, "6(2-4,6)"),
            Span(10, "7(2,6-8)"),
            Span(10, "8(2,7-8,9-11,13-14)")]:
        print(s.spec, "->", s)

    print("Spanset")
    S = Spanset(10, "1")
    for name in "top bottom head outer inner all 1(2-3)".split():
        print(name, "->", S.parse_spec(name))

    print("Instantiate:")
    s = Spanset(10, "3(1-2,4-5)")
    print(s.tex_hline(3))


    
