/*******************************************************************************
* Copyright 2010-2018 Intel Corporation
* All Rights Reserved.
*
* If this  software was obtained  under the  Intel Simplified  Software License,
* the following terms apply:
*
* The source code,  information  and material  ("Material") contained  herein is
* owned by Intel Corporation or its  suppliers or licensors,  and  title to such
* Material remains with Intel  Corporation or its  suppliers or  licensors.  The
* Material  contains  proprietary  information  of  Intel or  its suppliers  and
* licensors.  The Material is protected by  worldwide copyright  laws and treaty
* provisions.  No part  of  the  Material   may  be  used,  copied,  reproduced,
* modified, published,  uploaded, posted, transmitted,  distributed or disclosed
* in any way without Intel's prior express written permission.  No license under
* any patent,  copyright or other  intellectual property rights  in the Material
* is granted to  or  conferred  upon  you,  either   expressly,  by implication,
* inducement,  estoppel  or  otherwise.  Any  license   under such  intellectual
* property rights must be express and approved by Intel in writing.
*
* Unless otherwise agreed by Intel in writing,  you may not remove or alter this
* notice or  any  other  notice   embedded  in  Materials  by  Intel  or Intel's
* suppliers or licensors in any way.
*
*
* If this  software  was obtained  under the  Apache License,  Version  2.0 (the
* "License"), the following terms apply:
*
* You may  not use this  file except  in compliance  with  the License.  You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless  required  by   applicable  law  or  agreed  to  in  writing,  software
* distributed under the License  is distributed  on an  "AS IS"  BASIS,  WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the   License  for the   specific  language   governing   permissions  and
* limitations under the License.
*******************************************************************************/

/*
//     Intel(R) Integrated Performance Primitives. Cryptography Primitives.
//     Internal operations over GF(p) extension.
// 
//     Context:
//        cpGFpxInv()
//
*/

#include "owncp.h"
#include "pcpbnumisc.h"
#include "pcpgfpxstuff.h"
#include "gsscramble.h"

//tbcd: temporary excluded: #include <assert.h>

static BNU_CHUNK_T* gfpxPolyDiv(BNU_CHUNK_T* pQ, BNU_CHUNK_T* pR,
                        const BNU_CHUNK_T* pA,
                        const BNU_CHUNK_T* pB,
                        gsModEngine* pGFEx)
{
   if( GFP_IS_BASIC(pGFEx) )
      return NULL;

   else {
      int elemLen = GFP_FELEN(pGFEx);
      gsModEngine* pGroundGFE = GFP_PARENT(pGFEx);
      int termLen = GFP_FELEN(pGroundGFE);

      int degA = degree(pA, pGFEx);
      int degB = degree(pB, pGFEx);

      if(degB==0) {
         if( GFP_IS_ZERO(pB, termLen) )
            return NULL;
         else {
            gsModEngine* pBasicGFE = cpGFpBasic(pGroundGFE);

            cpGFpInv(pR, pB, pBasicGFE);
            cpGFpElementPadd(pR+GFP_FELEN(pGroundGFE), termLen-GFP_FELEN(pGroundGFE), 0);
            cpGFpxMul_GFE(pQ, pA, pR, pGFEx);
            cpGFpElementPadd(pR, elemLen, 0);
            return pR;
         }
      }

      if(degA < degB) {
         cpGFpElementPadd(pQ, elemLen, 0);
         cpGFpElementCopyPadd(pR, elemLen, pA, (degA+1)*termLen);
         return pR;
      }

      else {
         mod_mul mulF = GFP_METHOD(pGroundGFE)->mul;
         mod_sub subF = GFP_METHOD(pGroundGFE)->sub;

         int i, j;
         BNU_CHUNK_T* pProduct = cpGFpGetPool(2, pGroundGFE);
         BNU_CHUNK_T* pInvB = pProduct + GFP_PELEN(pGroundGFE);
         //tbcd: temporary excluded: assert(NULL!=pProduct);

         cpGFpElementCopyPadd(pR, elemLen, pA, (degA+1)*termLen);
         cpGFpElementPadd(pQ, elemLen, 0);

         cpGFpxInv(pInvB, GFPX_IDX_ELEMENT(pB, degB, termLen), pGroundGFE);

         for(i=0; i<=degA-degB && !GFP_IS_ZERO(GFPX_IDX_ELEMENT(pR, degA-i, termLen), termLen); i++) {
            /* compute q term */
            mulF(GFPX_IDX_ELEMENT(pQ, degA-degB-i, termLen),
                 GFPX_IDX_ELEMENT(pR, degA-i, termLen),
                 pInvB,
                 pGroundGFE);

            /* R -= B * q */
            cpGFpElementPadd(GFPX_IDX_ELEMENT(pR, degA-i, termLen), termLen, 0);
            for(j=0; j<degB; j++) {
               mulF(pProduct,
                    GFPX_IDX_ELEMENT(pB, j ,termLen),
                    GFPX_IDX_ELEMENT(pQ, degA-degB-i, termLen),
                    pGroundGFE);
               subF(GFPX_IDX_ELEMENT(pR, degA-degB-i+j, termLen),
                    GFPX_IDX_ELEMENT(pR, degA-degB-i+j, termLen),
                    pProduct,
                    pGroundGFE);
            }
         }

         cpGFpReleasePool(2, pGroundGFE);
         return pR;
      }
   }
}


