
# Error, that indicates a constraint violation
class ConstraintViolationError < StandardError
end

# Team class which enables sensing of constraint violation.
# A constraint is a subtype of class Constraint.
# 
class ConstraintSensing < Team
   
    # Constraint class to check a certain attribute.
    class AttributeConstraint 

        # This method has to be implemented to check
        # a given value if the constraint is valid.
        def check_attribute(value)
            raise "abstract method called"
        end
    end

    # Just a simple debug constraint, to see if the check is performed.
    class DebugConstraint < AttributeConstraint
        def check_attribute(value)
            puts "check attribute #{value.class} = #{value.to_s}\n"
        end
    end

    # Ensure, that the given attribute lives in a defined boundary.
    # The instance has to be initialized with two boundary objects,
    # which have to be understand the "<" and ">" operators.
    class MinMaxValue < AttributeConstraint
        #lower bound
        attr :min
        #upper bound
        attr :max
        
        def initialize(min, max)
            @min = min
            @max = max
        end
        
        def check_attribute(value)
            return "Value less than #{@min}" if (value<@min) 
            return "Value greater than #{@max}" if (value>@max)
        end
    end

    # Ensure, that the given attribute has a length between a given boundary.
    class MinMaxLength < AttributeConstraint
        #lower bound
        attr :min
        #upper bound
        attr :max
        
        def initialize(min, max)
            @min = min
            @max = max
        end
        
        def check_attribute(value)
            if (value.respond_to?("length"))
                return "Value.length less than #{@min}" if (value.length<@min) 
                return "Value.length greater than #{@min}" if (value.length>@max)
            end
        end
    end

    # Ensure, that the given value is not nil.
    class Mandatory  < AttributeConstraint
        def check_attribute(value)
            return "Mandatory attribute can't be null" if (value.nil?) 
        end
    end

    # Ensure, that the given value applies to the given regular expression
    class RegExp < AttributeConstraint
        
        attr :regexp
        
        def initialize(regexp)
            @regexp = regexp
        end
        
        def check_attribute(value)
            return ">#{value}< does not match #{@regexp.source}" if (!@regexp.match(value)) 
        end
    end

    # This class represents the object, which constraints have to be checked.
    # Each attribute can have several constraints.
    class ConstraintObject
        #constraints as hash: accessormethod -> list of constraints
        attr :constraints
        
        def initialize()
            @constraints = Hash.new
        end

        # To add a certain constraint to a given attribute.
        def add_constraint(attribute, constraints)
            @constraints[attribute] = constraints
        end

        # This method checks for the given attribute all defined constraints.
        # The method handle_constraint_check is called, if the given value does 
        # not conform to the defined constraints.
        def check_attribute(attribute, value)
            flag = true
            while flag
                flag = false
                @constraints[attribute].each { |constraint|
                    desc = constraint.check_attribute(value)
                    if (desc)
                        value = handle_constraint_check(attribute, value, constraint, desc)
                        flag = true
                    end
                }
            end
            
            return value
        end

        # Handle constraint violations:
        # Raise an exception to signal the violation.
        # Refine this method to handle the violation.
        def handle_constraint_check(attribute, value, constraint, description)
            raise "#{attribute} constraint #{constraint.class} failed: #{description}"
        end

        # Convenient method to check attribute settings via all set_XXX.
        # If you conform to the coding convention set_XXX(NEWXXX), that changes
        # attribute XXX to NEWXXX, than you can use this method as callin trigger.
        # The name of te joinpoint indicates the attribute to check for.
        def check_setter(value)
            setter = JoinPoint.actualJoinPoint.source_method
            setter = setter.sub(/^set_/, "")
            base(check_attribute(setter, value))
        end

    end
end

# The class ConstraintSensing is able to detect a constraint violation, but
# is not able to remedy the failure. This team tries to solve a certain 
# constraint violation.
class ConstraintHealing < ConstraintSensing

    # Baseclass of all solving strategies.
    class Solver
        def solve(attribute, value, constraint, description)
            raise "abstract method called"
        end
    end

    # Query a new String.
    class StringSolver < Solver
        def solve(attribute, value, constraint, description)
            puts "#{constraint.class.name} Error for #{attribute} of value >>#{value}<<:\n#{description}\n"
            puts "Please enter new string\n\n"
            return Readline.readline
        end
    end

    # Query for new Date.
    class DateSolver < Solver
        def solve(attribute, value, constraint, description)
            puts "#{constraint.class.name} Error: #{description}\n"
            puts "Please enter new date\n\n"
            begin
                newdate = Date.parse(Readline.readline)
            rescue ArgumentError=>er
                puts "\nCan't parse date! Reverting to old value...\n"
                newdate = value
            end
            return newdate
        end
    end

    # Refine this class to be able to solve constraint violation.
    class ConstraintObject
        
        # hash holding all solving strategies.
        attr :solver
        
        def initialize()
            super()
            @solver = Hash.new
        end

        # add solving strategy as well as the constraints to check.
        def add_constraint(attribute, constraints, solver)
            super(attribute, constraints)
            @solver[attribute] = solver
        end
        
        # Try to solve the given problem.
        def handle_constraint_check(attribute, value, constraint, description)
            solver = @solver[attribute]
            raise "no solver for #{attribute} given!" if solver.nil? 
            newvalue = solver.solve(attribute, value, constraint, description)
            return newvalue
        end
    end
end
