# Factorization of Multivariate polynomial
#
#   by Shin-ichiro Hara
#
# Version 1.0 (2001.09.14)


require "factors"
require "chinese-rem-th"
require "m-polynomial-factor-int"
require "m-polynomial-factor-zp"
require "combinatorial"

module Algebra
module MPolynomialFactorization
  include ChineseRemainderTheorem

  def each_point_int(n0, na)
    #obscure algorithm
    pblock = 7
    m = 0
    loop do
      each_point_zp(n0, na, m + pblock) do |pb0|
	next if (1...n0-na).find{|k| pb0[na+k] < m}
	yield pb0
      end
      m += pblock
      $stderr.puts "warning (each_point_int): it's hard to find adequate zero point"
    end
  end
 
  def each_point_zp(n0, na, char)
    avoid_indice = indice_of_constant
    Combinatorial.power_cubic(char, n0 - na - 1) do |a|
      pb0 = (0..na).collect{0} + a
      next if avoid_indice.find{|i| ! pb0[i].zero?}
      yield(pb0)
    end
    raise "each_point(limit over)"
  end

  def _factorize(na = 0)
    pring = Polynomial(ground, "x")

    f, f_one, pb0 = self, nil, nil
    # f     : m-poly. moved by pb0 reduced onto f_one
    # f_one : sqfree poly. 
    # pb0   : parallel moving vector
    
    if ground == Integer
      each_point_int(vars.size, na) do |pb0|
	f = move_const(pb0) if pb0.find{|x|!x.zero?}
	f_one = f.reduce2onevar(pring, na)
	break if f_one.psqfree?
      end
      fact_one, char = f_one._factorize
      hensel = :hensel_lift_int
    else # ground == Zp
      char = ground.char
      each_point_zp(vars.size, na, char) do |pb0|
	f = move_const(pb0) if pb0.find{|x|!x.zero?}
	f_one = f.reduce2onevar(pring, na)
	break if f_one.sqfree?
      end
      fact_one = f_one.factorize_modp
      hensel = :hensel_lift_zp
    end

    factors = Factors.new
    if fact_one.fact_size <= 1
      factors *= self
      return factors
    end
    height = totdeg_away_from(0)

    f1 = f
    fact_one.each_product do |g0|
      f1_one = f1.reduce2onevar(pring, na)
      next if g0.deg > f1_one.deg
      if fact_q = f1.send(hensel, g0, f1_one, char, height)
#      if fact_q = f1.hensel_lift(g0, f1_one, char, height)
	f0, f1 = fact_q
	factors.push [f0, 1]
	break if f1.totdeg <= 0
	true
      end
    end

    factors.collect{|f3, i| [f3.move_const(pb0, -1), i]}
  end

  def _hensel_lift(g0, f0, char, height)
    # self in MPolynomial/ZorZp
    # g0 in Polyomial/ZorZp, candidate of factor of f0
    # f0 in Polyomial/ZoZzp, one variable reduction of self

    ring, ring_one  = type, g0.type

    h0, r0 = f0.divmod g0
    unless r0.zero?
      raise "each_product does not work well"
    end

    where = 0
    ary = [g0, h0]
    cofacts = mk_cofacts(ary)
    fk = ary.collect{|fx| fx.value_on_one(ring, where)} #MPolynomial

    height.times do |k|
      c = self - fk[0]*fk[1]
      h = c.annihilate_away_from(where, k+1)
      h_alpha0 = h.collect_away_from(where, ring_one) #Hash: ind0=>Polynomial
      h_alpha = {}

      h_alpha0.each do |ind, ha|
	h_alpha[ind] = yield(ha, ary, cofacts)
      end

      hias = ary.collect{ {} }
      h_alpha.each do |ind, ha_i|
	ha_i.each_with_index do |fx, i|
	  next if fx.zero?
	  hias[i][ind] = fx
	end
      end

      hi = hias.collect{ |hia|
	e = ring.zero
	hia.each do |ind, fx|
	  e += ring.monomial(ind)*fx.value_on_one(ring, where)
	end
	e
      }
      fk_new = []
      hi.each_with_index do |fx, i|
	fk_new.push fk[i] + fx
      end
      fk = fk_new
    end
    fk
  end

  def _sqfree
    dvs = []
    vars.each_with_index do |v, i|
      dv = derivate_at(i)
      next if dv.zero?
      dvs.push dv
    end
    g = gcd_all(*dvs)
    self / g
  end

  def pp(an)
    x, *a = coeffs_at(an)
    content = x.gcd_all(*a)
    self / content
  end

  def monic_int(na = 0)
    d = deg_at(na)
    a = lc_at(na)
    ary = type.vars
    m = project0(type){|c, ind|
      n = ind[na]
      if n == d
	zero
      elsif n < d
	c * a ** (d - n - 1) * index_eval(type, ary, ind)
      else
	raise
      end
    }
    
    v =    index_eval(type, ary, MIndex.monomial(na, d)) + m
    v
  end
