#include "AdvMatrixUtil.hh"

#include <cmath>
#include <ios>
#include <ostream>
#include <iostream>
#include <iomanip>

//const string AdvMatrixUtil::className=string("maticUtil");

/**
 *  the digits  for an unsigned integer number
 *
 * \param[in] n  a unsigned integer number
 */
UInt4 AdvMatrixUtil::digitNum(UInt4 n) {
    return n==0 ? 1 : static_cast<Int4>( floor( log10(static_cast<Double>(n)) ) ) + 1;
}

/**
 *  the digits  for an unsigned integer number
 *
 * \param[in] n  a unsigned integer number
 */
UInt4 AdvMatrixUtil::digitNum(Int4 n) {
    return n==0 ? 1 : static_cast<Int4>( floor( log10( abs(static_cast<Double>(n)) ) ) ) + 1;
}

/**
 *  width for an matrix element
 *
 *  \param[in] fiexForm  the output form for the matrix elements
 *  \param[in] pric      the precision for a matrix element
 *
 * the width for a matrix element
 * element Width (precision = effective digits)
 *     precision(max 16) + exponent(4) + minus('-', 1) + point('.', 1) + expoment_symbol('e|E', 1) in the scientific form
 *     precision(max 16)               + minus('-', 1) + point('.', 1)                             in the fixed      form
 */
UInt4 AdvMatrixUtil::widthPerElement(const Bool fixForm, const UInt4 prec) {
    return (fixForm ? prec + 2 : prec + 7);
}


/**
 *  output a header line
 *
 *  \param[in] os             outpur stream
 *  \param[in] indetWidth     the indent width
 *  \param[in] withRowNo      the flag  for the row No. column
 *  \param[in] rowNoWidth     the width for the row No. column
 *  \param[in] colMin         the column No. from that the matrix be output
 *  \param[in] nElemPerLine   the n successive columns of the matrix to be output from colMin
 *  \param[in] widthElemCol   the width per matrix element or column No
 */
void AdvMatrixUtil::outpurHeaderLine(ostream &os,
                                  const UInt4 indentWidth, const Bool withRowNo, const UInt4 rowNoWidth,
                                  const UInt4 colMin, const UInt4 nElemPerLine, const UInt4 widthElemCol) {

    string memberName = string("outputHeaderLine");

    //DebugMessage(className, memberName, "withRowNo=%u\n", withRowNo);
    //DebugMessage(className, memberName, "colMin=%u nElemOerLine=%u\n", colMin, nElemPerLine);
    os << setw(indentWidth) << " ";
    if (withRowNo) {
        os << setw(rowNoWidth) << " ";
        os << setw(1) << " ";
    }
    os << resetiosflags(std::ios::scientific) << setw(widthElemCol) << setiosflags(std::ios::right) << colMin;
    for (UInt4 j=colMin+1; j<colMin+nElemPerLine; ++j) {
        os << setw(1) << " ";
        os << setw(widthElemCol) << setiosflags(std::ios::right) << j;
    }
    os << endl;
}

/**
 *  output column No. headers given by user
 *
 *  \param[in] os             outpur stream
 *  \param[in] indentWidth    the indent width
 *  \param[in] withRowNo      the flag  for the row No. column
 *  \param[in] rowNoWidth     the width for the row No. column
 *  \param[in] columnNo       the column No given by user
 *  \param[in] colMin         the column No. from that the matrix be output
 *  \param[in] nElemPerLine   the n successive columns of the matrix to be output from colMin
 *  \param[in] widthElemCol   the width per matrix element or column No
 */
void AdvMatrixUtil::outputUserHeader(ostream &os,
                                  const UInt4 indentWidth, const Bool withRowNo, const UInt4 rowNoWidth,
                                  const vector<Int4> &columnNo, const UInt4 from, const UInt4 nElemPerLine, const UInt4 widthElemCol) {
    string memberName = string("outputUserHeader()");

    os << setw(indentWidth) << " ";
    if (withRowNo) {
        os << setw(rowNoWidth) << " ";
        os << setw(1) << " ";
    }
    os << resetiosflags(std::ios::scientific) << setw(widthElemCol) << setiosflags(std::ios::right) << columnNo.at(from);
    for (UInt4 j=from+1; j<from+nElemPerLine; ++j) {
        os << setw(1) << " ";
        os << setw(widthElemCol) << setiosflags(std::ios::right) << columnNo.at(j);;
    }
    os << endl;
}

