from __future__ import print_function as _
from __future__ import absolute_import as _

import re
import sys

IS_PY3 = sys.version_info[0] > 2

if IS_PY3:
    # unicode can't be memoryviewed, so just slice
    buffer = lambda s, i=0: s[i:]

from pymol import cmd, parsing

DEBUG = False

STRICT = [parsing.STRICT, parsing.ABORT, parsing.SECURE, parsing.LEGACY]
LITERAL = [
    parsing.LITERAL, parsing.LITERAL1, parsing.LITERAL2, parsing.MOVIE,
    parsing.PYTHON
]
MULTILINE = [parsing.PYTHON_BLOCK, parsing.EMBED, parsing.SKIP]

brackopen = {'(': ')', '[': ']', '{': '}'}


def fancysplit(s, delim=',', stop=(';', '\n'), maxsplit=-1):
    stack = []
    quote = ''
    tristart = -1
    escaped = False

    for i, c in enumerate(s):
        if i == maxsplit:
            break

        if escaped:
            escaped = False
        elif c == '\\':
            escaped = True
        elif quote:
            if c == quote:
                if tristart == -1:
                    quote = ''
                elif i > tristart + 2 and s[i + 1:i + 3] == c + c:
                    tristart = -1
                    quote = ''
        elif c in ('"', "'"):
            quote = c
            if s[i + 1:i + 3] == c + c:
                tristart = i
        elif c in brackopen:
            stack.append(brackopen[c])
        elif stack:
            if c == stack[-1]:
                stack.pop()
        elif c == delim:
            yield i
        elif c in stop:
            yield i
            return

    if stack and DEBUG:
        print('warning: unmatched quotes')

    yield len(s)


def findnewline(s):
    m = re.search(r'(?:^|[^\\])$', s, re.M)
    if m is None:
        return len(s)
    return m.end() + 1


def parse_pml(text):
    if not (IS_PY3 or isinstance(text, bytes)):
        text = text.encode('ascii', 'ignore')

    i = 0
    L = len(text)

    while i < L:
        expr_start = i

        m = re.match(r'(_ )?[ \t]*($|#|/|@|[^\s;]+)', buffer(text, i), re.M)
        if m is None:
            break

        com = m.group(2)

        hit = {
            'type': 'python',
            'quiet': m.group(1) is not None,
            'command': (m.start(2) + i, m.end(2) + i),
            'multiline': 0,  # 0, 1 (closed), 2 (open)
        }

        mode = parsing.PYTHON

        if com == '':
            i += m.end() + 1
            continue
        elif com == '/':
            pass
        elif com == '#':
            # TODO could be handled as command
            hit['type'] = 'comment'
        elif com == '@':
            # TODO could be handled as command
            hit['type'] = 'command'
            mode = parsing.STRICT
        else:
            com = cmd.kwhash.shortcut.get(com, com)
            if com in cmd.keyword:
                mode = cmd.keyword[com][4]

            if mode != parsing.PYTHON:
                hit['type'] = 'command'
            else:
                i += m.start() - m.end()

        i += m.end()
        arg_i = [i]

        if mode in LITERAL:
            i += findnewline(buffer(text, i))
            arg_i.append(i)
        else:
            arg_i.extend((i + p) for p in fancysplit(buffer(text, i)))
            i = arg_i[-1] + 1

            if mode in MULTILINE:
                m = re.search(r'^(_ )?[ \t]*' + com + r' end\b',
                              buffer(text, arg_i[-1]), re.M)
                if m is None:
                    hit['multiline'] = 2
                    i = len(text)
                    arg_i.append(i)
                else:
                    hit['multiline'] = 1
                    arg_i.append(i + m.start())
                    i += m.end()

        hit['args'] = arg_i
        hit['expr'] = expr_start, i

        yield hit
