from datetime import datetime
import sys
import abc


class NIE(NotImplementedError):
    pass


class Tabby:

    def __init__(self):
        self.tab = 0

    def __iadd__(self, other):
        self.tab += other
        return self

    def __isub__(self, other):
        self.tab -= other
        if self.tab < 0:
            raise AssertionError("Tried to decrement tabby past 0")
        return self

    def __mul__(self, other):
        return self.tab * other

    def __rmul__(self, other):
        return self.tab * other

    def __eq__(self, other):
        return self.tab == other


class Tongue:

    __metaclass__ = abc.ABCMeta

    def __init__(self):
        self.tab = Tabby()
        self.ptab = 0
        self.fd = sys.stdout

    # ----------
    # properties
    # ----------
    @abc.abstractproperty
    def cmt_char(self):
        raise NIE

    @abc.abstractproperty
    def language(self):
        raise NIE

    @abc.abstractproperty
    def ext(self):
        raise NIE

    @abc.abstractproperty
    def this(self):
        raise NIE

    @abc.abstractproperty
    def null(self):
        raise NIE

    @abc.abstractproperty
    def true(self):
        raise NIE

    @abc.abstractproperty
    def false(self):
        raise NIE

    @property
    def tabby(self):
        if self.tab == 0:
            return ""

        return ("{:>" + str(self.tab * 4) + "}").format(" ")

    # -------
    # methods
    # -------
    @abc.abstractmethod
    def stmt(self, line, end="\n"):
        raise NIE

    @abc.abstractmethod
    def abstract_class(self, name, extends=None):
        raise NIE

    @abc.abstractmethod
    def class_(self, name, extends=None):
        raise NIE

    @abc.abstractmethod
    def classvar(self, prefix, name, val=None):
        raise NIE

    @abc.abstractmethod
    def attrvar(self, prefix, name, val=None):
        raise NIE

    @abc.abstractmethod
    def var(self, name, val=None):
        raise NIE

    @abc.abstractmethod
    def expand_args(self, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def constructor(self, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def function(self, name, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def method(self, name, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def static_method(self, name, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def abstract_static_method(self, name, *args, **kwargs):
        raise NIE

    @abc.abstractmethod
    def if_(self, condition):
        raise NIE

    @abc.abstractmethod
    def else_(self):
        raise NIE

    @abc.abstractmethod
    def end_if(self):
        raise NIE

    @abc.abstractmethod
    def end(self):
        raise NIE

    @abc.abstractmethod
    def return_(self, val=""):
        raise NIE

    @abc.abstractmethod
    def throw(self, name, msg):
        raise NIE

    @abc.abstractmethod
    def find_regex(self, match, regex, buf, offs):
        raise NIE

    @abc.abstractmethod
    def print(self, s, end="\n"):
        raise NIE

    @abc.abstractmethod
    def try_(self):
        raise NIE

    @abc.abstractmethod
    def except_any(self):
        raise NIE

    @abc.abstractmethod
    def nop(self):
        raise NIE

    @abc.abstractmethod
    def dict_new(self, name):
        raise NIE

    @abc.abstractmethod
    def dict_set(self, name, str_key, value, end):
        raise NIE

    @abc.abstractmethod
    def v_dict_get(self, name, str_key):
        raise NIE

    # ------------------------------------
    # these v_guys return rather than code
    # ------------------------------------
    @abc.abstractmethod
    def v_regex_group_zero(self, match):
        raise NIE

    @abc.abstractmethod
    def v_length(self, x):
        raise NIE

    @abc.abstractmethod
    def v_new(self, cls, *args):
        raise NIE

    @abc.abstractmethod
    def v_attr(self, attr):
        raise NIE

    @abc.abstractmethod
    def v_def_regex(self, regex):
        raise NIE

    @abc.abstractmethod
    def v_match_regex(self, regex, buf, offs):
        raise NIE

    @abc.abstractmethod
    def v_match(self, match):
        raise NIE

    def v_str(self, s):
        return '"' + s + '"'

    def v_sym(self, s):
        return "'" + s + "'"

    def raw(self, code):
        self._emit(code, end="")

    def file_header(self, filename, spell):
        self.cmt(box("{filename}{ext} - Generated by {spell} on {date} for {language}".format(
            filename=filename, ext=self.ext, spell=spell,
            date=str(datetime.now()).split(".")[0],
            language=self.language), "="))

    def cmt(self, words):
        pref = self.tabby + self.cmt_char + " "
        x = "\n" + pref
        words = pref + words.replace("\n", x)
        self._emit(words, end="\n")

    def cmt_hdr(self, text):
        self.cmt(box(text))

    def nl(self, n=1):
        for i in range(0, n):
            self._emit("", end="\n")

    def _emit(self, text, end=""):
        print(text, end=end, file=self.fd)

        if self.fd != sys.stdout:
            print(text, end=end)


class PyTongue(Tongue):

    def __init__(self):
        super().__init__()

    @property
    def cmt_char(self):
        return "#"

    @property
    def language(self):
        return "Python 3.3.2"

    @property
    def ext(self):
        return ".py"

    @property
    def this(self):
        return "self"

    @property
    def null(self):
        return "None"

    @property
    def false(self):
        return "False"

    @property
    def true(self):
        return "True"

    def stmt(self, line, end="\n"):
        self._emit(self.tabby + line, end=end)

    def if_(self, condition):
        self.stmt("if " + condition + ":")
        self.tab += 1

    def else_(self):
        self.tab -= 1
        self.stmt("else:", end="\n")
        self.tab += 1

    def return_(self, val=""):
        self.stmt("return " + val)

    def end_if(self):
        self.end()

    def end(self):
        self.tab -= 1
        self.nl()

    def expand_args(self, *args, **kwargs):
        a = ""
        for arg in args:
            if a != "":
                a += ", "
            a += arg

        for k, v in kwargs.items():
            if a != "":
                a += ", "
            a += k + "=" + v

        return a

    def constructor(self, *args, **kwargs):
        self.function("__init__", "self", *args, **kwargs)

    def function(self, name, *args, **kwargs):
        s = "def " + name + "("
        s += self.expand_args(*args, **kwargs) + "):"
        self.stmt(s)
        self.tab += 1

    def abstract_class(self, name, extends=None):
        self.class_(name, extends)
        self.stmt("__metaclass__ = abc.ABCMeta")

    def class_(self, name, extends=None):
        s = "class " + name
        if extends:
            s += "(" + extends + ")"
        s += ":"
        self.stmt(s)
        self.tab += 1

    def classvar(self, prefix, name, val=None):
        s = name
        if val:
            s += " = " + val
        self.stmt(s)

    def attrvar(self, prefix, name, val=None):
        # skip in python
        pass

    def var(self, name, val=None):
        if val:
            s = name + " = " + val
            self.stmt(s)

    def method(self, name, *args, **kwargs):
        self.function(name, "self", *args, **kwargs)

    def throw(self, name, msg):
        self.stmt("raise " + name + "(" + msg + ")")

    def abstract_static_method(self, name, *args, **kwargs):
        self.stmt("@staticmethod")
        self.stmt("@abc.abstractstaticmethod")
        self.function(name, *args, **kwargs)

    def static_method(self, name, *args, **kwargs):
        self.stmt("@staticmethod")
        self.function(name, *args, **kwargs)

    def find_regex(self, match, regex, buf, offs):
        self.stmt(match + " = " + self.v_match_regex(regex, buf, offs))

    def if_regex_match_not_found(self, regex, buf, offs):
        self.if_(self.v_match_regex(regex, buf, offs) + " is None")

    def print(self, s, end="\\n"):
        self.stmt("print(\"" + s + "\", end='" + end + "')")

    def try_(self):
        self.stmt("try:")
        self.tab += 1

    def except_any(self):
        self.tab -= 1
        self.stmt("except:")
        self.tab += 1

    def nop(self):
        self.stmt("pass")

    # ------------------------------------
    # these v_guys return rather than code
    # ------------------------------------
    def v_match_regex(self, regex, buf, offs):
        return regex + ".match(" + buf + ", " + offs + ")"

    def v_regex_group_zero(self, match):
        return match + ".group(0)"

    def v_new(self, cls, *args):
        a = self.expand_args(*args)
        return cls + "(" + a + ")"

    def v_ass(self, dst, src):
        return dst + " = " + src

    def v_length(self, x):
        return "len(" + x + ")"

    def v_attr(self, attr):
        return "self." + attr

    def v_def_regex(self, regex):
        return 're.compile(r"' + regex + '")'

    def v_match(self, match):
        return match

    def dict_new(self, name):
        self.stmt(name + " = dict()", end="\n")

    def dict_set(self, name, str_key, value, end="\n"):
        self.stmt(name + "['" + str_key + "'] = " + value, end=end)

    def v_dict_get(self, name, str_key):
        return name + "['" + str_key + "']"


# --------------------
# SuperCollider Tongue
# --------------------
class ScTongue(Tongue):

    def __init__(self):
        super().__init__()

    @property
    def cmt_char(self):
        return "//"

    @property
    def language(self):
        return "SuperCollider 3.7"

    @property
    def ext(self):
        return ".sc"

    @property
    def this(self):
        return "this"

    @property
    def null(self):
        return "nil"

    @property
    def true(self):
        return "true"

    @property
    def false(self):
        return "false"

    def stmt(self, line, end=";\n"):
        self._emit(self.tabby + line, end=end)

    def if_(self, condition):
        self.stmt("if (" + condition + ") {", end="\n")
        self.tab += 1

    def else_(self):
        self.tab -= 1
        self.stmt("} {", end="\n")
        self.tab += 1

    def end_if(self):
        self.tab -= 1
        self.stmt("}")
        self.nl()

    def end(self):
        self.tab -= 1
        self.stmt("}", end="\n")
        self.nl()

    def return_(self, val=""):
        self.stmt("^" + val)

    def function(self, name, *args, **kwargs):
        self.stmt(name + " {", end="\n")
        self.tab += 1

        a = self.expand_args(*args, **kwargs)
        if a != "":
            self.stmt("| " + a + " |", end="\n")
            self.nl()

    def abstract_static_method(self, name, *args, **kwargs):
        self.static_method(name, *args, **kwargs)

    def method(self, name, *args, **kwargs):
        self.function(name, *args, **kwargs)

    def static_method(self, name, *args, **kwargs):
        name = "*" + name
        self.function(name, *args, **kwargs)

    def constructor(self, *args, **kwargs):
        a = self.expand_args_just_keys(*args, **kwargs)

        self.static_method("new", *args, **kwargs)
        self.return_("super.new.init( " + a + " )")
        self.end()

        self.function("init", *args, **kwargs)

    def abstract_class(self, name, extends=None):
        self.class_(name, extends)

    def class_(self, name, extends=None):
        s = name
        if extends:
            s += " : " + extends
        s += " {"
        self.stmt(s, end="\n")
        self.tab += 1

    def classvar(self, prefix, name, val=None):
        s = prefix + name
        if val is not None:
            s += " = " + val

        self.stmt("classvar " + s)

    def attrvar(self, prefix, name, val=None):
        self.stmt("var " + prefix + name)

    def var(self, name, val=None):
        s = name
        if val is not None:
            s += " = " + val
        self.stmt("var " + s)

    def throw(self, name, msg):
        self.stmt(name + "(" + msg + ").throw")

    def print(self, s, end="\n"):
        self.stmt('"' + s + end + '".post')

    def if_regex_match_not_found(self, regex, buf, offs):
        self.if_(self.v_match_regex(regex, buf, offs) + " == " + self.false)

    def find_regex(self, match, regex, buf, offs):
        self.stmt(match + " = " + buf + ".findRegexpAt(" + regex + ", " + offs + ")")

    def try_(self):
        self.stmt("try {", end="\n")
        self.tab += 1

    def except_any(self):
        self.tab -= 1
        self.stmt("} {", end="\n")
        self.tab += 1

    def nop(self):
        self.stmt("// pass", end="\n")

    def dict_new(self, name):
        self.stmt(name + " = IdentityDictionary()", end="\n")

    def dict_set(self, name, str_key, value, end=");\n"):
        self.stmt(name + ".put('" + str_key + "', " + value, end=end)

    # ------------------------------------
    # these v_guys return rather than code
    # ------------------------------------
    def v_dict_get(self, name, str_key):
        return name + "['" + str_key + "']"

    def v_match_regex(self, regex, buf, offs):
        return regex + ".matchRegexp(" + buf + "," + offs + ")"

    def v_ass(self, dst, src):
        return dst + " = " + src

    def v_regex_group_zero(self, match):
        return match + "[0][1]"

    def v_new(self, cls, *args):
        a = self.expand_args(*args)
        return cls + ".new(" + a + ")"

    def v_length(self, x):
        return x + ".size"

    def v_attr(self, attr):
        return attr

    def v_def_regex(self, regex):
        return '"' + regex.replace("\\", "\\\\") + '"'

    def v_match(self, match):
        return self.v_length(match) + " > 0"

    def expand_args(self, *args, **kwargs):
        a = ""
        for arg in args:
            if a != "":
                a += ", "
            a += arg

        for k, v in kwargs.items():
            if a != "":
                a += ", "
            a += k + "=" + v

        return a

    def expand_args_just_keys(self, *args, **kwargs):
        a = ""
        for arg in args:
            if a != "":
                a += ", "
            a += arg

        for k, v in kwargs.items():
            if a != "":
                a += ", "
            a += k + "=" + v

        return a


def box(text, char="-"):
    n = len(text)

    line = ("{:" + char + ">" + str(n) + "}").format(char)

    return "{line}\n{text}\n{line}".format(line=line, text=text)

SC = None
PY = None
tongues = []


def init():
    global SC, PY, tongues

    SC = ScTongue()
    PY = PyTongue()

    tongues = [SC, PY]
