# MIT License
#
# Copyright (c) [2020 - 2021] The yinyang authors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import random

from yinyang.src.mutators.Mutator import Mutator


class TypeAwareOpMutation(Mutator):
    def __init__(self, formula, args):
        self.args = args
        self.formula = formula
        self.bidirectional = []
        self.unidirectional = []

        self.parse_config_file()

    def parse_config_file(self):
        with open(self.args.config) as f:
            lines = f.readlines()

        for line in lines:
            if ";" in line:
                continue
            if not line.strip():
                continue
            arity = -1
            if ":arity" in line:
                arity = line.split(":arity")[-1].split(" ")[-1].strip()
                line = " ".join(line.split(" ")[:-2])
            if "->" in line:
                op_from = line.split("->")[0].strip()
                op_to = line.split("->")[1].strip()
                self.unidirectional.append((arity, op_from, op_to))
                continue

            op_class = [op.strip() for op in line.split(",")]
            self.bidirectional.append((arity, op_class))

    def arities_mismatch(self, arity, op_occ):
        if arity == "2+" and len(op_occ.subterms) < 2:
            return True

        if arity == "1-" and len(op_occ.subterms) > 2:
            return True
        return False

    def get_replacee(self, op_occ):
        for b in self.bidirectional:
            arity, op_class = b[0], b[1]
            if self.arities_mismatch(arity, op_occ):
                continue

            if op_occ.op in op_class:
                diff = op_class.copy()
                diff.remove(op_occ.op)
                replacee = random.choice(diff)
                return replacee

            if op_occ.quantifier in op_class:
                diff = op_class.copy()
                diff.remove(op_occ.quantifier)
                replacee = random.choice(diff)
                return replacee

        for u in self.unidirectional:
            arity, op, replacee = u[0], u[1], u[2]
            if op_occ.op != op or op_occ.quantifier != op:
                continue
            if self.arities_mismatch(arity, op_occ):
                continue
            return replacee
        return None

    def mutate(self):
        success = False
        for _ in range(self.args.modulo):
            max_choices = len(self.formula.op_occs)
            for _ in range(max_choices):
                op_occ = random.choice(self.formula.op_occs)
                replacee = self.get_replacee(op_occ)
                if replacee:
                    success = True
                    op_occ.op = replacee
                    break
        return self.formula, success, False
