#include "Matrix.h"

#include <cstddef>
#include <mkl_cblas.h>
#include <string>
#include <cstring>
#include <iostream>

#include "AuroraDefs.h"

#include <Eigen/Core>
#include <Eigen/Dense>

#include "Function.h"

namespace Aurora{
    typedef void(*CalcFuncD)(const MKL_INT n, const double a[], const MKL_INT inca, const double b[],
                             const MKL_INT incb, double r[], const MKL_INT incr);

    typedef void(*CalcFuncZ)(const MKL_INT n, const MKL_Complex16 a[], const MKL_INT inca, const MKL_Complex16 b[],
                             const MKL_INT incb, MKL_Complex16 r[], const MKL_INT incr);

    inline Matrix operatorMxA(CalcFuncD aFunc, double aScalar, const Matrix &aMatrix) {
        double *output = malloc(aMatrix.getDataSize(), aMatrix.getValueType() == Complex);
        //复数时，+和-需要特别操作，只影响实部
        if (aMatrix.getValueType() == Complex && (aFunc == &vdAddI || aFunc == &vdSubI)) {
            aFunc(aMatrix.getDataSize(), aMatrix.getData() , 2, &aScalar, 0, output ,
                  2);
            double zero = 0.0;
            aFunc(aMatrix.getDataSize(), aMatrix.getData()+1 , 2, &zero, 0, output+1 ,
                  2);
        } else{
            aFunc(aMatrix.getDataSize(), aMatrix.getData(), 1, &aScalar, 0, output, 1);
        }
        return Matrix::New(output, aMatrix.getDimSize(0), aMatrix.getDimSize(1), aMatrix.getDimSize(2),
                                   aMatrix.getValueType());
    }

    inline Matrix &operatorMxA_RR(CalcFuncD aFunc, double aScalar, Aurora::Matrix &&aMatrix) {
        if (aMatrix.getValueType() == Complex) {
            //针对实部操作
            aFunc(aMatrix.getDataSize(), aMatrix.getData(), 2, &aScalar, 0,
                  aMatrix.getData(),
                  2);
            //乘法除法需特别操作，影响虚部
            if (aFunc == &vdDivI || aFunc == &vdMulI){
                aFunc(aMatrix.getDataSize(), aMatrix.getData() + 1, 2, &aScalar, 0,
                  aMatrix.getData() + 1, 2);
            }
        } else {
            aFunc(aMatrix.getDataSize(), aMatrix.getData(), 1, &aScalar, 0,
                  aMatrix.getData(),
                  1);
        }
        return aMatrix;
    }


    inline void V_MxM_CN_Calc(
            CalcFuncD aFuncD,
            const int size, double* xC,double* yN,double *output, int DimsStride) {
        aFuncD(size, xC, DimsStride * 2, yN, 1, output, 2);
        if (aFuncD == &vdDivI || aFuncD == &vdMulI){
            aFuncD(size, xC + 1, DimsStride * 2, yN, 1, output + 1, 2);
        }
        else{
            double zero = 0.0;
            aFuncD(size, xC+1 , DimsStride*2, &zero, 0, output + 1, 2);
        }
    }

    inline double* _MxM_CN_Calc(
            CalcFuncD aFuncD,
            const int size, double* xC,double* yN, int dimsStride)
    {
        double *output = malloc(size, true);
        V_MxM_CN_Calc(aFuncD, size, xC, yN, output, dimsStride);
        return output;
    }

    inline void V_MxM_NC_Calc(
            CalcFuncD aFuncD,
            const int size, double* xN,double* yC,double *output, int DimsStride) {
        aFuncD(size, xN, DimsStride, yC, 2, output, 2);
        if (aFuncD == &vdDivI || aFuncD == &vdMulI){
            aFuncD(size, xN , DimsStride, yC+ 1, 2, output + 1, 2);
        }
        else{
            double zero = 0.0;
            aFuncD(size, yC+ 1, 2, &zero, 0, output + 1, 2);
        }
    }

    inline double* _MxM_NC_Calc(
            CalcFuncD aFuncD,
            const int size, double* xN,double* yC, int dimsStride)
    {
        double *output = malloc(size, true);
        V_MxM_NC_Calc(aFuncD, size, xN, yC, output, dimsStride);
        return output;
    }

    inline void V_MxM_NN_Calc(
            CalcFuncD aFuncD,
            const int size, double* x,double* y,double* output, int DimsStride) {
        aFuncD(size, x, DimsStride, y, 1, output,1);
    }

    inline double* _MxM_NN_Calc(
            CalcFuncD aFuncD,
            const int size, double* x,double* y, int DimsStride) {
        double *output = malloc(size);
        V_MxM_NN_Calc(aFuncD, size, x, y, output, DimsStride);
        return output;
    }

    inline void V_MxM_CC_Calc(
            CalcFuncZ aFuncZ, const int size, double* x,double* y,double* output,
            int DimsStride) {
        aFuncZ(size, (std::complex<double> *) x, DimsStride,
               (std::complex<double> *) y, 1, (std::complex<double> *) output, 1);
    }

    inline double* _MxM_CC_Calc(
            CalcFuncZ aFuncZ, const int size, double* x,double* y,
            int DimsStride) {
        double *output = malloc(size, true);
        V_MxM_CC_Calc(aFuncZ, size, x, y, output, DimsStride);
        return output;
    }

