# Lair Configuration DSL Case Study (Python 3)
# Semantic Model
# H. Conrad Cunningham

# Developed for CSci 658, Software Language Engineering, Spring 2018

#234567890123456789012345678901234567890123456789012345678901234567890

# 2018-02-27: (V1) adapted from Martin Fowler's Ruby version model.rb

# Caveats: This is my first Python program. I mostly used Fowler's
# Ruby design from his book chapter "One Lair and Twenty Ruby DSLs",
# for which I implemented a Lua version in 2013. Here I reinterpreted
# it with Python 3.6 object-oriented programming features. I am biased
# toward information hiding, so I did not necessarily follow Python's
# typically looser approach to encapsulation.

class Configuration:

    def __init__(self):
        self._items = {}

    # Configuration's check added to Python version functionality
    def check(self):
        for it in self.all_items():
            it.check()

    def add_item(self,arg):
        self._items[arg.id] = arg

    def get_item(self,arg):
        return self._items[arg]

    def all_items(self):
        return list(self._items.values())

    def __str__(self):
        config_str = "\n".join(str(it) for it in self.all_items())
        return "Configuration\n" + config_str


class Item:

    def __init__(self,id):
        self._id = id
        self._uses = []
        self._provisions = []
        self._dependencies = []

    def add_usage(self,an_item):
        self._uses.append(an_item)

    def add_provision(self,an_item):
        self._provisions.append(an_item)

    def add_dependency(self,an_item):
        self._dependencies.append(an_item)

    @property
    def id(self):
        return self._id

    @property
    def uses(self):
        return self._uses

    @property
    def provisions(self):
        return self._provisions

    @property
    def dependencies(self):
        return self._dependencies

    def __str__(self):
        ustr = ", ".join(str(u) for u in self.uses)
        pstr = ", ".join(str(p) for p in self.provisions)
        dstr = ", ".join(d.id   for d in self.dependencies)
        return f"{self.id}\n uses: {ustr}\n provides: {pstr}\n => {dstr}"

    # I followed Fowler's design here, but I prefer to check validity
    # on initial creation rather than after the fact. I also prefer
    # not to use Exceptions in this way.
    
    def check(self):
        result = self.validate()
        if not result.is_ok():
            raise Exception("Item invalid\n" + str(result))

    def validate(self):
        result = Notification()
        if not all(Item.is_Resource(u) for u in self.uses):
            result.error("Bad resource in uses")
        if not all(Item.is_Resource(p) for p in self.provisions):
            result.error("Bad resource in provisions") 
        return result

    @staticmethod
    def is_Resource(obj):
        return isinstance(obj,Resource)

# I added the Resource superclass to the Python (and Lua) version, but
# I am only using it to group the resource classes for a type check.

class Resource:

    def __str__(self):
        return "Generic Resource?"

class Acid(Resource):

    def __init__(self):
        self._type = None
        self._grade = None

    @property
    def type(self):
        return self._type

    @type.setter
    def type(self,acid_type):
        self._type = acid_type

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self,acid_grade):
        self._grade = acid_grade

    def __str__(self):
        return f"Acid[{self.type},{self.grade}]"

    
class Electricity(Resource):

    def __init__(self,power):
        self._power = power

    @property
    def power(self):
        return self._power

    def __str__(self):
        return f"Elec[{self.power}]"

        
class Notification:

    def __init__(self):
        self._messages = []

    def fail(self,message="Uncommunicative failure"):
        self._messages.append(message)

    def error(self,message):
        self._messages.append(message)

    def is_ok(self):
        return len(self._messages) == 0

    def __str__(self):
        if self.is_ok():
            return "OK"
        else:
            result = ["Failed!"]
            result.extend(list(str(m) for m in self._messages))
            return "\n".join(result)