/**
 *  output n successive columns of a row of a matrix from the given column
 *
 *  \param[in] indentWidth  the indent width
 *  \param[in] withRowNo    the flag  for row No. columm
 *  \param[in] widthRowNo   the width for row No. columm
 *  \param[in] row          the row to be output
 *  \param[in] colMin       the minmum No. to be output
 *  \param[in] nElemPerLine n successive columns to be output form colMin
 *  \param[in] fixForm      fix form when true, scientific form otherwise
 *  \param[in] widthElemCol the column width per matrix element
 *  \param[in] prec         the precision for a matrix element
 */
void AdvMatrixUtil::outputPartialRow(ostream &os,
                                  const UInt4 indentWidth,   const Bool withRowNo,     const UInt4 widthRowNo,   const UInt4 rowNo,
                                  const vector<Double> &row, const UInt4 colMin,       const UInt4 nElemPerLine,
                                  const Bool fixForm,        const UInt4 widthElemCol, const Int4 prec) {

    string memberName = string("outputPartialRow");

    //assert(withRowNo && (widthRowNo > 0) || ! widthRowNo );
    assert(0 <= colMin && colMin < row.size());
    assert(nElemPerLine > 0);
    assert(fixForm ? widthElemCol-prec >=2 : widthElemCol-prec >=7);

    //DebugMessage(className, memberName, "rowNo=%u withRowNo=%u\n", rowNo, withRowNo);
    //DebugMessage(className, memberName, "colMin=%u nElemOerLine=%u\n", colMin, nElemPerLine);
    //DebugMessage(className, memberName, "widthElemCol=%u prec=%u\n", widthElemCol, prec);
    // a row of the matrix
    os << setw(indentWidth) << " ";
    if (withRowNo) {
        os << setw(widthRowNo) << setiosflags(std::ios::right) << rowNo;
        os << setw(1) << " ";
    }
    if (fixForm) {
        os << resetiosflags(std::ios::scientific) << setw(widthElemCol) << setprecision(prec) << setiosflags(std::ios::right) << row.at(colMin);
    } else {
        os <<   setiosflags(std::ios::scientific) << setw(widthElemCol) << setprecision(prec) << setiosflags(std::ios::right) << row.at(colMin);
    }
    for (UInt4 j=colMin+1; j<colMin+nElemPerLine; ++j) {
        os << setw(1) << " ";
        if (fixForm) {
            os << resetiosflags(std::ios::scientific) << setw(widthElemCol) << setprecision(prec) << setiosflags(std::ios::right) << row.at(j);
        } else {
            os <<   setiosflags(std::ios::scientific) << setw(widthElemCol) << setprecision(prec) << setiosflags(std::ios::right) << row.at(j);
        }
    }
    os << endl;
}

/**
 *  output a matrix with format
 *
 *  \param[in] indentWidth the indent width
 *  \param[in] m           a matrix
 *  \param[in] rowFirst    the first row to be output
 *  \param[in] nRow        the number of row to be output
 *  \param[in] colFirst    the first column to be output
 *  \param[in] nCol        the number of column to be output
 *  \param[in] fix         the flag for fixed form or scientific form, fix form when the flag is true
 *  \param[in] prec        the precision for an matrix element
 *  \param[in] withRowNo   the flag for printing row numvers
 *  \param[in] withColNo   the flag for printing column numvers
 *  \param[in] lineLength  the length of a Line
 */
