module Mm2ep
  module Depend
    class TreeExpr
      def compute
        raise NotImplementedError
      end
    end

    class TreeValue
      attr_reader :value

      def to_s
        raise NotImplementedError
      end
    end

    class VarValue < TreeValue
      attr_reader :errors

      def initialize(str, value)
        @errors = []
        @name = str
        @value = value
        @value = true if value =~ /^true$/i
        @value = false if value =~ /^false$/i
        return unless @value.nil?
        @errors << VarNotDefined.new(
          message: "No value for #{@name}",
          var: @name
        )
      end

      def compute
        @value
      end

      def to_s
        "var:#{@name}<-(#{@value})"
      end
    end

    class NumberValue < TreeValue
      attr_reader :errors

      def initialize(str)
        @errors = []
        @value = str
      end

      def compute
        @value
      end

      def to_s
        "number:#{@value}"
      end
    end

    class StringValue < TreeValue
      attr_reader :errors

      def initialize(str)
        @errors = []
        @value = str
      end

      def compute
        @value
      end

      def to_s
        "string:\"#{@value}\""
      end
    end

    class BoolValue < TreeValue
      attr_reader :errors

      def initialize(str)
        @errors = []
        @value = case str
                 when /true/i then true
                 when /false/i then false
                 end
      end

      def compute
        @value
      end

      def to_s
        "bool:#{@value}"
      end
    end

    class AndOp < TreeExpr
      attr_reader :errors

      def initialize(lval, rval)
        @errors = []
        @errors.concat lval.errors
        @errors.concat rval.errors
        @lval = lval
        @rval = rval
      end

      def compute
        @lval.compute && @rval.compute
      end

      def to_s
        "( #{@lval} ) AND ( #{@rval} )"
      end
    end

    class OrOp
      attr_reader :errors

      def initialize(lval, rval)
        @errors = []
        @errors.concat lval.errors
        @errors.concat rval.errors
        @lval = lval
        @rval = rval
      end

      def compute
        @lval.compute || @rval.compute
      end

      def to_s
        "( #{@lval} ) OR ( #{@rval} )"
      end
    end

    class NotOp
      attr_reader :errors

      def initialize(expr)
        @errors = []
        @errors.concat expr.errors
        @expr = expr
      end

      def compute
        !@expr.compute
      end

      def to_s
        "NOT ( #{@expr} )"
      end
    end

    class EqOp
      attr_reader :errors

      def initialize(lval, rval)
        @errors = []
        @errors.concat lval.errors
        @errors.concat rval.errors
        @lval = lval
        @rval = rval
      end

      def compute
        @lval.value == @rval.value
      end

      def to_s
        "#{@lval} = #{@rval}"
      end
    end




    # Cut HERE
    # 8< ---- 8< ---- ...



    class Parser < Rly::Yacc
      attr_writer :names

      # Check if grammar is valid
      def check_grammar(line, tokens)
        grammar = tokens.to_s.split(/=|AND|OR/)
        expr = line.split(/=|AND|OR/)
        return if grammar.size == expr.size
        return if grammar.empty?
        tokens.errors << InvalidGrammar.new(
          message: 'Invalid Grammar'
        )
      end

      precedence :left, :OR_OP
      precedence :left, :AND_OP
      precedence :left, :EQ_OP
      precedence :right, :L_PAR, :R_PAR
      precedence :right, :UMINUS

      rule 'statement : expr' do |st, e|
        st.value = e.value
      end

      rule 'expr : VAR' do |ex, l|
        ex.value = EqOp.new(
          VarValue.new(l.value, @names[l.value]),
          BoolValue.new('true')
        )
      end

      rule 'bool_expr : F_BOOL' do |ex, l|
        ex.value = l.value
      end

      rule 'bool_expr : T_BOOL' do |ex, l|
        ex.value = l.value
      end

      rule 'expr : bool_expr' do |ex, l|
        ex.value = BoolValue.new(l.value.to_s)
      end

      rule 'expr : expr OR_OP expr' do |ex, l, _e, r|
        ex.value = OrOp.new(l.value, r.value)
      end

      rule 'expr : expr AND_OP expr' do |ex, l, _e, r|
        ex.value = AndOp.new(l.value, r.value)
      end

      rule 'expr : L_PAR expr R_PAR' do |ex, _l, e, _r|
        ex.value = e.value
      end

      rule 'expr : NOT_OP expr %prec UMINUS' do |ex, _l, e|
        ex.value = NotOp.new(e.value)
      end

      rule 'expr : VAR EQ_OP bool_expr' do |ex, v, _eq, n|
        ex.value = EqOp.new(
          VarValue.new(v.value.to_s, @names[v.value]),
          BoolValue.new(n.value)
        )
      end

      rule 'expr : VAR EQ_OP STRING' do |ex, v, _eq, n|
        ex.value = EqOp.new(
          VarValue.new(v.value.to_s, @names[v.value]),
          StringValue.new(n.value)
        )
      end

      rule 'expr : VAR EQ_OP NUMBER' do |ex, v, _eq, n|
        ex.value = EqOp.new(
          VarValue.new(v.value.to_s, @names[v.value]),
          NumberValue.new(n.value)
        )
      end
    end # class
  end # module
end # module