end

class MPolynomial
  include MPolynomialFactorization

  def factorize
    if  ground <= Integer
      factorize_int
    elsif defined?(Rational) &&  ground <= Rational or
	defined?(LocalizedRing) &&  ground <= LocalizedRing
      factorize_rational
    elsif defined?(ResidueClassRing) &&  ground <= ResidueClassRing &&
	ground.ground <= Integer
      factorize_modp
    else
      factorize_alg
    end
  end

  def reduce2onevar(pring, n)
    evar = []
    type.vars.each_with_index do |v, i|
      evar.push i == n ? pring.var : pring.zero
    end
    project(pring, evar){|c, ind| c}
  end

  def annihilate_away_from(n, e = 0)
    #n = 0, e = 2
    # x*y*z + x*y**2*z + x*y*z + y*z + z**3 #=> x*y*z + y*z + z**3
    project(type) do |c, ind|
      ind.totdeg - ind[n] > e ? ground.zero : c
    end
  end

  def deg_at(an)
    if zero?
      -1
    else
      max = 0
      each do |ind, c|
	unless c.zero?
	  t = ind[an]
	  max = t if t > max
	end
      end
      max
    end
  end

  def indice_of_constant
    (0...vars.size).find_all{|an| deg_at(an) <= 0}
  end

  def lc_at(an)
    d = deg_at(an)
    s = zero
    each do |ind, c|
      next if c.zero?
      if ind[an] == d
	md = ind.annihilate(an)
	s += monomial(md, c)
      end
    end
    s
  end

  def coeffs_at(an)
    ss = (0..deg_at(an)).collect{ zero }
    each do |ind, c|
      next if c.zero?
      md = ind.annihilate(an)
      ss[ind[an]] += monomial(md, c)
    end
    ss
  end

  def totdeg_away_from(n)
    max = 0
    each do |ind, c|
      next if c.zero?
      m = ind.totdeg - ind[n]
      max = m if m > max
    end
    max
  end

  def collect_away_from(n, ps)
    #2*x*y*z + x**2*y*z + x*y #=> [y*z=>(2*x + x**2), y=>x]
    h_alpha = {}
    each do |ind, c|
      next if c.zero?
      i = ind[n]
      ind0 = ind.annihilate(n)
      v = ps.var
      h_alpha[ind0] = h_alpha[ind0] ? h_alpha[ind0] + c*v**i : c*v**i
    end
    h_alpha
  end
end

class Polynomial
  def value_on_one(ring, n = 0)
    e = ring.zero
    x = ring.vars[n]
    reverse_each do |c|
      e = e * x + c
    end
    e
  end
end
end

if __FILE__ == $0
  require "residue-class-ring"
  require "m-polynomial"
  include Algebra
  Z7 = ResidueClassRing(Integer, 7)
  P = MPolynomial(Z7)
  x, y, z = P.vars("xyz")
#  f = x**2*y*z + (-z**2 - y*z + y + z + 2)*x + (y*z**3 - z**3 - y**2*z - y*z + 2*z)
#  f = x**2*y + x**2*z + x * y * z
#  f = x**2*y + x**2*z + x * y 
  f = (x + y)*(y + z)*(z + x)

#  p f.lc_at(0)
  p f
#  p f.monic_int
  p f.coeffs_at(0)
  p( (x*x + y + 1 + x*z + z*2).indice_of_constant)
  p( (x).indice_of_constant)
end
