require “treetop”

module Bayesnet

module Parsers
  grammar Bif
    include Bayesnet::Parsers::Builder

    rule CompilationUnit
      SEP NetworkDeclaration list:( VariableDeclaration / ProbabilityDeclaration )* SEP {
      def nodes
        list.elements.select { |e| e.respond_to?(:node) }.map(&:node)
      end

      def cpts
        list.elements.select { |e| e.respond_to?(:cpt) }.map(&:cpt)
      end
      }

    end

    rule NetworkDeclaration
      SEP NETWORK SEP WORD SEP NetworkContent SEP
    end

    rule NetworkContent
      SEP "{" ( SEP Property  )* SEP "}" SEP
    end

    rule VariableDeclaration
      SEP VARIABLE SEP probabilityVariableName SEP variableContent SEP {
      def node
        values = variableContent.values
        [probabilityVariableName.text_value.to_sym, values]
      end
      }
    end

    rule variableContent
      SEP "{"  list:(SEP Property / variableDiscrete )* SEP "}" SEP {
      def values
        list.elements.select { |e| e.respond_to?(:values) }.map(&:values).flatten
      end
      }
    end

    rule variableDiscrete
      SEP VARIABLETYPE SEP DISCRETE SEP "[" SEP DECIMAL_LITERAL SEP "]" SEP "{" SEP variableValuesList SEP "}" SEP ";" SEP {
      def values
        variableValuesList.values
      end
      }
    end

    rule variableValuesList
      SEP probabilityVariableValue list:( SEP probabilityVariableValue )* {
      def values
        [probabilityVariableValue.value] + list.elements.map { |e| e.elements[1].value }.flatten
      end
      }
    end

    rule probabilityVariableValue
      VALUE {
      def value
        self.text_value.to_sym
      end
      }
    end

    rule ProbabilityDeclaration
      SEP PROBABILITY SEP probabilityVariablesList SEP probabilityContent SEP {
      def cpt
        { variable: probabilityVariablesList.value,
          parents: probabilityVariablesList.parents,
          cpt: probabilityContent.cpt}
      end
      }
    end

    rule probabilityVariablesList
      SEP "(" SEP probabilityVariableName list:( SEP probabilityVariableName )* SEP ")" SEP {
      def value
        probabilityVariableName.value
      end

      def parents
         list.elements.map { |e| e.elements[1].value }.flatten
      end
      }
    end

    rule probabilityVariableName
      WORD {
      def value
        self.text_value.to_sym
      end
      }
    end

    rule probabilityContent
      SEP "{" SEP list:( SEP Property / ProbabilityDefaultEntry / ProbabilityEntry  / ProbabilityTable  )* SEP "}" SEP {
      def cpt
         distributions = list.elements.select { |e| e.respond_to?(:given) }.map do |e|
           {given: e.given,
            distribution: e.distribution}
         end
         table = list.elements.select { |e| e.respond_to?(:table) }.map do |e|
          {table: e.table}
         end
         if distributions.empty? && !table.empty?
           return table.first
         elsif !distributions.empty? && table.empty?
           return distributions
         elsif distributions.empty? && table.empty?
           raise "Either distributions or table must be provided"
         else
           raise "Both - distributions or table cannot be provided at the same time"
         end
      end
      }
    end

    rule ProbabilityEntry
      SEP probabilityValuesList SEP floatingPointList SEP ";" SEP {
      def given
        probabilityValuesList.values
      end
      def distribution
        floatingPointList.values
      end
      }
    end

    rule probabilityValuesList
      SEP "(" SEP probabilityVariableValue list:( SEP probabilityVariableValue  )*  SEP ")" {
      def values
        [probabilityVariableValue.value] + list.elements.map { |e| e.elements[1].value }
      end
      }
    end

    rule ProbabilityDefaultEntry
      SEP floatingPointList SEP ";" SEP {
      def values
        floatingPointList.values
      end
      }
    end

    rule ProbabilityTable
      SEP TABLEVALUE SEP floatingPointList SEP ";" SEP {
      def table
        floatingPointList.values
      end
      }
    end

    rule floatingPointList
      SEP floatingPointToken  list:( SEP floatingPointToken )* {
      def values
        [floatingPointToken.value] + list.elements.map {|e| e.elements[1]}.select { |e| e.respond_to?(:value) }.map(&:value)
      end
      }
    end

    rule floatingPointToken
      FLOATING_POINT_LITERAL {
      def value
        self.text_value.to_f
      end
      }
    end

    rule Property
      PROPERTYSTRING
    end

    rule NETWORK
      'network'
    end

    rule VARIABLE
      'variable'
    end

    rule PROBABILITY
      'probability'
    end

    rule PROPERTY
      'property'
    end

    rule VARIABLETYPE
      'type'
    end

    rule DISCRETE
      'discrete'
    end

    rule DEFAULTVALUE
      'default'
    end

    rule TABLEVALUE
      'table'
    end

    rule WORD
      LETTER (LETTER / DIGIT)*
    end

    rule LETTER
      [a-zA-Z_-]
    end

    rule VALUE
      [/<>=.+a-zA-Z_0-9-]+
    end

    rule DIGIT
      [0-9]
    end

    rule DECIMAL_LITERAL
      DIGIT DIGIT*
    end

    rule FLOATING_POINT_LITERAL
      DIGIT+ '.' DIGIT* EXPONENT?
      / '.' DIGIT+ EXPONENT?
      / DIGIT+ EXPONENT
    end

    rule EXPONENT
     [eE] [+-]? DIGIT+
    end

    rule PROPERTYSTRING
      PROPERTY .* ';'
    end

    rule SEP
      [\r\n\t ,|]*
    end

  end
end

end