import itertools
import string
from abc import ABCMeta, abstractproperty

import attr


def is_valid_formula(inst, attr, value):
    if not isinstance(value, (Formula, str)):
        raise ValueError('{} is not a valid formula type.'.format(value))


class Formula(object):
    __metaclass__ = ABCMeta

    group = {'open': '(', 'close': ')'}

    @abstractproperty
    def token(self):
        pass


@attr.s
class Atomic(Formula):
    token = list(itertools.chain.from_iterable(
                 [string.uppercase, string.lowercase]))
    exp = attr.ib(validator=is_valid_formula)


@attr.s
class And(Formula):
    token = ['^', '&']
    left = attr.ib(validator=is_valid_formula)
    right = attr.ib(validator=is_valid_formula)


@attr.s
class Or(Formula):
    token = ['|']
    left = attr.ib(validator=is_valid_formula)
    right = attr.ib(validator=is_valid_formula)


@attr.s
class Imply(Formula):
    token = ['>']
    left = attr.ib(validator=is_valid_formula)
    right = attr.ib(validator=is_valid_formula)


@attr.s
class Not(Formula):
    token = ['~']
    exp = attr.ib(validator=is_valid_formula)