    inline Matrix operatorMxM(CalcFuncD aFuncD, CalcFuncZ aFuncZ, const Matrix &aMatrix,
                              const Matrix &aOther) {
        // 2v2,1v1,3v3
        if (aMatrix.compareShape(aOther)) {
            int DimsStride = 1;
            double *data = nullptr;
            if (aMatrix.getValueType() != aOther.getValueType()) {
                if (aMatrix.getValueType() == Normal) {
                    data = _MxM_NC_Calc(aFuncD, aMatrix.getDataSize(), aMatrix.getData(), aOther.getData(),
                                        DimsStride);
                    return Matrix::New(data, aOther);
                } else {
                    data = _MxM_CN_Calc(aFuncD, aMatrix.getDataSize(), aMatrix.getData(), aOther.getData(),
                                        DimsStride);
                    return Matrix::New(data, aMatrix);
                }

            } else if (aMatrix.getValueType() == Normal) {
                data = _MxM_NN_Calc(aFuncD, aMatrix.getDataSize(), aMatrix.getData(), aOther.getData(), DimsStride);
                return Matrix::New(data, aMatrix);
            } else {
                data = _MxM_CC_Calc(aFuncZ, aMatrix.getDataSize(), aMatrix.getData(), aOther.getData(), DimsStride);
                return Matrix::New(data, aMatrix);
            }
        }
        //0v3, 0v2
        else if (aMatrix.getDataSize()==1){
            if (aMatrix.getValueType() ==Normal)return operatorMxA(aFuncD,aMatrix.getData()[0],aOther);
            else{
                std::cerr<<"M * M fail, Complex scalar * not support now!"<<std::endl;
                return Matrix();
            }
        }
        //3v0, 2v0
        else if (aOther.getDataSize()==1){
            if (aOther.getValueType() ==Normal)return operatorMxA(aFuncD,aOther.getData()[0],aMatrix);
            else{
                std::cerr<<"M * M fail, Complex scalar * not support now!"<<std::endl;
                return Matrix();
            }
        }
        //other
        else {
            std::cerr<<"M * M Shape error!If you want do vector * Matrix, use repmat to replicate the vector"<<std::endl;
            return Matrix();
        }
    }

    inline Matrix operatorMxM_RR(CalcFuncD aFuncD, CalcFuncZ aFuncZ, const Aurora::Matrix &aMatrix,
                                 Aurora::Matrix &&aOther,bool oppside = false) {
        if (aMatrix.compareShape(aOther)) {
            int DimsStride = 1;
            double* X = oppside?aOther.getData():aMatrix.getData();
            double* Y = oppside?aMatrix.getData():aOther.getData();
            if (aMatrix.getValueType() != aOther.getValueType()) {
                //aOther is not a complex matrix
                if (aMatrix.getValueType() == Complex) {
                    double *output = oppside?_MxM_NC_Calc(aFuncD,aMatrix.getDataSize(),  aOther.getData(),aMatrix.getData(),DimsStride)
                        :_MxM_CN_Calc(aFuncD,aMatrix.getDataSize(), aMatrix.getData(), aOther.getData(),DimsStride);
                    return Matrix::New(output, aMatrix);
                }
                //aOther is a complex matrix, use aOther as output
                if (oppside){
                    V_MxM_CN_Calc(aFuncD, aMatrix.getDataSize(), aOther.getData(),aMatrix.getData(),aOther.getData(),DimsStride);
                }
                else
                {
                    V_MxM_NC_Calc(aFuncD, aMatrix.getDataSize(), aMatrix.getData(),aOther.getData(),aOther.getData(),DimsStride);
                }
                return aOther;
            } else if (aMatrix.getValueType() == Normal) {
                V_MxM_NN_Calc(aFuncD, aMatrix.getDataSize(), X, Y, aOther.getData(),
                              DimsStride);
                return aOther;
            } else {
                V_MxM_CC_Calc(aFuncZ, aMatrix.getDataSize(), X, Y, aOther.getData(),
                              DimsStride);
                return aOther;
            }
        }
        //0v3, 0v2
        else if (aMatrix.getDataSize()==1){
            if (aMatrix.getValueType() ==Normal){
                return operatorMxA(aFuncD,aMatrix.getData()[0],std::forward<Aurora::Matrix &&>(aOther));
            }
            else{
                std::cerr<<"M * M fail, Complex scalar * not support now!"<<std::endl;
                return Matrix();
            }
        }
            //3v0, 2v0
        else if (aOther.getDataSize()==1){
            if (aOther.getValueType() ==Normal)return operatorMxA(aFuncD,aOther.getData()[0],aMatrix);
            else{
                std::cerr<<"M * M fail, Complex scalar * not support now!"<<std::endl;
                return Matrix();
            }
        }
            //other
        else {
            std::cerr<<"M * M Shape error!If you want do vector * Matrix, use repmat to replicate the vector"<<std::endl;
            return Matrix();
        }
    }
};


namespace Aurora {
    Matrix::Matrix(std::shared_ptr<double> aData, std::vector<int> aInfo, ValueType aValueType)
            : mValueType(aValueType)
            , mData(aData)
            , mInfo(aInfo)
    {
        size_t infoSize = mInfo.size();
        for(;infoSize<3;++infoSize)
        {
            mInfo.push_back(1);
        }
    }