void AdvMatrixUtil::outputMatrix(ostream &os,
                              const UInt4 indentWidth, const vector< vector<Double> > &m,
                              const UInt4 rowFirst, const UInt4 nRow, const UInt4 colFirst, const UInt4 nCol,
                              const Bool fixForm, const UInt4 prec, const Bool withRowNo, const Bool withColNo,
                              const UInt4 lineLength) {

    string memberName = string("outputMatrix");

    assert(0 <= rowFirst && rowFirst <m.size());
    assert(rowFirst + nRow <= m.size()      );
    assert(colFirst + nCol <= m.at(0).size());
    assert(prec <= 16); // maxmum digits for double precision

    UInt4 widthRowNo   = withRowNo ? this->digitNum(static_cast<UInt4>(rowFirst+nRow-1)) : 0;
    UInt4 widthElemCol = withColNo ? max(this->digitNum(static_cast<UInt4>(colFirst+nCol-1)), this->widthPerElement(fixForm, prec)) : this->widthPerElement(fixForm, prec) ;
    UInt4 maxColPerLine = (lineLength - indentWidth - (withRowNo ? widthRowNo : 0))/(widthElemCol+1);
    //DebugMessage(className, memberName, "widthRowNo=%u widthElemCol=%u maxColPerLine=%u\n", widthRowNo, widthElemCol, maxColPerLine);

    UInt4 q=nCol/maxColPerLine;
    UInt4 r=nCol%maxColPerLine;

    UInt4 colMin=colFirst;
    for (UInt4 p=0; p<q; ++p) {
        //DebugMessage(className, memberName, "p=%u q=%u r=%u\n", p, q, r);
        // header line
        if (withColNo) {
            this->outpurHeaderLine(os, indentWidth, withRowNo, widthRowNo, colMin, maxColPerLine, widthElemCol);
        }
        // matrix elements
        for (UInt4 i=rowFirst; i<rowFirst+nRow; ++i) {
            this->outputPartialRow(os, indentWidth, withRowNo, widthRowNo, i, m.at(i), colMin, maxColPerLine, fixForm, widthElemCol, prec);
        }
        os << endl;
        colMin += maxColPerLine;
    }

    if (r > 0) {
        // header line
        if (withColNo) {
            this->outpurHeaderLine(os, indentWidth, withRowNo, widthRowNo, colMin, r,             widthElemCol);
        }
        // matrix elements
        for (UInt4 i=rowFirst; i<rowFirst+nRow; ++i) {
            this->outputPartialRow(os, indentWidth, withRowNo, widthRowNo, i, m.at(i), colMin, r,             fixForm, widthElemCol, prec);
        }
        os << endl;
    }

}


/**
 *  output a matrix with given column headers
 *
 *  \param[in] indentWidth the indent width
 *  \param[in] m           a matrix
 *  \param[in] rowNo l     row No. as row headers
 *  \param[in] rowFirst    the first row to be output
 *  \param[in] nRow        the number of row to be output
 *  \param[in] colFirst    the first column to be output
 *  \param[in] nCol        the number of column to be output
 *  \param[in] fix         the flag for fixed form or scientific form, fix form when the flag is true
 *  \param[in] prec        the precision for an matrix element
 *  \param[in] withColNo   the flag for printing column numvers
 *  \param[in] lineLength  the length of a Line
 */
void AdvMatrixUtil::outputMatrixWithUsersRowHeader(ostream &os,
                                                const UInt4 indentWidth, const vector< vector<Double> > &m, const vector<Int4> &columnNo,
                                                const UInt4 rowFirst, const UInt4 nRow, const UInt4 colFirst, const UInt4 nCol,
                                                const Bool fixForm, const UInt4 prec, const Bool withColNo, const UInt4 lineLength) {

    string memberName = string("outputMatrix");

    assert(0 <= rowFirst && rowFirst <m.size());
    assert(rowFirst + nRow <= m.size()      );
    assert(colFirst + nCol <= m.at(0).size());
    assert(prec <= 16); // maxmum digits for double precision

    //UInt4 widthRowNo   = withRowNo ? this->digitNum(static_cast<UInt4>(columnNo.at(rowFirst+nRow-1))) : 0;
    UInt4 widthRowNo   = this->digitNum(static_cast<UInt4>(columnNo.at(rowFirst+nRow-1)));
    //UInt4 widthElemCol = max(this->digitNum(static_cast<UInt4>(colFirst+nCol-1)), this->widthPerElement(fixForm, prec)) ;
    UInt4 widthElemCol = withColNo ? max(this->digitNum(static_cast<UInt4>(colFirst+nCol-1)), this->widthPerElement(fixForm, prec)) : this->widthPerElement(fixForm, prec) ;
    UInt4 maxColPerLine = (lineLength - indentWidth - widthRowNo)/(widthElemCol+1);

    UInt4 q=nCol/maxColPerLine;
    UInt4 r=nCol%maxColPerLine;

    UInt4 colMin=colFirst;
    for (UInt4 p=0; p<q; ++p) {

        // header line
        if (withColNo) {
            this->outpurHeaderLine(os, indentWidth, true, widthRowNo, colMin, maxColPerLine, widthElemCol);
        }

        // matrix elements
        for (UInt4 i=rowFirst; i<rowFirst+nRow; ++i) {
            this->outputPartialRow(os, indentWidth, true, widthRowNo, columnNo.at(i), m.at(i), colMin, maxColPerLine, fixForm, widthElemCol, prec);
        }
        os << endl;
        colMin += maxColPerLine;
    }

    if (r > 0) {
        // header line
        if (withColNo) {
            this->outpurHeaderLine(os, indentWidth, true, widthRowNo, colMin, r,             widthElemCol);
        }

        // matrix elements
        for (UInt4 i=rowFirst; i<rowFirst+nRow; ++i) {
            this->outputPartialRow(os, indentWidth, true, widthRowNo, columnNo.at(i), m.at(i), colMin, r,             fixForm, widthElemCol, prec);
        }
        os << endl;
    }
}

