#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This is an experimental property parser
"""

import os
import ply.lex as lex
import ply.yacc as yacc
from .containers import CaselessDict
from .log import Logger, deprecated


deprecated(dep="taurus.core.util.property_parser", rel="5.0.0")

reserved = CaselessDict(
    {"true": "true", "false": "false", "yes": "yes", "no": "no"}
)

tokens = [
    "EQUALS",
    "KEY",
    "STRING",
    "NUMBER",
    "COMMA",
    "LLST",
    "RLST",
    # 'LBRACKET',
    # 'RBRACKET',
] + list(reserved.values())

t_EQUALS = r"\="
t_LLST = r"\["
t_RLST = r"\]"
t_COMMA = r"\,"
# t_LBRACKET = r'\{'
# t_RBRACKET = r'\}'


def t_NUMBER(t):
    r"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?"
    try:
        if t.value.count(".") == 0:
            try:
                t.value = int(t.value)
            except Exception:
                t.value = float(t.value)
        else:
            t.value = float(t.value)
    except Exception:
        print("[%d]: Number %s is not valid!" % (t.lineno, t.value))
        t.value = 0
    return t


# The following backslash sequences have their usual special meaning: \",
# \\, \n, and \t.


def t_STRING(t):
    r'\"([^\\"]|(\\.))*\"'
    escaped = 0
    str = t.value[1:-1]
    new_str = ""
    for i in range(0, len(str)):
        c = str[i]
        if escaped:
            if c == "n":
                c = "\n"
            elif c == "t":
                c = "\t"
            new_str += c
            escaped = 0
        else:
            if c == "\\":
                escaped = 1
            else:
                new_str += c
    t.value = new_str
    return t


def t_KEY(t):
    r"[a-zA-Z0-9/_\.\/]+"
    t.type = reserved.get(t.value, "KEY")  # Check for reserved words
    return t


# Ignored characters
t_ignore = " \t"

t_ignore_COMMENT = r"\#.*"


def t_newline(t):
    r"\n+"
    t.lexer.lineno += t.value.count("\n")


def t_error(t):
    print("[%d]: Illegal character '%s'" % (t.lexer.lineno, t.value[0]))
    t.lexer.skip(1)


def p_error(p):
    print("[%d]: Syntax error in input [%s]" % (p.lineno, (str(p))))


# -------------------------------------------------------------------------
# Yacc Starting symbol
# -------------------------------------------------------------------------


def p_property_file(p):
    """property_file : statement_list
    |
    """
    if len(p) == 1:
        p[0] = {}
    else:
        p[0] = p[1]


def p_statement_list(p):
    """statement_list : statement_list statement"""
    p[1].update(p[2])
    p[0] = p[1]


def p_statement(p):
    """statement_list : statement"""
    p[0] = p[1]


def p_statement_assign(p):
    """statement : KEY EQUALS value"""
    p[0] = {p[1]: p[3]}


def p_value(p):
    """value : atomic
    | list
    """
    p[0] = p[1]


def p_value_atomic(p):
    """atomic : KEY
    | STRING
    | NUMBER
    | boolean
    """
    p[0] = p[1]


def p_value_boolean(p):
    """boolean : true
    | false
    | yes
    | no
    """
    v = p[1].lower()
    p[0] = v in ("true", "yes")


def p_value_list(p):
    """list : LLST value_list RLST"""
    p[0] = p[2]


def p_value_list_elems1(p):
    """value_list : value_list COMMA value"""
    p[0] = p[1] + [p[3]]


def p_value_list_elems2(p):
    """value_list : value"""
    p[0] = [p[1]]


class PropertyParser(Logger):
    def __init__(self, parent=None):
        name = self.__class__.__name__
        self.call__init__(Logger, name, parent)
        self._last_filename = None

    def parse_file(self, f, logger=None, debug=0, optimize=1):
        self._last_filename = os.path.abspath(f.name)

        kw = {
            "debuglog": logger or self,
            "errorlog": logger or self,
            "debug": debug,
            "optimize": optimize,
        }

        kw_lex = kw.copy()
        kw_lex.update({"lextab": "property_lextab"})
        kw_yacc = kw.copy()
        kw_yacc.update({"tabmodule": "property_yacctab"})
        lexer = lex.lex(**kw_lex)
        parser = yacc.yacc(**kw_yacc)

        if debug:
            debug = self
        return parser.parse(f.read(), debug=debug, lexer=lexer)

    def parse(self, filename, logger=None, debug=0, optimize=1):
        filename = os.path.realpath(filename)
        f, res = None, None
        try:
            f = open(filename)
            res = self.parse_file(
                f, logger=logger, debug=debug, optimize=optimize
            )
            f.close()
        except IOError:
            if f:
                f.close()
            raise
        return res

    def getLastFilename(self):
        return self._last_filename


if __name__ == "__main__":
    import sys

    pp = PropertyParser()
    res = pp.parse(sys.argv[1])
    print(res)
