﻿class Rule
  attr_reader :rule_name, :lhs_list, :rhs
  
  def initialize(rule_name, lhs_list, rhs_str)
    @rule_name = rule_name
    @lhs_list = lhs_list
    @rhs = rhs_str
  end
  
end

class RuleFactory
  @@rule_num = 0
  
  def RuleFactory.make_rule(lhs_list, rhs)
    @@rule_num += 1
    
    Rule.new("R#{@@rule_num}", lhs_list, rhs)
  end

  def RuleFactory.clear_number
    @@rule_num = 0
  end
  
end

class WorkingMemory
  
  def initialize
      @knowledge_list = []
  end
  
  def find_knowledge(find_str)
    return nil if nil == @knowledge_list
    
    @knowledge_list.find { |know| know == find_str }
  end

  def add_knowledge(knowledge)
    return if find_knowledge(knowledge)
    
    @knowledge_list << knowledge
  end
  
  def load_from_file(file_path)
    File.open(file_path, 'r') do |file|
      file.each_line do |line|
        line[-1] = '' if line[-1] == 10   # '\n' is 10(ascii)
         
        add_knowledge(line)
      end
    end
  end
    
  def clear
    @knowledge_list.clear
  end
  
end

class ProductionMemory
  
  def initialize
      @rule_list = []
  end
  
  def add_rule(rule)
    @rule_list << rule
  end
  
  # 등록된 순서대로 리턴
  def find_rules(rhs_str)
    # find rule RHS
    @rule_list.select { |rule| rule.rhs == rhs_str }
  end

  def load_from_file(file_path)
    RuleFactory.clear_number
    
    File.open(file_path, 'r') do |file|
      file.each_line do |line|
        line[-1] = '' if line[-1] == 10   # '\n' is 10(ascii)

        # split |
        split_lhsrhs = line.split('|')
        
        # split ,
        split_each_lhs = split_lhsrhs[0].split(',')
        
        add_rule(RuleFactory.make_rule(split_each_lhs, split_lhsrhs[1]))
      end
    end
  end

  def clear
    @rule_list.clear
  end

end

class RuleInterpreter
  
  def initialize
    @wm = nil
    @pm = nil
    @prev_fired_rule = nil
    @unsatisfied_chain_stack =[]
  end

  def set_working_memory(wm)
    @wm = wm
  end
  
  def set_production_memory(pm)
    @pm = pm
  end
    
  def set_goal(goal_str)
    @goal = goal_str
  end
  
  def fire_rule(rule)
    # rhs 의 정보를 WM 으로 넣음
    @wm.add_knowledge(rule.rhs)
    
    puts "Fired #{rule.rule_name}"
    
    @prev_fired_rule = rule
  end
  
  def do_bwd_recursive(target_knowledge)
    # 찾아야 할 지식이 rhs 로 있는 rule 후보들을 찾음
    next_candidate_rules = @pm.find_rules(target_knowledge)
    
    while( next_candidate_rules.length > 0 )
      # 남은 후보 중에서 하나를 선택
      selected_rule = select_rule_with_conflict(next_candidate_rules)
    
      # 이전에 fire 한 rule 만 남음 
      break if nil == selected_rule      
    
      # WM 에 있는 것들은 통과, 없는 것들에 대해 리스트 뽑기
      unsatisfied_lhs_list = selected_rule.lhs_list.select do |lhs|
        nil == @wm.find_knowledge(lhs)
      end
      
      if unsatisfied_lhs_list
        # 만족하지 못하는 lhs 들을 찾아서 들어감(DFS)
        unsatisfied_lhs_list.each do |lhs|
          break if !do_bwd_recursive(lhs)
          
          unsatisfied_lhs_list.delete(lhs)
        end
      end
      
      # 만족하지 못하는 lhs 를 어떤 rule 로도 만들 수 없음
      if unsatisfied_lhs_list && unsatisfied_lhs_list.length > 0
        next_candidate_rules.delete(selected_rule)
        
        puts "Abandoned #{selected_rule.rule_name}"
        
        next
      end
            
      # WM 에 전부 있으면 여기까지 진입가능
      # rule 을 fire
      fire_rule(selected_rule)
        
      # target_knowledge 는 해결함, 이전 미해결로 리턴
      return true
    end
    
    # 후보 중 진행 가능한 것이 없음
    return nil
  end
  
  def do_bwd
    return nil if nil == @wm || nil == @pm
    
    do_bwd_recursive(@goal)
  end
  
  def select_rule_with_conflict(rule_list)
    # 3) specificity 적용, 2) Recency 는 적용하지 않음(WM의 순서를 뒤져야 함)
    most_preferred_rule = nil
    max_condition = 0
    
    rule_list.each do |rule|
      # 1) refraction 을 방지
      next if @prev_fired_rule == rule
      
      if nil == most_preferred_rule
        most_preferred_rule = rule
        max_condition = rule.lhs_list.length
      else
        if max_condition < rule.lhs_list.length
          most_preferred_rule = rule
          max_condition = rule.lhs_list.length
        end
      end
      
    end
    
    return most_preferred_rule if most_preferred_rule
    
    # 이전 rule 밖에 남지 않음, 다시 fire 하지 않음
    return nil
  end
  
end

class ProductionSystem
    def initialize
      @wm = nil
      @pm = nil
    end
    
    def set_working_memory(wm)
      @wm = wm
    end
    
    def set_production_memory(pm)
      @pm = pm
    end
    
    def do_bwd(goal)
      interpreter = RuleInterpreter.new
      
      interpreter.set_working_memory @wm
      interpreter.set_production_memory @pm
      interpreter.set_goal goal
      
      found = interpreter.do_bwd
        
      if found
        puts "Success"
      else
        puts "Not Success"
      end
    
    end
    
    def clear_knowledge
      @wm = nil
    end
    
    def clear_rule
      @pm = nil
    end
          
end


# 데이터 넣기
ps = ProductionSystem.new

pm = ProductionMemory.new

pm.load_from_file 'zoo_production_rule.dat'

ps.set_production_memory pm

# GIRAFFE
puts 'Test:: Find giraffe'

wm = WorkingMemory.new

wm.load_from_file 'working_memory_ex_giraffe.dat'

ps.set_working_memory wm

ps.do_bwd('giraffe')

ps.clear_knowledge

# CHEETAH
puts 'Test:: Find cheetah'

wm = WorkingMemory.new

wm.load_from_file 'working_memory_ex_cheetah.dat'

ps.set_working_memory wm

ps.do_bwd('cheetah')


