module Dydx
  module Algebra
    class Formula
      include Helper
      attr_accessor :operator, :terms

      def initialize(operator, *terms)
        @operator, @terms = operator, terms
        commutate! if (terms[1].num? && operator.commutative?)
      end

      def f
        @terms[0]
      end

      def g
        @terms[1]
      end

      def f=(x)
        @terms[0] = x
      end

      def g=(x)
        @terms[1] = x
      end

      def tms
        terms
      end

      # TODO: Cylomatic complexity for differentiate is too high. [7/6]
      def differentiate(sym=:x)
        case @operator
        when :+ then f.d(sym) + g.d(sym)
        when :* then (f.d(sym) * g) + (f * g.d(sym))
        when :**
          # TODO:
          if g.num?
            f.d(sym) * g * (f ** (g - 1))
          elsif (f == sym) && (
              g.num? || (
                g.is_a?(Symbol) && g != sym
              )
            )
            g * f ** (g - 1)
          elsif f == e
            g.d(sym) * self
          else
            self * (g * log(f)).d(sym)
          end
        end
      end
      alias_method :d, :differentiate

      def integrate(sym=:x)
      end

      def to_s
        str = if formula?(:*) && (f.minus1? || g.minus1?)
          "( - #{g} )"
        elsif g.inverse?(operator)
          "( #{f} #{operator.inv} #{g.x} )"
        elsif f.inverse?(operator)
          "( #{g} #{operator.inv} #{f.x} )"
        elsif f.negative?
          "( #{g} - #{f.n.abs} )"
        elsif formula?(:*) && !rationals.empty?
          terms = [f, g]
          terms.delete(rationals.first)
          "( #{(terms.first * rationals.first.n.numerator)} / #{rationals.first.n.denominator} )"
        else
          "( #{f} #{operator} #{g} )"
        end
      end

      def subst(hash = {})
        f.subst(hash).send(operator, g.subst(hash))
      end

      def to_f
        f.to_f.send(operator, g.to_f)
      end

      def include?(x)
        f == x || g == x
      end

      def openable?(operator, x)
        distributive?(self.operator, operator) &&
        (f.combinable?(x, operator) || g.combinable?(x, operator))
      end

      def rationals
        [f, g].select{ |term| term.num? && term.n.is_a?(Rational) }
      end

      def ==(x)
        if to_s == x.to_s
          true
        else
          result = commutate!.to_s == x.to_s
          commutate!
          result
        end
      end

      def commutate!
        @terms.reverse!
        self
      end

      def index(tm)
        tms.index(tm)
      end

      def delete(tm)
        tms.delete(tm)
        tms.count.one? ? tms.first : self
      end
    end
  end
end
