# Complex number support for Flt::Num types.
#
# Complex is extended to handle properly components of Num types.
#
# Examples:
#   require 'flt/complex'
#   include Flt
#   DecNum.context(:precision=>4) do
#     puts (Complex(1,2)*DecNum(2)).abs.inspect            # => DecNum('4.472')
#   end
#
# Complex functions are provided through Num.ccontext object, Context#.cmath blocks and CMath modules.
#
# Examples:
#   require 'flt/complex'
#   include Flt
#   DecNum.context.precision = 10
#   puts DecNum.ccontext.sqrt(-2)                           # => 0+1.414213562i
#   puts DecNum.ccontext(:precision=>5).sqrt(-2)            # => 0+1.4142i
#   puts DecNum.context(:precision=>6).cmath{sqrt(-2)}      # => 0+1.41421i
#
# CMath Examples:
#   DecNum.context(:precision=>5) do
#     puts DecNum::CMath.sqrt(-2)                           # => 0+1.4142i
#   end
#   DecNum.context.precision = 10
#   include DecNum::CMath
#   puts sqrt(-2)                                           # => 0+1.414213562i
#

require 'flt/math'
require 'complex'

class Complex

  alias abs! abs
  def abs
    num_class.nil? ? abs! : num_class.context.hypot(real, imag)
  end

  alias polar! polar
  def polar
    num_class.nil? ? polar! : [num_class.context.hypot(real, imag), num_class.context.atan2(imag, real)]
  end

  # alias power! **
  # def **(other)
  #   if classnum_class_num.nil? && other.class_num.nil?
  #     self.power!(other)
  #   else
  #     num_class.ccontext.power(self, other)
  #   end
  # end

  private

  def num_class
    real.kind_of?(Flt::Num) ? real.class : imag.kind_of?(Flt::Num) ? imag.class : nil
  end

end # Complex

