#!/usr/bin/ruby

class Value < Struct.new(:value)
  def to_s
    value.to_s
  end
  def inspect
    "<#{self}>"
  end
  def reducible?
    false
  end
end

class Number < Value
end

class Boolean < Value
end

class Variable < Struct.new(:name)
  def to_s
    name.to_s
  end

  def inspect
    "<#{self}>"
  end

  def reduce(environment)
    environment[name]
  end

  def reducible?
    true
  end
end

class BiOp < Struct.new(:left, :right)
  def inspect
    "<#{self}>"
  end
  def reducible?
    true
  end
end

class Add < BiOp
  def to_s
    "#{left} + #{right}"
  end
  def reduce(environment)
    if left.reducible?
      Add.new(left.reduce(environment),right)
    elsif right.reducible?
      Add.new(left,right.reduce(environment))
    else
      Number.new(left.value+right.value)
    end
  end
end

class Multiply < BiOp
  def to_s
    "#{left} * #{right}"
  end
  def reduce(environment)
    if left.reducible?
      Add.new(left.reduce(environment),right)
    elsif right.reducible?
      Add.new(left,right.reduce(environment))
    else
      Number.new(left.value*right.value)
    end
  end
end

class LessThan < BiOp
  def to_s
    "#{left} < #{right}"
  end

  def reduce(environment)
    if left.reducible?
      LessThan.new(left.reduce(environment), right)
    elsif right.reducible?
      LessThan.new(left, right.reduce(environment))
    else
      Boolean.new(left.value < right.value)
    end
  end
end

class DoNothing
  def to_s
    'do-nothing'
  end

  def inspect
    "<#{self}>"
  end

  def ==(other_statement)
    other_statement.instance_of?(DoNothing)
  end

  def reducible?
    false
  end
end

class Assign < Struct.new(:name, :expression)
  def to_s
    "#{name} = #{expression}"
  end

  def inspect
    "<#{self}>"
  end

  def reducible?
    true
  end

  def reduce(environment)
    if expression.reducible?
      [Assign.new(name, expression.reduce(environment)), environment]
    else
      [DoNothing.new, environment.merge({name => expression })]
    end
  end
end

class If < Struct.new(:condition, :consequence, :alternative)
  def to_s
    "if (#{condition}) { #{consequence} } else { #{alternative} }"
  end

  def inspect
    "<#{self}>"
  end

  def reducible?
    true
  end

  def reduce(environment)
    if condition.reducible?
      [If.new(condition.reduce(environment), consequence, alternative), environment]
    else
      case condition
        when Boolean.new(true)
          [consequence, environment]
        when Boolean.new(false)
          [alternative, environment]
      end
    end
  end
end

class Sequence < Struct.new(:first, :second)
  def to_s
    "#{first}; #{second}"
  end

  def inspect
    "<#{self}>"
  end

  def reducible?
    true
  end

  def reduce(environment)
    case first
      when DoNothing.new
        [second,environment]
      else
        reduced_first, reduced_environment = first.reduce(environment)
        [Sequence.new(reduced_first, second), reduced_environment]
  end

class Machine < Struct.new(:statement, :environment)
  def step
    self.statement, self.environment = statement.reduce(environment)
  end

  def run
    while statement.reducible?
      puts "#{statement}, #{environment}"
      step
    end

    puts "#{statement}, #{environment}"
  end
end