# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

"""Parser pour un fichier de configuration

Syntaxe:
~~~
# comment
object id var=value
  continuation="line starting with a space"
  -link otherid

-link objectref1 predicate -link objectref2
~~~
"""

__all__ = (
    'split_namev', 'split_nvalue', 'split_nlist',
    'Parser',
)

import logging; log = logging.getLogger(__name__)
import sys, re

from .utils import *
from .lexer import *
from .objects import *

RE_NAMETYPE = re.compile(r'(\S+):(\w+)$')
RE_NAMEVALUE = re.compile(r'(\S+)=(\S*)')

def split_namev(arg, sep=None):
    """spliter un argument de la forme
        name[method][=value]

    Si value n'est pas spécifié, il vaut None
    method peut être % (reset), + (add), - (del) et sa valeur par défaut est ADD_UNIQUE

    si sep is not None, splitter values sur cette valeur
    """
    if '=' in arg:
        name, value = arg.split('=', 1)
        if sep is not None:
            value = value.split(sep)
    else:
        name = arg
        value = None
    if name.endswith('%'):
        name = name[:-1]
        method = RESET_ADD
    elif name.endswith('+'):
        name = name[:-1]
        method = ADD
    elif name.endswith('-'):
        name = name[:-1]
        method = REMOVE
    else:
        method = ADD_UNIQUE
    mo = RE_NAMETYPE.match(name)
    if mo is not None:
        name, type = mo.groups()
    else:
        type = None
    return name, value, method

def split_nvalue(arg):
    """spliter un argument de la forme
        [name=]value

    Si name n'est pas spécifié, il vaut None
    """
    if '=' in arg:
        name, value = arg.split('=', 1)
    else:
        name = None
        value = arg
    return name, value

def split_nlist(arg):
    """spliter un argument de la forme
        [name=]values

    Si name n'est pas spécifié, il vaut None
    values est un ensemble de valeurs séparées par des virgules
    """
    if '=' in arg:
        name, values = arg.split('=', 1)
        values = values.split(',')
    else:
        name = None
        values = arg.split(',')
    return name, values

def split_list(arg):
    """spliter un argument de la forme
        values

    values est un ensemble de valeurs séparées par des virgules
    """
    return arg.split(',')

class LoneError(ValueError):
    """Exception lancée quand un verbe attend un argument inexistant
    """

class Parser(object):
    args = None

    def __init__(self, file=None):
        if file is not None:
            self.parse(file=file)

    def parse(self, predicates=None, file=None):
        if file is not None:
            lexer = Lexer(file)
            predicates = lexer.get_predicates()
        for args in predicates:
            self.args = args
            if self.isa_link(): self.handle_fact()
            else: self.handle_object()
        return self

    def eop(self):
        return not self.args
    def isa_verb(self):
        return self.args and not self.args[0].startswith('-')
    def isa_link(self):
        return self.args and self.args[0].startswith('-')
    def isa_namevalue(self):
        return self.args and RE_NAMEVALUE.match(self.args[0]) is not None

    def pop(self, desc=None):
        arg = self.args.pop(0)
        if desc is not None and self.eop():
            log.warning("lone %s '%s' was ignored", desc, arg)
            raise LoneError(arg)
        return arg
    def pop_link(self, desc=None):
        if not self.isa_link():
            raise ValueError("expected -otype")
        return self.pop(desc)[1:]
    def pop_namev(self, sep=None):
        return split_namev(self.pop(), sep)
    def pop_nvalue(self):
        return split_nvalue(self.pop())
    def pop_nlist(self):
        return split_nlist(self.pop())
    def pop_list(self):
        return split_list(self.pop())

    def handle_object(self):
        # créer ou mettre à jour un objet
        try:
            otype = self.pop("object type")
            if self.isa_namevalue():
                oid, values, method = self.pop_namev(',')
            else:
                oid = self.pop()
                values = ()
                method = ADD_UNIQUE
            object = catalog.create_object(otype, oid)
            object.update('values', values, method)
            while not self.isa_link():
                if self.eop(): break
                name, value, method = self.pop_namev()
                if value is None: value = 'true'
                object.update(name, value, method)
            while self.isa_link():
                ltype = self.pop_link()
                lids = self.pop_list()
                links = [object.linkto(lid, ltype) for lid in lids]
                while not self.isa_link():
                    if self.eop(): break
                    name, value, method = self.pop_namev()
                    if value is None: value = 'true'
                    for link in links:
                        link.update(name, value, method)
        except LoneError:
            pass

    def handle_fact(self):
        # créer un ou plusieurs liens
        try:
            sotype = self.pop_link("source link type")
            soids = self.pop_list()
            defo = Object() # pour les attributs par défaut
            while self.isa_namevalue():
                name, value, method = self.pop_namev()
                if value is None: value = 'true'
                defo.update(name, value, method)
            while not self.eop():
                verb = self.pop("verb")
                totype = self.pop_link("dest link type")
                toids = self.pop_list()
                facts = []
                for soid in soids:
                    for toid in toids:
                        fact = catalog.create_fact(sotype, soid, verb, totype, toid)
                        fact.update(defo.attrs)
                        facts.append(fact)
                while self.isa_namevalue():
                    name, value, method = self.pop_namev()
                    if value is None: value = 'true'
                    for fact in facts:
                        fact.update(name, value, method)
        except LoneError:
            pass

    def parse_attrs(self, namevalues, object):
        """analyser une liste de définition d'attributs name=value et mettre à jour
        object
        """
        self.args = listof(namevalues)
        while not self.eop():
            name, value, method = self.pop_namev()
            if value is None: value = 'true'
            object.update(name, value, method)
