// binary operator +

// General includes.
#include "cl_sysdep.h"

// Specification.
#include "cln/integer.h"


// Implementation.

#include "cl_I.h"
#include "cl_DS.h"

namespace cln {

const cl_I operator+ (const cl_I& x, const cl_I& y)
{
    // Methode:
    // x Fixnum ->
    //   y Fixnum -> beide direkt addieren, mit L_to_I beenden
    //   y Bignum -> falls x=0, y; sonst beide zu DS machen, addieren.
    // x Bignum ->
    //   y Fixnum -> falls y=0, x; sonst beide zu DS machen, addieren.
    //   y Bignum -> beide zu DS machen, addieren.
      var uintD* MSDptr;
      var uintC len;
      var uintD* LSDptr;
      // MSDptr/len/LSDptr bilden die DS des Ergebnisses.
      if (fixnump(x))
        { // x ist Fixnum
          if (fixnump(y))
            { // x,y sind Fixnums
              #if (cl_value_len < intLsize)
              return L_to_I( FN_to_L(x) + FN_to_L(y) ); // als 32-Bit-Zahlen addieren
              #elif (cl_word_size==64)
              return Q_to_I( FN_to_Q(x) + FN_to_Q(y) ); // als 64-Bit-Zahlen addieren
              #else // (cl_value_len == intLsize)
              var sint32 xhi = sign_of(FN_to_L(x));
              var uint32 xlo = FN_to_L(x);
              var sint32 yhi = sign_of(FN_to_L(y));
              var uint32 ylo = FN_to_L(y);
              xhi += yhi;
              xlo += ylo;
              if (xlo < ylo) { xhi += 1; }
              return L2_to_I(xhi,xlo);
              #endif
            }
            else
            { // x ist Fixnum, y ist Bignum, also y länger
              #if (intDsize==64)
              var sint64 x_ = FN_to_L(x); // Wert von x
              #else
              var sint32 x_ = FN_to_L(x); // Wert von x
              #endif
              if (FN_L_zerop(x,x_)) { return y; } // bei x=0 Ergebnis y
              CL_ALLOCA_STACK;
              BN_to_NDS_1(y, MSDptr=,len=,LSDptr=); // NDS zu y bilden.
              // len>=bn_minlength. len>pFN_maxlength erzwingen:
              if ((bn_minlength==pFN_maxlength) && (len==pFN_maxlength))
                { var sintD sign = sign_of_sintD(mspref(MSDptr,0));
                  lsprefnext(MSDptr) = sign; len++;
                }
              // x_ zu den oberen pFN_maxlength Digits von y addieren:
              {
                #if (intDsize==64)
                var uint64 y_ = lspref(LSDptr,0);
                var uint64 y_new = y_+(uint64)x_;
                lspref(LSDptr,0) = y_new;
                #else
                var uint32 y_ = pFN_maxlength_digits_at(LSDptr);
                var uint32 y_new = y_+(uint32)x_;
                set_pFN_maxlength_digits_at(LSDptr,y_new);
                #endif
                var uintD* midptr = LSDptr lspop pFN_maxlength;
                if (y_new < y_)
                  { // Carry.
                    if (!FN_L_minusp(x,x_)) // kürzerer Summand war positiv
                      // Dann ist ein positiver Übertrag weiterzutragen
                      // (Beispiel: 0002FFFC + 0007 = 00030003)
                      { DS_1_plus(midptr,len-pFN_maxlength); }
                  }
                  else
                  { // Kein Carry.
                    if (FN_L_minusp(x,x_)) // kürzerer Summand war negativ
                      // Dann ist ein negativer Übertrag weiterzutragen
                      // (Beispiel: 00020003 + FFF5 = 0001FFF8)
                      { DS_minus1_plus(midptr,len-pFN_maxlength); }
              }   }
              return DS_to_I(MSDptr,len); // DS wieder zum Integer machen
            }
        }
        else
        { // x ist Bignum
          if (fixnump(y))
            { // x ist Bignum, y ist Fixnum, also x länger
              #if (intDsize==64)
              var sint64 y_ = FN_to_L(y); // Wert von y
              #else
              var sint32 y_ = FN_to_L(y); // Wert von y
              #endif
              if (FN_L_zerop(y,y_)) { return x; } // bei y=0 Ergebnis x
              CL_ALLOCA_STACK;
              BN_to_NDS_1(x, MSDptr=,len=,LSDptr=); // NDS zu x bilden.
              // len>=bn_minlength. len>pFN_maxlength erzwingen:
              if ((bn_minlength==pFN_maxlength) && (len==pFN_maxlength))
                { var sintD sign = sign_of_sintD(mspref(MSDptr,0));
                  lsprefnext(MSDptr) = sign; len++;
                }
              // y_ zu den oberen pFN_maxlength Digits von x addieren:
              {
                #if (intDsize==64)
                var uint64 x_ = lspref(LSDptr,0);
                var uint64 x_new = x_+(uint64)y_;
                lspref(LSDptr,0) = x_new;
                #else
                var uint32 x_ = pFN_maxlength_digits_at(LSDptr);
                var uint32 x_new = x_+(uint32)y_;
                set_pFN_maxlength_digits_at(LSDptr,x_new);
                #endif
                var uintD* midptr = LSDptr lspop pFN_maxlength;
                if (x_new < x_)
                  { // Carry.
                    if (!FN_L_minusp(y,y_)) // kürzerer Summand war positiv
                      // Dann ist ein positiver Übertrag weiterzutragen
                      // (Beispiel: 0002FFFC + 0007 = 00030003)
                      { DS_1_plus(midptr,len-pFN_maxlength); }
                  }
                  else
                  { // Kein Carry.
                    if (FN_L_minusp(y,y_)) // kürzerer Summand war negativ
                      // Dann ist ein negativer Übertrag weiterzutragen
                      // (Beispiel: 00020003 + FFF5 = 0001FFF8)
                      { DS_minus1_plus(midptr,len-pFN_maxlength); }
              }   }
              return DS_to_I(MSDptr,len); // DS wieder zum Integer machen
            }
            else
            { // x und y sind Bignums
              CL_ALLOCA_STACK;
              if (TheBignum(x)->length > TheBignum(y)->length)
                { // x das längere von beiden.
                  BN_to_NDS_1(x, MSDptr=,len=,LSDptr=); // NDS zu x bilden.
                  var const uintD* yMSDptr;
                  var uintC ylen;
                  var const uintD* yLSDptr;
                  BN_to_NDS_nocopy(y, yMSDptr=,ylen=,yLSDptr=); // NDS zu y bilden.
                  // yMSDptr/ylen/yLSDptr bilden die DS des kürzeren Arguments y.
                  // Es ist len>ylen.
                  // addieren:
                  { var uintD* midptr = LSDptr lspop ylen;
                    var uintD carry = addto_loop_lsp(yLSDptr,LSDptr,ylen);
                    if (carry)
                      { // Carry.
                        if ((sintD)mspref(yMSDptr,0) >=0) // kürzerer Summand war positiv
                          // Dann ist ein positiver Übertrag weiterzutragen
                          // (Beispiel: 0002FFFC + 0007 = 00030003)
                          { DS_1_plus(midptr,len-ylen); }
                      }
                      else
                      { // Kein Carry.
                        if ((sintD)mspref(yMSDptr,0) <0) // kürzerer Summand war negativ
                          // Dann ist ein negativer Übertrag weiterzutragen
                          // (Beispiel: 00020003 + FFF5 = 0001FFF8)
                          { DS_minus1_plus(midptr,len-ylen); }
                  }   }
                  return DS_to_I(MSDptr,len); // DS wieder zum Integer machen
                }
                else
                { // y das längere von beiden.
                  BN_to_NDS_1(y, MSDptr=,len=,LSDptr=); // NDS zu y bilden.
                  var const uintD* xMSDptr;
                  var uintC xlen;
                  var const uintD* xLSDptr;
                  BN_to_NDS_nocopy(x, xMSDptr=,xlen=,xLSDptr=); // NDS zu x bilden.
                  // xMSDptr/xlen/xLSDptr bilden die DS des kürzeren Arguments x.
                  // len>xlen erzwingen:
                  if (len==xlen)
                    { var sintD sign = sign_of_sintD(mspref(MSDptr,0));
                      lsprefnext(MSDptr) = sign; len++;
                    }
                  // addieren:
                  { var uintD* midptr = LSDptr lspop xlen;
                    var uintD carry = addto_loop_lsp(xLSDptr,LSDptr,xlen);
                    if (carry)
                      { // Carry.
                        if ((sintD)mspref(xMSDptr,0) >=0) // kürzerer Summand war positiv
                          // Dann ist ein positiver Übertrag weiterzutragen
                          // (Beispiel: 0002FFFC + 0007 = 00030003)
                          { DS_1_plus(midptr,len-xlen); }
                      }
                      else
                      { // Kein Carry.
                        if ((sintD)mspref(xMSDptr,0) <0) // kürzerer Summand war negativ
                          // Dann ist ein negativer Übertrag weiterzutragen
                          // (Beispiel: 00020003 + FFF5 = 0001FFF8)
                          { DS_minus1_plus(midptr,len-xlen); }
                  }   }
                  return DS_to_I(MSDptr,len); // DS wieder zum Integer machen
                }
            }
        }
}

}  // namespace cln