    Matrix::Matrix(const Matrix::MatrixSlice& slice) {
        auto temp = slice.toMatrix();
        this->mData = temp.mData;
        this->mInfo = temp.mInfo;
        this->mValueType = temp.mValueType;
    }

    bool Matrix::isNull() const {
        return !mData || mInfo.empty();
    }

    bool Matrix::isNan() const
    {
        for(size_t i=0; i<getDataSize(); ++i)
        {
            if(mData.get()[i] == mData.get()[i])
            {
                return false;
            }
        }
        return true;
    }

    bool Matrix::isScalar() const {
        return (getDimSize(0) == 1 &&
                getDimSize(1) == 1 &&
                getDimSize(2) < 2);
    }

    double Matrix::getScalar() const {
        if (isNull()) return 0.0;
        if (isNull()) return 0.0;
        return getData()[0];
    }

    bool Matrix::isVector() const{
        if (getDimSize(2)>1) return false;
        if (isScalar()) return false;
        return getDimSize(0) == 1 ||
                getDimSize(1) == 1;
    }

    bool Matrix::isMKLAllocated() const
    {
        return mMKL_Allocated;
    }

    int Matrix::getDims() const {
        if(mInfo[2] > 1)
        {
            return 3;
        }
        return 2;
    }

    double *Matrix::getData() const {
        return mData.get();
    }

    int Matrix::getDimSize(int aIndex) const {
        if (aIndex >= 0 && aIndex < 3) {
            return mInfo.at(aIndex);
        }
        return 0;
    }


    size_t Matrix::getDataSize() const {
        if (!mData.get())return 0;
        size_t ret = 1;
        for (auto v: mInfo) {
            ret *= v;
        }
        return ret;
    }

    void Matrix::forceReshape(int rows, int columns, int slices)
    {
        mInfo = {rows,columns,slices};
    }

    bool Matrix::compareShape(const Matrix &other) const {
        if (mInfo[2] == 1 && other.mInfo[2] == 1) {
            if (mInfo[0]==1 && other.mInfo[1] == 1 && mInfo[1] == other.mInfo[0]) return true;
            if (mInfo[1]==1 && other.mInfo[0] == 1 && mInfo[0] == other.mInfo[1]) return true;
        }
        for (int i = 0; i < mInfo.size(); ++i) {
            if (mInfo[i] != other.mInfo[i]) return false;
        }
        return true;
    }

    Matrix Matrix::New(double *data, int rows, int cols, int slices, ValueType type) {
        if (!data) return Matrix();
        std::vector<int> vector(3);
        vector[0]=rows;
        vector[1] = (cols > 0?cols:1);
        vector[2] = (slices > 0?slices:1);
        Matrix ret({data, free}, vector);
        if (type != Normal)ret.setValueType(type);
        ret.mMKL_Allocated = true;
        return ret;
    }

    Matrix Matrix::New(double *data, const Matrix &shapeMatrix) {
        return New(data,
                   shapeMatrix.getDimSize(0),
                   shapeMatrix.getDimSize(1),
                   shapeMatrix.getDimSize(2),
                   shapeMatrix.getValueType());
    }

    Matrix Matrix::New(const Matrix &shapeMatrix) {
        double *newBuffer = malloc(shapeMatrix.getDataSize(), shapeMatrix.getValueType());
        return New(newBuffer, shapeMatrix);
    }

    Matrix Matrix::fromRawData(double *data, int rows, int cols, int slices, ValueType type) {
        if (!data) return Matrix();
        std::vector<int> vector;
        vector.push_back(rows);
        if (cols > 0)vector.push_back(cols);
        if (slices > 0)vector.push_back(slices);
        Matrix ret({data, std::default_delete<double[]>()}, vector);
        if (type != Normal)ret.setValueType(type);
        return ret;
    }

    Matrix Matrix::copyFromRawData(double *data, int rows, int cols, int slices, ValueType type) {
        int colsize = cols>0?cols:1;
        int slicesize = slices>0?slices:1;
        int size = rows*colsize*slicesize;
        double *newBuffer = Aurora::malloc(size, type);
        cblas_dcopy(size*type,data,1,newBuffer,1);
        return New(newBuffer,rows,cols,slices,type);
    }