/**
 *  output a matrix with format, the version for gsl_matrix
 *
 *  \param[in] indentWidth the indent width
 *  \param[in] m           a matrix
 *  \param[in] rowFirst    the first row to be output
 *  \param[in] nRow        the number of row to be output
 *  \param[in] colFirst    the first column to be output
 *  \param[in] nCol        the number of column to be output
 *  \param[in] fix         the flag for fixed form or scientific form, fix form when the flag is true
 *  \param[in] prec        the precision for an matrix element
 *  \param[in] withRowNo   the flag for printing row numvers
 *  \param[in] withColNo   the flag for printing column numvers
 *  \param[in] lineLength  the length of a Line
 */
void AdvMatrixUtil::outputMatrix(ostream &os,
                              const UInt4 indentWidth, const gsl_matrix *m,
                              const UInt4 rowFirst, const UInt4 nRow, const UInt4 colFirst, const UInt4 nCol,
                              const Bool fixForm, const UInt4 prec, const Bool withRowNo, const Bool withColNo,
                              const UInt4 lineLength) {

    vector< vector<Double> > *vv=this->gslMatrixToMatrix(m);

    this->outputMatrix(os, indentWidth, *vv, rowFirst, nRow, colFirst, nCol, fixForm, prec, withRowNo, withColNo, lineLength);

    for (UInt4 i=0; i<vv->size(); ++i) {
        vv->at(i).clear();
    }
    vv->clear();
    delete vv;
}

/**
 *  output a matrix with given column headers, the vertiosn for gsl_matrix
 *
 *  \param[in] indentWidth the indent width
 *  \param[in] m           a matrix
 *  \param[in] columnNo    column No for column headers
 *  \param[in] rowFirst    the first row to be output
 *  \param[in] nRow        the number of row to be output
 *  \param[in] colFirst    the first column to be output
 *  \param[in] nCol        the number of column to be output
 *  \param[in] fix         the flag for fixed form or scientific form, fix form when the flag is true
 *  \param[in] prec        the precision for an matrix element
 *  \param[in] withRowNo   the flag for printing row numvers
 *  \param[in] lineLength  the length of a Line
 */
void AdvMatrixUtil::outputMatrixWithUsersRowHeader(ostream &os,
                  const UInt4 indentWidth, const gsl_matrix *m, const vector<Int4> &rowNo,
                  const UInt4 rowFirst, const UInt4 nRow, const UInt4 colFirst, const UInt4 nCol,
                  const Bool fixForm, const UInt4 prec, const Bool withColNo, const UInt4 lineLength) {

    vector< vector<Double> > *vv=this->gslMatrixToMatrix(m);

    this->outputMatrixWithUsersRowHeader(os, indentWidth, *vv, rowNo, rowFirst, nRow, colFirst, nCol, fixForm, prec, withColNo, lineLength);

    for(UInt4 i=0; i<vv->size(); ++i) {
        vv->at(i).clear();
    }
    vv->clear();
    delete vv;
}
