# Copyright (c) 2022 Zhendong Peng (pzd17@tsinghua.org.cn)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import string

EOS = '<EOS>'
TN_ORDERS = {
    'date': ['year', 'month', 'day'],
    'fraction': ['denominator', 'numerator'],
    'measure': ['denominator', 'numerator', 'value'],
    'money': ['value', 'currency'],
    'time': ['noon', 'hour', 'minute', 'second']
}
EN_TN_ORDERS = {
    'date': ['preserve_order', 'text', 'day', 'month', 'year'],
    'money': ['integer_part', 'fractional_part', 'quantity', 'currency_maj'],
}
ITN_ORDERS = {
    'date': ['year', 'month', 'day'],
    'fraction': ['sign', 'numerator', 'denominator'],
    'measure': ['numerator', 'denominator', 'value'],
    'money': ['currency', 'value', 'decimal'],
    'time': ['hour', 'minute', 'second', 'noon']
}


class Token:

    def __init__(self, name):
        self.name = name
        self.order = []
        self.members = {}

    def append(self, key, value):
        self.order.append(key)
        self.members[key] = value

    def string(self, orders):
        output = self.name + ' {'
        if self.name in orders.keys():
            if "preserve_order" not in self.members.keys() or \
                    self.members["preserve_order"] != "true":
                self.order = orders[self.name]

        for key in self.order:
            if key not in self.members.keys():
                continue
            output += ' {}: "{}"'.format(key, self.members[key])
        return output + ' }'


class TokenParser:

    def __init__(self, ordertype="tn"):
        if ordertype == "tn":
            self.orders = TN_ORDERS
        elif ordertype == "itn":
            self.orders = ITN_ORDERS
        elif ordertype == "en_tn":
            self.orders = EN_TN_ORDERS
        else:
            raise NotImplementedError()

    def load(self, input):
        assert len(input) > 0
        self.index = 0
        self.text = input
        self.char = input[0]
        self.tokens = []

    def read(self):
        if self.index < len(self.text) - 1:
            self.index += 1
            self.char = self.text[self.index]
            return True
        self.char = EOS
        return False

    def parse_ws(self):
        not_eos = self.char != EOS
        while not_eos and self.char == ' ':
            not_eos = self.read()
        return not_eos

    def parse_char(self, exp):
        if self.char == exp:
            self.read()
            return True
        return False

    def parse_chars(self, exp):
        ok = False
        for x in exp:
            ok |= self.parse_char(x)
        return ok

    def parse_key(self):
        assert self.char != EOS
        assert self.char not in string.whitespace

        key = ''
        while self.char in string.ascii_letters + '_':
            key += self.char
            self.read()
        return key

    def parse_value(self):
        assert self.char != EOS
        escape = False

        value = ''
        while self.char != '"':
            value += self.char
            escape = self.char == '\\'
            self.read()
            if escape:
                escape = False
                value += self.char
                self.read()
        return value

    def parse(self, input):
        self.load(input)
        while self.parse_ws():
            name = self.parse_key()
            self.parse_chars(' { ')

            token = Token(name)
            while self.parse_ws():
                if self.char == '}':
                    self.parse_char('}')
                    break
                key = self.parse_key()
                self.parse_chars(': "')
                value = self.parse_value()
                self.parse_char('"')
                token.append(key, value)
            self.tokens.append(token)

    def reorder(self, input):
        self.parse(input)
        output = ''
        for token in self.tokens:
            output += token.string(self.orders) + ' '
        return output.strip()
