/**
 * Runtime support for complex arithmetic code generation (for Posix).
 *
 * Copyright: Copyright Digital Mars 2001 - 2011.
 * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
 * Authors:   Walter Bright, Sean Kelly
 * Source: $(DRUNTIMESRC rt/_cmath2.d)
 */

/*          Copyright Digital Mars 2001 - 2011.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
module rt.cmath2;

import core.stdc.math : fabs;

debug import core.stdc.stdio : printf;

extern (C):

/****************************
 * Multiply two complex floating point numbers, x and y.
 * Input:
 *      x.re    ST3
 *      x.im    ST2
 *      y.re    ST1
 *      y.im    ST0
 * Output:
 *      ST1     real part
 *      ST0     imaginary part
 */

void _Cmul()
{
    // p.re = x.re * y.re - x.im * y.im;
    // p.im = x.im * y.re + x.re * y.im;
    asm
    {   naked                   ;
        fld     ST(1)           ; // x.re
        fmul    ST,ST(4)        ; // ST0 = x.re * y.re

        fld     ST(1)           ; // y.im
        fmul    ST,ST(4)        ; // ST0 = x.im * y.im

        fsubp   ST(1),ST        ; // ST0 = x.re * y.re - x.im * y.im

        fld     ST(3)           ; // x.im
        fmul    ST,ST(3)        ; // ST0 = x.im * y.re

        fld     ST(5)           ; // x.re
        fmul    ST,ST(3)        ; // ST0 = x.re * y.im

        faddp   ST(1),ST        ; // ST0 = x.im * y.re + x.re * y.im

        fxch    ST(4),ST        ;
        fstp    ST(0)           ;
        fxch    ST(4),ST        ;
        fstp    ST(0)           ;
        fstp    ST(0)           ;
        fstp    ST(0)           ;

        ret                     ;
    }
/+
    if (isnan(x) && isnan(y))
    {
        // Recover infinities that computed as NaN+ iNaN ...
        int recalc = 0;
        if ( isinf( a) || isinf( b) )
        {   // z is infinite
            // "Box" the infinity and change NaNs in the other factor to 0
            a = copysignl( isinf( a) ? 1.0 : 0.0, a);
            b = copysignl( isinf( b) ? 1.0 : 0.0, b);
            if (isnan( c)) c = copysignl( 0.0, c);
            if (isnan( d)) d = copysignl( 0.0, d);
            recalc = 1;
        }
        if (isinf(c) || isinf(d))
        {   // w is infinite
            // "Box" the infinity and change NaNs in the other factor to 0
            c = copysignl( isinf( c) ? 1.0 : 0.0, c);
            d = copysignl( isinf( d) ? 1.0 : 0.0, d);
            if (isnan( a)) a = copysignl( 0.0, a);
            if (isnan( b)) b = copysignl( 0.0, b);
            recalc = 1;
        }
        if (!recalc && (isinf(ac) || isinf(bd) ||
            isinf(ad) || isinf(bc)))
        {
            // Recover infinities from overflow by changing NaNs to 0 ...
            if (isnan( a)) a = copysignl( 0.0, a);
            if (isnan( b)) b = copysignl( 0.0, b);
            if (isnan( c)) c = copysignl( 0.0, c);
            if (isnan( d)) d = copysignl( 0.0, d);
            recalc = 1;
        }
        if (recalc)
        {
            x = INFINITY * (a * c - b * d);
            y = INFINITY * (a * d + b * c);
        }
    }
+/
}

/****************************
 * Divide two complex floating point numbers, x / y.
 * Input:
 *      x.re    ST3
 *      x.im    ST2
 *      y.re    ST1
 *      y.im    ST0
 * Output:
 *      ST1     real part
 *      ST0     imaginary part
 */

void _Cdiv()
{
    real x_re, x_im;
    real y_re, y_im;
    real q_re, q_im;
    real r;
    real den;

    asm
    {
        fstp    y_im    ;
        fstp    y_re    ;
        fstp    x_im    ;
        fstp    x_re    ;
    }

    if (fabs(y_re) < fabs(y_im))
    {
        r = y_re / y_im;
        den = y_im + r * y_re;
        q_re = (x_re * r + x_im) / den;
        q_im = (x_im * r - x_re) / den;
    }
    else
    {
        r = y_im / y_re;
        den = y_re + r * y_im;
        q_re = (x_re + r * x_im) / den;
        q_im = (x_im - r * x_re) / den;
    }
//printf("q.re = %g, q.im = %g\n", (double)q_re, (double)q_im);
/+
    if (isnan(q_re) && isnan(q_im))
    {
        real denom = y_re * y_re + y_im * y_im;

        // non-zero / zero
        if (denom == 0.0 && (!isnan(x_re) || !isnan(x_im)))
        {
            q_re = copysignl(INFINITY, y_re) * x_re;
            q_im = copysignl(INFINITY, y_re) * x_im;
        }
        // infinite / finite
        else if ((isinf(x_re) || isinf(x_im)) && isfinite(y_re) && isfinite(y_im))
        {
            x_re = copysignl(isinf(x_re) ? 1.0 : 0.0, x_re);
            x_im = copysignl(isinf(x_im) ? 1.0 : 0.0, x_im);
            q_re = INFINITY * (x_re * y_re + x_im * y_im);
            q_im = INFINITY * (x_im * y_re - x_re * y_im);
        }
        // finite / infinite
        else if (isinf(logbw) && isfinite(x_re) && isfinite(x_im))
        {
            y_re = copysignl(isinf(y_re) ? 1.0 : 0.0, y_re);
            y_im = copysignl(isinf(y_im) ? 1.0 : 0.0, y_im);
            q_re = 0.0 * (x_re * y_re + x_im * y_im);
            q_im = 0.0 * (x_im * y_re - x_re * y_im);
        }
    }
    return q_re + q_im * 1.0i;
+/
    asm
    {
        fld     q_re;
        fld     q_im;
    }
}

/****************************
 * Compare two complex floating point numbers, x and y.
 * Input:
 *      x.re    ST3
 *      x.im    ST2
 *      y.re    ST1
 *      y.im    ST0
 * Output:
 *      8087 stack is cleared
 *      flags set
 */

void _Ccmp()
{
  version (D_InlineAsm_X86)
    asm
    {   naked                   ;
        fucomp  ST(2)           ; // compare x.im and y.im
        fstsw   AX              ;
        sahf                    ;
        jne     L1              ;
        jp      L1              ; // jmp if NAN
        fucomp  ST(2)           ; // compare x.re and y.re
        fstsw   AX              ;
        sahf                    ;
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        ret                     ;

      L1:
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        ret                     ;
    }
  else version (D_InlineAsm_X86_64)
    asm
    {   naked                   ;
        fucomip  ST(2)          ; // compare x.im and y.im
        jne     L1              ;
        jp      L1              ; // jmp if NAN
        fucomip  ST(2)          ; // compare x.re and y.re
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        ret                     ;

      L1:
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        fstp    ST(0)           ; // pop
        ret                     ;
    }
  else
        static assert(0);
}