module Flt

  class ComplexContext # consider: < Num::ContextBase

    def initialize(context)
      @context = context
    end

    def num_class
      @context.num_class
    end

    def math
      num_class.context
    end

    def Num(z)
      if z.kind_of?(Complex)
        Complex.rectangular(*z.rectangular.map{|v| @context.num_class[v]})
      else
        Complex.rectangular(@context.num_class[z])
      end
    end

    def abs(z)
      z.abs
    end

    def self.math_function(mth, negative_arg_to_complex=false, extra_prec=3, &blk)
      class_eval do
        define_method mth do |*args|
          is_complex = args.detect{|z| z.kind_of?(Complex)}
          if is_complex || (negative_arg_to_complex && args.first<0)
            num_class.context(:extra_precision => extra_prec) do |extra_context|
              #Complex.rectangular(*blk[extra_context, *args].map{|v| @context.plus(v)})
              Complex.rectangular(*instance_exec(extra_context, *args, &blk).map{|v| @context.plus(v)})
            end
          else
            @context.send(mth, *args) # Num(@context.send(mth, *args)) ?
          end
        end
      end
    end

    math_function :exp do |mth, z|
      re_exp = mth.exp(z.real)
      [re_exp * mth.cos(z.imag), re_exp * mth.sin(z.imag)]
    end

    math_function :log, true do |mth, x, *args| # we can do |mth, x, b=nil| in Ruby 1.9 but not in 1.8
      b = args.first
      r, theta = Num(x).polar
      [mth.log(r, b), theta]
    end

    math_function :ln, true do |mth, x|
      r, theta = Num(x).polar
      [mth.ln(r), theta]
    end

    math_function :log2, true do |mth, x|
      r, theta = Num(x).polar
      [mth.log2(r), theta]
    end

    math_function :log10, true do |mth, x|
      r, theta = Num(x).polar
      [mth.log10(r), theta]
    end

    math_function :power, true, 4 do |mth, x, y|
      if (y.respond_to?(:zero?) && y.zero?) || y==0
        [1,0]
      else
        r, theta = Num(x).polar
        ore, oim = y.kind_of?(Complex) ? y.rectangular : [y, 0]
        log_r = mth.ln(r)
        nr = mth.exp(ore*log_r - oim*theta)
        ntheta = theta*ore + oim*log_r
        [nr*mth.cos(ntheta), nr*mth.sin(ntheta)]
      end
    end

    def sqrt(z)
      z_is_complex = z.kind_of?(Complex)
      if z_is_complex || z<0
        if z_is_complex
          re = im = nil
          num_class.context(:extra_precision=>3) do |mth|
            z = Num(z)
            i_sign = z.imag.sign
            r = abs(z)
            x = z.real
            re = ((r+x)/2).sqrt
            im = ((r-x)/2).sqrt.copy_sign(i_sign)
          end
          fix_rect(re, im)
        else
          Complex(0, @context.sqrt(-z))
        end
      else
        @context.sqrt(z)
      end
    end

    math_function :sin do |mth, z|
      [mth.sin(z.real)*mth.cosh(z.imag), mth.cos(z.real)*mth.sinh(z.imag)]
    end

    math_function :cos do |mth, z|
      [mth.cos(z.real)*mth.cosh(z.imag), -mth.sin(z.real)*mth.sinh(z.imag)]
    end

    math_function :tan do |mth, z|
      mth.cmath{sin(z)/cos(z)}.rectangular
    end

    math_function :atan do |mth, z|
      i = Complex(0,mth.num_class[1])
      mth.cmath{i*ln((i+z)/(i-z))*num_class.one_half}.rectangular
    end

    math_function :sinh do |mth, z|
      [mth.sinh(z.real)*mth.cos(z.imag), mth.cosh(z.real)*mth.sin(z.imag)]
    end

    math_function :cosh do |mth, z|
      [mth.cosh(z.real)*mth.cos(z.imag), mth.sinh(z.real)*mth.sin(z.imag)]
    end

    math_function :tanh do |mth, z|
      mth.cmath{sinh(z)/cosh(z)}.rectangular
    end

    math_function :asinh do |mth, z|
      mth.cmath{ln(z+sqrt(z*z+1))}.rectangular
    end

    def asin(z)
      z_is_complex = z.kind_of?(Complex)
      if z_is_complex || z.abs>1
        # z = Complex(1) unless z_is_complex
        i = Complex(0,@context.num_class[1])
        fix num_class.context(:extra_precision=>3).cmath {
          -i*ln(i*z + sqrt(1-z*z))
        }
      else
        @context.asin(z)
      end
    end

    def acos(z)
      z_is_complex = z.kind_of?(Complex)
      if z_is_complex || z.abs>1
        # z = Complex(1) unless z_is_complex
        i = Complex(0,@context.num_class[1])
        fix num_class.context(:extra_precision=>3).cmath {
          -i*ln(z + i*sqrt(1-z*z))
        }
      else
        @context.acos(z)
      end
    end

    def acosh(z)
      z_is_complex = z.kind_of?(Complex)
      if z_is_complex || z<=1
        # z = Complex(1) unless z_is_complex
        fix num_class.context(:extra_precision=>3).cmath{ ln(z + sqrt(z*z-1)) }
      else
        @context.acosh(z)
      end
    end

    def atanh(z)
      z_is_complex = z.kind_of?(Complex)
      if z_is_complex || z.abs>1
        # z = Complex(1) unless z_is_complex
        # i = Complex(0, @context.num_class[1])
        fix num_class.context(:extra_precision=>3).cmath{ num_class.one_half*ln((1+z)/(1-z)) }
      else
        @context.atanh(z)
      end
    end

    extend Forwardable
    def_delegators :@context, :pi


    private

    def fix_rect(re, im)
      Complex(@context.plus(re), @context.plus(im))
    end

    def fix_polar(r, theta)
      re = im = nil
      num_class.context(:extra_precision=>3) do |mth|
        re = r*mth.cos(theta)
        im = r*mth.sin(theta)
      end
      fix_rect(re, im)
    end

    def fix(z)
      fix_rect(*z.rectangular)
    end

  end # ComplexContext

  class Num

    def self.ccontext(*args)
      ComplexContext(self.context(*args))
    end

    class ContextBase
      def cmath(*parameters, &blk)
        # if ComplexContext is derived from ContextBase: return ComplexContext(self).math(*parameters, &blk)
        num_class.context(self) do
          if parameters.empty?
            Flt.ComplexContext(num_class.context).instance_eval(&blk)
          else
            Flt.xiComplexContext(num_class.context).instance_exec(*parameters, &blk)
          end
        end
      end
    end

  end # Num

  module_function
  def ComplexContext(context)
    ComplexContext.new(context)
  end

  module DecNum::CMath
    include MathBase
    num_class(DecNum){num_class.ccontext}
    math_function(*Trigonometry.public_instance_methods)
    math_function :exp, :log, :log2, :log10, :sqrt
  end

  module BinNum::CMath
    include MathBase
    num_class(BinNum){num_class.ccontext}
    math_function(*Trigonometry.public_instance_methods)
    math_function :exp, :log, :log2, :log10, :sqrt
  end

end # Flt