    Matrix Matrix::deepCopy() const {
        double *newBuffer = malloc(getDataSize(), getValueType()==Complex);
        cblas_dcopy(getDataSize() * getValueType(),getData(),1,newBuffer,1);
        return New(newBuffer,
                   getDimSize(0),
                   getDimSize(1),
                   getDimSize(2),
                   getValueType());
    }
    //operation +
    Matrix Matrix::operator+(double aScalar) const { return operatorMxA(&vdAddI, aScalar, *this);}
    Matrix operator+(double aScalar, const Matrix &matrix) {return matrix + aScalar;}
    Matrix Matrix::operator+(const Matrix &matrix) const {return operatorMxM(vdAddI, vzAddI, *this, matrix);}
    Matrix &operator+(double aScalar, Matrix &&matrix) {
        return operatorMxA_RR(&vdAddI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix &operator+(Matrix &&matrix,double aScalar) {
        return operatorMxA_RR(&vdAddI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix Matrix::operator+(Matrix &&aMatrix) const {
        return operatorMxM_RR(&vdAddI,&vzAddI,*this,std::forward<Matrix&&>(aMatrix));
    }

    Matrix operator+(Matrix &&aMatrix, Matrix &aOther) {
        return operatorMxM_RR(&vdAddI,&vzAddI,aOther,std::forward<Matrix&&>(aMatrix),true);
    }
    //operation -
    Matrix Matrix::operator-(double aScalar) const { return operatorMxA(&vdSubI, aScalar, *this);}
    Matrix operator-(double aScalar, const Matrix &matrix) {return matrix - aScalar;}
    Matrix Matrix::operator-(const Matrix &matrix) const {return operatorMxM(vdSubI, vzSubI, *this, matrix);}
    Matrix &operator-(double aScalar, Matrix &&matrix) {
        return operatorMxA_RR(&vdSubI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix &operator-(Matrix &&matrix,double aScalar) {
        return operatorMxA_RR(&vdSubI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix Matrix::operator-(Matrix &&aMatrix) const {
        return operatorMxM_RR(&vdSubI,&vzSubI,*this,std::forward<Matrix&&>(aMatrix));
    }

    Matrix operator-(Matrix &&aMatrix, Matrix &aOther) {
        return operatorMxM_RR(&vdSubI,&vzSubI,aOther,std::forward<Matrix&&>(aMatrix),true);
    }
    //operation *
    Matrix Matrix::operator*(double aScalar) const { return operatorMxA(&vdMulI, aScalar, *this);}
    Matrix operator*(double aScalar, const Matrix &matrix) {return matrix * aScalar;}
    Matrix Matrix::operator*(const Matrix &matrix) const {return operatorMxM(vdMulI, vzMulI, *this, matrix);}
    Matrix &operator*(double aScalar, Matrix &&matrix) {
        return operatorMxA_RR(&vdMulI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix &operator*(Matrix &&matrix,double aScalar) {
        return operatorMxA_RR(&vdMulI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix Matrix::operator*(Matrix &&aMatrix) const {
        return operatorMxM_RR(&vdMulI,&vzMulI,*this,std::forward<Matrix&&>(aMatrix));
    }

    Matrix operator*(Matrix &&aMatrix, Matrix &aOther) {
        return operatorMxM_RR(&vdMulI,&vzMulI,aOther,std::forward<Matrix&&>(aMatrix),true);
    }
    //operation /
    Matrix Matrix::operator/(double aScalar) const { return operatorMxA(&vdDivI, aScalar, *this);}
    Matrix operator/(double aScalar, const Matrix &matrix) {return matrix / aScalar;}
    Matrix Matrix::operator/(const Matrix &matrix) const {return operatorMxM(vdDivI, vzDivI, *this, matrix);}
    Matrix &operator/(double aScalar, Matrix &&matrix) {
        return operatorMxA_RR(&vdDivI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix &operator/(Matrix &&matrix,double aScalar) {
        return operatorMxA_RR(&vdDivI,aScalar, std::forward<Matrix&&>(matrix));
    }
    Matrix Matrix::operator/(Matrix &&aMatrix) const {
        return operatorMxM_RR(&vdDivI,&vzDivI,*this,std::forward<Matrix&&>(aMatrix));
    }

    Matrix operator/(Matrix &&aMatrix, Matrix &aOther) {
        return operatorMxM_RR(&vdDivI,&vzDivI,aOther,std::forward<Matrix&&>(aMatrix),true);
    }
    //operator ^ (pow)
    Matrix Matrix::operator^(int times) const { return operatorMxA(&vdPowI, times, *this);}

    Matrix operator^( Matrix &&matrix,int times) {
        return operatorMxA(&vdPowI, times, std::forward<Matrix&&>(matrix));
    }

    double& Matrix::operator[](size_t index) { return getData()[index];}

    void Matrix::printf() {
        if(isNull())
        {
            ::printf("null matrix\n");
            return;
        }
        int k_count = getDimSize(2);
        int j_count = getDimSize(1);
        int complexstep = 1;
        const char* mark = "+";
        if(mValueType == Complex)
        {
            complexstep = 2;
        }
        for (int k = 0; k <k_count; ++k) {
        ::printf("slice %d:\r\n[",k);
        for (int i = 0; i < getDimSize(0); ++i) {
            ::printf("[");
            for (int j = 0; j < j_count; ++j) {
                ::printf("%f2",getData()[(k*getDimSize(1)*getDimSize(0)+j*getDimSize(0)+i)*complexstep]);
                if(mValueType == Complex)
                {
                    double value = getData()[(k*getDimSize(1)*getDimSize(0)+j*getDimSize(0)+i)*complexstep+1];
                    if(value<0)
                    {
                        mark = "";
                    }
                    ::printf("%s%f2i",mark,value);
                    mark = "+";
                }
                ::printf(", ");
            }
            ::printf("]\r\n");
        }
        ::printf("]\r\n");
        }
    }

    void Matrix::printfShape() {
        std::cerr << "Matrix shape:(" << getDimSize(0) << "," << getDimSize(1) << ","
                  << getDimSize(2) << ")" << std::endl;
    }

    Matrix::MatrixSlice Matrix::operator()(int aRowIdx, int aColIdx, int aSliceIdx) const {
        std::vector<int> dims = {aRowIdx, aColIdx, aSliceIdx};
        std::vector<int> allDimIndex;
        int mode = 0;
        for (int j = 0; j < 3; ++j) {
            if (dims[j]==$ && getDims()>j){
                ++mode;
                allDimIndex.push_back(j);
            }
        }
        int rowStride = 1;
        int colStride =  getDimSize(0);
        int sliceStride =  getDimSize(0)*getDimSize(1);
        int strides[3] = {rowStride, colStride, sliceStride};
        int rowOffset = aRowIdx == $ ? 0 : aRowIdx;
        int colOffset = aColIdx == $ ? 0 : aColIdx;
        int sliceOffset = aSliceIdx == $ ? 0 : aSliceIdx;
        double *startPointer = getData() + (rowStride * rowOffset
                                            + colStride * colOffset
                                            + sliceStride * sliceOffset) * getValueType();
        int size1 = allDimIndex.empty()?1:getDimSize(allDimIndex[0]);
        int stride1 = allDimIndex.empty()?1:strides[allDimIndex[0]];
        switch (mode) {
            //matrix mode
            case 2:{
                int size2 = getDimSize(allDimIndex[1]);
                int stride2 = strides[allDimIndex[1]];
                return Matrix::MatrixSlice(size1, stride1, startPointer, getValueType(), mode, size2, stride2);
            }
            //vector mode & default
            case 1:
            {
                return Matrix::MatrixSlice(size1, stride1, startPointer, getValueType(), mode);
            }
            //scalar mode or ALL $
            case 0:
            default: {
                return Matrix::MatrixSlice(1 , 1, startPointer,getValueType(), 0);
            }
        }
    }

    Matrix Matrix::operator>(double aScalar) const {
        if (isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        double *ret = malloc(getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, getDataSize());
        result.setConstant(0.0);
        result = (v.array() > aScalar).select(1.0, result);
        return New(ret, getDimSize(0), getDimSize(1), getDimSize(2));
    }

    Matrix operator>(double aScalar, const Matrix &matrix) {
        if (matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (aScalar>v.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator>(const Matrix &matrix) const {
        if (isComplex() || matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        if (!compareShape(matrix) && !isScalar() && !matrix.isScalar()) {
            std::cerr << "Matrix not equal, matrix 1(" << matrix.getDimSize(0) << "," << matrix.getDimSize(1) << ","
                      << matrix.getDimSize(2) << "), matrix 2(" << getDimSize(0) << "," << getDimSize(1) << ","
                      << getDimSize(2) << ")" << std::endl;
            return Matrix();
        }
        if(isScalar()){
            return getData()[0]>matrix;
        }
        if(matrix.isScalar()){
            return (*this)>matrix.getData()[0];
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        Eigen::Map<Eigen::VectorXd> v2(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (v.array() > v2.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator>=(double aScalar) const {
        if (isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        double *ret = malloc(getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, getDataSize());
        result.setConstant(0.0);
        result = (v.array() >= aScalar).select(1.0, result);
        return New(ret, getDimSize(0), getDimSize(1), getDimSize(2));
    }

    Matrix operator>=(double aScalar, const Matrix &matrix) {
        if (matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (aScalar >= v.array() ).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator>=(const Matrix &matrix) const {
        if (isComplex() || matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        if (!compareShape(matrix) && !isScalar() && !matrix.isScalar()) {
            std::cerr << "Matrix not equal, matrix 1(" << matrix.getDimSize(0) << "," << matrix.getDimSize(1) << ","
                      << matrix.getDimSize(2) << "), matrix 2(" << getDimSize(0) << "," << getDimSize(1) << ","
                      << getDimSize(2) << ")" << std::endl;
            return Matrix();
        }
        if(isScalar()){
            return getData()[0]<=matrix;
        }
        if(matrix.isScalar()){
            return (*this)>=matrix.getData()[0];
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        Eigen::Map<Eigen::VectorXd> v2(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (v.array() >= v2.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator<(double aScalar) const {
        if (isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        double *ret = malloc(getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, getDataSize());
        result.setConstant(0.0);
        result = (v.array() < aScalar).select(1.0, result);
        return New(ret, getDimSize(0), getDimSize(1), getDimSize(2));
    }

    Matrix operator<(double aScalar, const Matrix &matrix) {
        if (matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (aScalar < v.array() ).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator<(const Matrix &matrix) const {
        if (isComplex() || matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        if (!compareShape(matrix) && !isScalar() && !matrix.isScalar()) {
            std::cerr << "Matrix not equal, matrix 1(" << matrix.getDimSize(0) << "," << matrix.getDimSize(1) << ","
                      << matrix.getDimSize(2) << "), matrix 2(" << getDimSize(0) << "," << getDimSize(1) << ","
                      << getDimSize(2) << ")" << std::endl;
            return Matrix();
        }
        if(isScalar()){
            return getData()[0]<matrix;
        }
        if(matrix.isScalar()){
            return (*this)<matrix.getData()[0];
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        Eigen::Map<Eigen::VectorXd> v2(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (v.array() < v2.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator<=(double aScalar) const {
        if (isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        double *ret = malloc(getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, getDataSize());
        result.setConstant(0.0);
        result = (v.array() <= aScalar).select(1.0, result);
        return New(ret, getDimSize(0), getDimSize(1), getDimSize(2));
    }

    Matrix operator<=(double aScalar, const Matrix &matrix) {
        if (matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (aScalar <= v.array() ).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator<=(const Matrix &matrix) const {
        if (isComplex() || matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        if (!compareShape(matrix) && !isScalar() && !matrix.isScalar()) {
            std::cerr << "Matrix not equal, matrix 1(" << matrix.getDimSize(0) << "," << matrix.getDimSize(1) << ","
                      << matrix.getDimSize(2) << "), matrix 2(" << getDimSize(0) << "," << getDimSize(1) << ","
                      << getDimSize(2) << ")" << std::endl;
            return Matrix();
        }
        if(isScalar()){
            return getData()[0]<=matrix;
        }
        if(matrix.isScalar()){
            return (*this)<=matrix.getData()[0];
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        Eigen::Map<Eigen::VectorXd> v2(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (v.array() <= v2.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator==(double aScalar) const {
        if (isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        double *ret = malloc(getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, getDataSize());
        result.setConstant(0.0);
        result = (v.array() == aScalar).select(1.0, result);
        return New(ret, getDimSize(0), getDimSize(1), getDimSize(2));
    }

    Matrix operator==(double aScalar, const Matrix &matrix) {
        if (matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        Eigen::Map<Eigen::VectorXd> v(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (aScalar == v.array() ).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix Matrix::operator==(const Matrix &matrix) const {
        if (isComplex() || matrix.isComplex()) {
            std::cerr << "Complex cann't compare!" << std::endl;
            return Matrix();
        }
        if (!compareShape(matrix) && !isScalar() && !matrix.isScalar()) {
            std::cerr << "Matrix not equal, matrix 1(" << matrix.getDimSize(0) << "," << matrix.getDimSize(1) << ","
                      << matrix.getDimSize(2) << "), matrix 2(" << getDimSize(0) << "," << getDimSize(1) << ","
                      << getDimSize(2) << ")" << std::endl;
            return Matrix();
        }
        if(isScalar()){
            return getData()[0]==matrix;
        }
        if(matrix.isScalar()){
            return (*this)==matrix.getData()[0];
        }
        Eigen::Map<Eigen::VectorXd> v(getData(), getDataSize());
        Eigen::Map<Eigen::VectorXd> v2(matrix.getData(), matrix.getDataSize());
        double *ret = malloc(matrix.getDataSize());
        Eigen::Map<Eigen::VectorXd> result(ret, matrix.getDataSize());
        result.setConstant(0.0);
        result = (v.array() == v2.array()).select(1.0, result);
        return Matrix::New(ret, matrix.getDimSize(0), matrix.getDimSize(1), matrix.getDimSize(2));
    }

    Matrix operator-(Matrix &&aMatrix) {
        int size = aMatrix.getDataSize()*aMatrix.getValueType();
        double zero = 0.0;
        vdSubI(size,&zero,0,aMatrix.getData(),1,aMatrix.getData(),1);
        return aMatrix;
    }

    Matrix operator-(const Matrix &aMatrix) {
        double *newBuffer = malloc(aMatrix.getDataSize(), aMatrix.getValueType()==Complex);
        double zero = 0.0;
        if (aMatrix.isComplex()){
            vdSubI( aMatrix.getDataSize(),&zero,0,aMatrix.getData(),2,newBuffer,2);
            vdSubI( aMatrix.getDataSize(),&zero,0,aMatrix.getData()+1,2,newBuffer+1,2);
        }
        else{
            vdSubI( aMatrix.getDataSize(),&zero,0,aMatrix.getData(),1,newBuffer,1);
        }
        
        return Matrix::New(newBuffer,aMatrix);
    }

    Matrix Matrix::mul(const Matrix& aOther) const {
        if (isNull() || aOther.isNull()){
            std::cerr<<"Mul operation fail, Matrix is null!"<<std::endl;
            return Matrix();
        }
        if (getDimSize(2)>1 || aOther.getDimSize(2)>1){
            std::cerr<<"In matrix mul operation not support 3d data!"<<std::endl;
            return Matrix();
        }
        // V x ?
        if (isVector())
        {
            // row vector
            if (getDimSize(0) > 1){
                std::cerr<<"In matrix mul operation Left vector must be a column vector!"<<std::endl;
                return Matrix();
            }
            //V x Scalar
            if (aOther.isScalar())
            {
                return (*this)*aOther.getScalar();
            }
            else{
                //right size
                if(getDimSize(1) == aOther.getDimSize(0))
                {
                    auto result = deepCopy();
                    cblas_dgemv(CblasColMajor,CblasTrans,aOther.getDimSize(0),aOther.getDimSize(1),1.0,
                    aOther.getData(),aOther.getDimSize(0),getData(),1,0,result.getData(),1);
                    return result;
                }
                std::cerr << "Matrix mul operation fail, can't do Matrix("
                          << getDimSize(0) << "," << getDimSize(1)
                          << ") * Matrix(" << aOther.getDimSize(0) << ","
                          << aOther.getDimSize(1) << ")" << std::endl;
            }
        }
        else if(isScalar()){
            return getScalar()*aOther;
        }
        // M x ?
        else if(aOther.isScalar()){
            return aOther.getScalar()*(*this);
        } else if (aOther.isVector()) {
            // right size
            if (getDimSize(1) == aOther.getDimSize(0)) {
                auto result = aOther.deepCopy();
                cblas_dgemv(CblasColMajor, CblasNoTrans, getDimSize(0),
                            getDimSize(1), 1.0, getData(),
                            getDimSize(0), aOther.getData(), 1, 0,
                            result.getData(), 1);
                return result;
            }
            std::cerr << "Matrix mul operation fail, can't do Matrix("
                      << getDimSize(0) << "," << getDimSize(1) << ") * Matrix("
                      << aOther.getDimSize(0) << "," << aOther.getDimSize(1)
                      << ")" << std::endl;
            return Matrix();
        }
        //MxM
        else {
            // fit size
            if (getDimSize(1) == aOther.getDimSize(0))
            {
                double * output = malloc(getDimSize(0)*aOther.getDimSize(1));
                int M =  getDimSize(0);
                int N =  aOther.getDimSize(1);
                int K =  getDimSize(1);
                cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, M, N, K, 1.0, getData(), M, aOther.getData(),K, 0, output, M);
                return Matrix::New(output,M,N);
            }
            std::cerr << "Matrix mul operation fail, can't do Matrix("
                      << getDimSize(0) << "," << getDimSize(1) << ") * Matrix("
                      << aOther.getDimSize(0) << "," << aOther.getDimSize(1)
                      << ")" << std::endl;
            return Matrix();
        }
    }

    Matrix::MatrixSlice::MatrixSlice(int aSize,int aStride, double* aData, ValueType aType, int aSliceMode,int aSize2, int aStride2):
    mSliceMode(aSliceMode),mData(aData),
    mSize(aSize),mSize2(aSize2),
    mStride(aStride),mStride2(aStride2),
    mType(aType)
    {

    }

    Matrix::MatrixSlice &Matrix::MatrixSlice::operator=(const Matrix::MatrixSlice &slice) {
        if (this==&slice) return *this;
        if(!mData){
            std::cerr <<"Assign value fail!Des data pointer is null!";
            return *this;
        }
        if(!slice.mData){
            std::cerr <<"Assign value fail!Src data pointer is null!"<<std::endl;
            return *this;
        }
        if (slice.mSliceMode!=mSliceMode) {
            std::cerr << "Assign value fail!Src slice(dims count:" << slice.mSliceMode
                      << "), not match of des(dims count:" << mSliceMode << ")!" << std::endl;
            return *this;
        }
        if (slice.mSize!=mSize) {
            std::cerr <<"Assign value fail!Src slice(dim 1 size:"<< slice.mSize <<"), not match of des(dim 1 size:"<<mSize<<")!"<<std::endl;
            return *this;
        }
        if (slice.mSize2!=mSize2) {
            std::cerr <<"Assign value fail!Src slice(dim 2 size:"<< slice.mSize2 <<"), not match of des(dim 2 size:"<<mSize2<<")!"<<std::endl;
            return *this;
        }
        if (slice.mType!=mType) {
            std::cerr <<"Assign value fail!Src slice(value type:"<< slice.mType <<"), not match of des(value type:"<<mType<<")!"<<std::endl;
            return *this;
        }
        switch (mSliceMode) {
            case 2:{
                if (mType== Normal) {
//                    cblas_dcopy_batch_strided(mSize, slice.mData, slice.mStride, slice.mStride2, mData, mStride,
//                                              mStride2, mSize2);
                    for (int i = 0; i < mSize2; ++i) {
                        cblas_dcopy(mSize,slice.mData + i*slice.mStride2,slice.mStride,mData+i*mStride2,mStride);
                    }
                }
                else {
//                    cblas_zcopy_batch_strided(mSize,(std::complex<double>*)slice.mData,slice.mStride,slice.mStride2,
//                            (std::complex<double>*)mData,mStride,mStride2,mSize2);
                    for (int i = 0; i < mSize2; ++i) {
                        cblas_zcopy(mSize, (std::complex<double> *) (slice.mData + i * slice.mStride2), slice.mStride,
                                    (std::complex<double> *) (mData + i * mStride2), mStride);
                    }
                }
                break;
            }
            case 1:{
                if (mType== Normal){
                    cblas_dcopy(mSize,slice.mData,slice.mStride,mData,mStride);
                }
                else {
                    cblas_dcopy(mSize*2, slice.mData, slice.mStride, mData, mStride);
                }
                break;
            }
            case 0:
            default:{
                mData[0] = slice.mData[0];
                if (mType != Normal)mData[1] = slice.mData[1];
            }
        }
        return *this;
    }

    Matrix::MatrixSlice &Matrix::MatrixSlice::operator=(const Matrix &matrix) {
        if(!mData){
            std::cerr <<"Assign value fail!Des data pointer is null!";
            return *this;
        }
        if(!matrix.getData()){
            std::cerr <<"Assign value fail!Src data pointer is null!";
            return *this;
        }
        switch (mSliceMode) {
            case 2://matrix mode
            {
                if(matrix.getDims()!=2 || matrix.getDimSize(1)<=1)
                {
                    std::cerr <<"Assign value fail!Src matrix(dims count:"<< matrix.getDims() <<"), not match of des(dims count:"<<mSliceMode<<")!";
                    return *this;
                }
                if (matrix.getDimSize(1)!=mSize2) {
                    std::cerr <<"Assign value fail!Src slice(dim 2 size:"<< matrix.getDimSize(1) <<"), not match of des(dim 2 size:"<<mSize2<<")!";
                    return *this;
                }
                break;
            }
            case 1:{
                if(!matrix.isVector())
                {
                    std::cerr <<"Assign value fail!Src matrix(dims count:"<< matrix.getDims() <<"), not match of des(dims count:"<<mSliceMode<<")!";
                    return *this;
                }
                break;
            }
            case 0:{
                if(!matrix.isScalar()){
                    std::cerr <<"Assign value fail!Src matrix(dims count:"<< matrix.getDims() <<"), not match of des(dims count:"<<mSliceMode<<")!";
                    return *this;
                }
            }
        }
        if (matrix.getDimSize(0)!=mSize) {
            std::cerr <<"Assign value fail!Src matrix(dim 1 size:"<< matrix.getDimSize(0)<<"), not match of des(dim 1 size:"<<mSize<<")!";
            return *this;
        }
        if (matrix.getValueType()!=mType) {
            std::cerr <<"Assign value fail!Src slice(value type:"<< matrix.getValueType() <<"), not match of des(value type:"<<mType<<")!";
            return *this;
        }
        switch (mSliceMode) {
            //matrix mode
            case 2:{
                if (mType== Normal) {
                    cblas_dcopy_batch_strided(mSize, matrix.getData(), 1, matrix.getDimSize(0), mData, mStride,
                                              mStride2, mSize2);
                }
                else {
                    cblas_zcopy_batch_strided(mSize,(std::complex<double>*)matrix.getData(),1,matrix.getDimSize(0),
                                              (std::complex<double>*)mData,mStride,mStride2,mSize2);
                }
                break;
            }
            //vector mode
            case 1:{
                if (mType== Normal){
                    cblas_dcopy(mSize,matrix.getData(),1,mData,mStride);
                }
                else {
                    cblas_zcopy(mSize, (std::complex<double> *) matrix.getData(),1,
                                (std::complex<double> *) mData, mStride);
                }
                break;
            }
            //scalar mode
            case 0:
            default:{
                mData[0] = matrix.getData()[0];
                if (mType != Normal)mData[1] = matrix.getData()[1];
            }
        }
        return *this;
    }

    Matrix::MatrixSlice &Matrix::MatrixSlice::operator=(double value) {
        if(!mData){
            std::cerr <<"Assign value fail!Des data pointer is null!";
            return *this;
        }
        if (mSliceMode!=0) {
            std::cerr <<"Assign value fail!Des slicemode is"<<mSliceMode<<", not scalar mode!";
            return *this;
        }
        if (mSize!=1) {
            std::cerr <<"Assign value fail!Des  size:"<<mSize<<", not scalar mode!";
            return *this;
        }
        if (mType!=Normal) {
            std::cerr <<"Assign value fail!Des type is complex!";
            return *this;
        }
        mData[0]=value;
        return *this;
    }

    Matrix::MatrixSlice &Matrix::MatrixSlice::operator=(std::complex<double> value) {
        if(!mData){
            std::cerr <<"Assign value fail!Des data pointer is null!";
            return *this;
        }
        if (mSliceMode!=0) {
            std::cerr <<"Assign value fail!Des slicemode is"<<mSliceMode<<", not scalar mode!";
            return *this;
        }
        if (mSize!=1) {
            std::cerr <<"Assign value fail!Des  size:"<<mSize<<", not scalar mode!";
            return *this;
        }
        if (mType!=Complex) {
            std::cerr <<"Assign value fail!Des type is not complex!";
            return *this;
        }
        mData[0]=value.real();
        mData[1]=value.imag();
        return *this;
    }

    Matrix Matrix::MatrixSlice::toMatrix() const {
        double * data = (double *) malloc(mSize*(mSize2>0?mSize2:1) ,mType == Complex);

        switch (mSliceMode) {
            case 2:{
                if (mType== Normal) {
                    cblas_dcopy_batch_strided(mSize,  mData, mStride,
                                              mStride2,data, 1, mSize, mSize2);
                }
                else {
                    cblas_zcopy_batch_strided(mSize, (std::complex<double> *) mData, mStride, mStride2,
                                              (std::complex<double> *) data, 1, mSize,
                                              mSize2);
                }
                break;
            }
            case 1:{
                if (mType== Normal){
                    cblas_dcopy(mSize,mData,mStride,data,1);
                }
                else {
                    cblas_zcopy(mSize, (std::complex<double> *) mData, mStride,
                                (std::complex<double> *) data, 1);
                }
                break;
            }
            case 0:
            default:{
                data[0]= mData[0];
                if (mType != Normal) data[1] = mData[1];
            }
        }

        return Matrix::New(data,mSize,mSize2,1,mType);
    }
}




