/* NiuTrans.Tensor - an open-source tensor library
 * Copyright (C) 2017, Natural Language Processing Lab, Northestern University.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 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.
 */

/*
 * $Created by: XIAO Tong (email: xiaotong@mail.neu.edu.cn) 2018-07-28
 * It is extreamly hot these days and i cannot sleep well. Fortunately we had 
 * good lunch of Steamed Cold Noodles. This made me feel much better!
 */

#include "Transpose.h"
#include "Merge.h"
#include "../../XUtility.h"
#include "../../XName.h"

namespace nts { // namespace nts(NiuTrans.Tensor)

/*
tensor transposition of dimensions i and j
b = transposed(a) 

For a input tensor a, we tranpose the dimensions i and j of it.
E.g., let a be a tensor of size x * y * z, i = 0, j = 2, 
then the output will be a tensor of size z * y * x.

>> a - the input tensor
>> b - the output tensor by transpose tensor a with specified dimensions i and j
>> i - the transposed dimension
>> j - the transposed dimension
*/
void _Transpose(const XTensor * a, XTensor * b, const int i, const int j)
{
    CheckNTErrors(a && b, "Empty tensors");
    CheckNTErrors(a->order == b->order, "Wrong tensor orders");
    CheckNTErrors(a->unitNum == b->unitNum && a->unitSize == b->unitSize, "Wrong tensor sizes");
    CheckNTErrors(a->order > i && i >= 0, "index of dimension is out of scope!");
    CheckNTErrors(a->order > j && j >= 0, "index of dimension is out of scope!");

    for(int k = 0; k < a->order; k++){
        if(k == i){
            CheckNTErrors(a->dimSize[k] == b->dimSize[j], "Wrong dimension size in transposition");
        }
        else if(k == j){
            CheckNTErrors(a->dimSize[k] == b->dimSize[i], "Wrong dimension size in transposition");
        }
        else{
            CheckNTErrors(a->dimSize[k] == b->dimSize[k], "Wrong dimension size in transposition");
        }
    }

    if(i == j){
        XMemCopy(b->data, b->devID, a->data, a->devID, b->unitNum * b->unitSize);
    }
    else{
        int I = MIN(i, j);
        int J = MAX(i, j);
        int * dims = new int[a->order + 1];

        for(int k = 0; k <= J; k++)
            dims[k] = a->dimSize[k];
        dims[J + 1] = -1;
        for(int k = J + 1; k < a->order; k++)
            dims[k + 1] = a->dimSize[k];

        /* reshape tensor a form (..., n_I, ..., n_J, ...) => (..., n_I, ..., n_J, 1, ...)*/
        XTensor * aTMP =  new XTensor(a->order + 1, dims, a->dataType, a->denseRatio, a->devID, a->mem);
        aTMP->data = a->data;

        for(int k = 0; k < I; k++)
            dims[k] = a->dimSize[k];
        for(int k = I + 1; k <= J; k++)
            dims[k - 1] = a->dimSize[k];
        dims[J] = a->dimSize[I];
        for(int k = J + 1; k < a->order; k++)
            dims[k] = a->dimSize[k];

        /* reshape tensor b form (..., m_I, ..., m_J, ...) => (..., m_J, m_I, ...) */
        b->Reshape(b->order, dims);

        /* tensor (..., n_I, ..., n_J, 1, ...) => tensor (..., m_J, m_I, ...) */
        _Merge(aTMP, b, J + 1, I);

        memcpy(dims, a->dimSize, sizeof(int) * a->order);
        dims[I] = a->dimSize[J];
        dims[J] = a->dimSize[I];

        /* reshape tensor b form (..., m_J, m_I, ...) => (..., m_J, ..., m_I, ...) =>  */
        b->Reshape(b->order, dims);

        aTMP->data = NULL;
        delete[] dims;
        delete aTMP;
    }
}

/*
tensor transposition of dimensions i and j (return a XTensor structure).
make a new tensor to keep the result and return it.
b = transposed(a)

For a input tensor a, we tranpose the dimensions i and j of it.
E.g., let a be a tensor of size x * y * z, i = 0, j = 2, 
then the output will be a tensor of size z * y * x.

>> a - the input tensor
>> i - the transposed dimension
>> j - the transposed dimension
<< return - the output tensor by transpose tensor a with specified dimensions i and j
*/
XTensor Transpose(const XTensor &a, const int i, const int j)
{
    CheckNTErrors(a.order > i && i >= 0, "index of dimension is out of scope!");
    CheckNTErrors(a.order > j && j >= 0, "index of dimension is out of scope!");

    int order = a.order;
    int * dimSize = new int[order];
    for(int k = 0; k < order; k++){
        if(k == i)
            dimSize[k] = a.dimSize[j];
        else if(k == j)
            dimSize[k] = a.dimSize[i];
        else
            dimSize[k] = a.dimSize[k];
    }

    float dr = (!a.isSparse) ? 1.0F : a.denseRatio;
    XTensor b(order, dimSize, a.dataType, dr, a.devID, a.mem);
    b.SetTMP();

    /* call _Transpose function */
    _Transpose(&a, &b, i, j);
    
    /* tensor connection */
    XLink::MakeLink(&a, NULL, &b, SHAPE_TRANSPOSE);
    XLink::AddParamToHeadInt(&b, i);
    XLink::AddParamToHeadInt(&b, j);

    /* destroy variables */
    delete[] dimSize;

    return b;
}

}
