#!/usr/bin/python3
#
#  Naše objektové řešení obsahovalo spoustu duplicitního kódu: speciálně
#  všechny binární operátory měly identické metody __init__ a velmi podobné
#  metody __str__ a eval.
#
#  Zde se duplicit zbavíme tím, že je přesuneme do společného předka:
#  třídy BinaryNode, která popisuje chování obecného binárního operátoru.
#  Každý konkrétní operátor si pak pouze dodefinuje, jak se jmenuje a jaké
#  funkci odpovídá.
#

class Node:

    def eval(self):
        raise NotImplementedError()

    def depth(self):
        raise NotImplementedError()

    def to_postfix(self, dest):
        raise NotImplementedError()


class NumNode(Node):

    def __init__(self, x):
        self.value = x

    def __str__(self):
        return str(self.value)

    def eval(self):
        return self.value

    def depth(self):
        return 0

    def to_postfix(self, dest):
        dest.append(str(self.value))


class BinaryNode(Node):

    op_name = '?'

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def __str__(self):
        return '(' + str(self.left) + self.op_name + str(self.right) + ')'

    def eval(self):
        return self.eval_op(self.left.eval(), self.right.eval())

    def eval_op(self, x, y):
        raise NotImplementedError()

    def depth(self):
        return max(self.left.depth(), self.right.depth()) + 1

    def to_postfix(self, dest):
        self.left.to_postfix(dest)
        self.right.to_postfix(dest)
        dest.append(self.op_name)


class UnaryNode(Node):

    op_name = '?'

    def __init__(self, arg):
        self.arg = arg

    def __str__(self):
        return '(' + self.op_name + str(self.arg) + ')'

    def eval(self):
        return self.eval_op(self.arg.eval())

    def eval_op(self, x):
        raise NotImplementedError()

    def depth(self):
        return self.arg.depth() + 1

    def to_postfix(self, dest):
        self.arg.to_postfix(dest)
        dest.append(self.op_name)


class NegNode(UnaryNode):

    op_name = 'N'

    def eval_op(self, x):
        return -x


class AddNode(BinaryNode):

    op_name = '+'

    def eval_op(self, x, y):
        return x + y


class SubNode(BinaryNode):

    op_name = '-'

    def eval_op(self, x, y):
        return x - y


class MulNode(BinaryNode):

    op_name = '*'

    def eval_op(self, x, y):
        return x * y


class DivNode(BinaryNode):

    op_name = '/'

    def eval_op(self, x, y):
        return x // y


x = AddNode(NegNode(MulNode(NumNode(3), NumNode(4))), SubNode(NumNode(5), NumNode(6)))
print(x)
print(x.eval())
print(x.depth())

dest = []
x.to_postfix(dest)
print(" ".join(dest))