static BNU_CHUNK_T* gfpxGeneratorDiv(BNU_CHUNK_T* pQ, BNU_CHUNK_T* pR, const BNU_CHUNK_T* pB, gsModEngine* pGFEx)
{
   if( GFP_IS_BASIC(pGFEx) )
      return NULL;

   else {
      int elemLen = GFP_FELEN(pGFEx);

      gsModEngine* pGroundGFE = GFP_PARENT(pGFEx);
      mod_mul mulF = GFP_METHOD(pGroundGFE)->mul;
      mod_sub subF = GFP_METHOD(pGroundGFE)->sub;

      int termLen = GFP_FELEN(pGroundGFE);

      BNU_CHUNK_T* pInvB = cpGFpGetPool(2, pGroundGFE);
      BNU_CHUNK_T* pTmp  = pInvB + GFP_PELEN(pGroundGFE);

      int degB = degree(pB, pGFEx);
      int i;

      //tbcd: temporary excluded: assert(NULL!=pInvB);

      cpGFpElementCopy(pR, GFP_MODULUS(pGFEx), elemLen);
      cpGFpElementPadd(pQ, elemLen, 0);

      cpGFpxInv(pInvB, GFPX_IDX_ELEMENT(pB, degB, termLen), pGroundGFE);

      for(i=0; i<degB; i++) {
         BNU_CHUNK_T* ptr;
         mulF(pTmp, pInvB, GFPX_IDX_ELEMENT(pB, i, termLen), pGroundGFE);
         ptr = GFPX_IDX_ELEMENT(pR, GFP_EXTDEGREE(pGFEx)-degB+i, termLen);
         subF(ptr, ptr, pTmp, pGroundGFE);
      }

      gfpxPolyDiv(pQ, pR, pR, pB, pGFEx);

      cpGFpElementCopy(GFPX_IDX_ELEMENT(pQ, GFP_EXTDEGREE(pGFEx)-degB, termLen), pInvB, termLen);

      cpGFpReleasePool(2, pGroundGFE);
      return pR;
   }
}

BNU_CHUNK_T* cpGFpxInv(BNU_CHUNK_T* pR, const BNU_CHUNK_T* pA, gsModEngine* pGFEx)
{
   if( GFP_IS_BASIC(pGFEx) )
      return cpGFpInv(pR, pA, pGFEx);

   if(0==degree(pA, pGFEx)) {
      gsModEngine* pGroundGFE = GFP_PARENT(pGFEx);
      BNU_CHUNK_T* tmpR = cpGFpGetPool(1, pGroundGFE);
      //tbcd: temporary excluded: assert(NULL!=tmpR);

      cpGFpxInv(tmpR, pA, pGroundGFE);

      cpGFpElementCopyPadd(pR, GFP_FELEN(pGFEx), tmpR, GFP_FELEN(pGroundGFE));
      cpGFpReleasePool(1, pGroundGFE);
      return pR;
   }

   else {
      int elemLen = GFP_FELEN(pGFEx);
      gsModEngine* pGroundGFE = GFP_PARENT(pGFEx);
      gsModEngine* pBasicGFE = cpGFpBasic(pGFEx);

      int pxVars = 6;
      int pelemLen = GFP_PELEN(pGFEx);
      BNU_CHUNK_T* lastrem = cpGFpGetPool(pxVars, pGFEx);
      BNU_CHUNK_T* rem     = lastrem + pelemLen;
      BNU_CHUNK_T* quo     = rem +  pelemLen;
      BNU_CHUNK_T* lastaux = quo + pelemLen;
      BNU_CHUNK_T* aux     = lastaux + pelemLen;
      BNU_CHUNK_T* temp    = aux + pelemLen;
      //tbcd: temporary excluded: assert(NULL!=lastrem);

      cpGFpElementCopy(lastrem, pA, elemLen);
      cpGFpElementCopyPadd(lastaux, elemLen, GFP_MNT_R(pBasicGFE), GFP_FELEN(pBasicGFE));

      gfpxGeneratorDiv(quo, rem, pA, pGFEx);
      cpGFpxNeg(aux, quo, pGFEx);

      while(degree(rem, pGFEx) > 0) {
         gfpxPolyDiv(quo, temp, lastrem, rem, pGFEx);
         SWAP_PTR(BNU_CHUNK_T, rem, lastrem); //
         SWAP_PTR(BNU_CHUNK_T, temp, rem);

         GFP_METHOD(pGFEx)->neg(quo, quo, pGFEx);
         GFP_METHOD(pGFEx)->mul(temp, quo, aux, pGFEx);
         GFP_METHOD(pGFEx)->add(temp, lastaux, temp, pGFEx);
         SWAP_PTR(BNU_CHUNK_T, aux, lastaux);
         SWAP_PTR(BNU_CHUNK_T, temp, aux);
      }
      if (GFP_IS_ZERO(rem, elemLen)) { /* gcd != 1 */
         cpGFpReleasePool(pxVars, pGFEx);
         return NULL;
      }

      {
         BNU_CHUNK_T* invRem  = cpGFpGetPool(1, pGroundGFE);
         //tbcd: temporary excluded: assert(NULL!=invRem);

         cpGFpxInv(invRem, rem, pGroundGFE);
         cpGFpxMul_GFE(pR, aux, invRem, pGFEx);

         cpGFpReleasePool(1, pGroundGFE);
      }

      cpGFpReleasePool(pxVars, pGFEx);

      return pR;
   }
}