#ifndef CONTAINER_FOR_MATRIX
#define CONTAINER_FOR_MATRIX

#include "Header.hh"
#include "StringTools.hh"

#include "ContainerBaseTemplate.hh"
#include "ContainerForVector.hh"

template<typename _SCALER_TYPE >
struct ValueColumnSizeComp {
    Bool operator() (std::pair<const std::string, _SCALER_TYPE >& item1, std::pair<const std::string, _SCALER_TYPE >& item2) {
        return item1.second.at(0).size() < item2.second.at(0).size();
    };
};


template<typename _ELEMENT_TYPE >
class AdvContainerForMatrix : public ContainerForScaler< std::vector< std::vector< _ELEMENT_TYPE > > >{

    public:
        /** constructor */
        AdvContainerForMatrix< _ELEMENT_TYPE >() { this->clear(); };

        /** destructor */
        ~AdvContainerForMatrix< _ELEMENT_TYPE >() { this->clear(); };

        /** get the row of the matrix with the specified key.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the index of the row
         */
        std::vector< _ELEMENT_TYPE > getRow(const std::string& key, const UInt4 i) throw(std::invalid_argument, std::out_of_range);

        /** get the row of the matrix with the specified key.
         *  @param[in] key  the key for an entry
         *  @param[in] j    the index of the column
         */
        std::vector< _ELEMENT_TYPE > getCol(const std::string& key, const UInt4 j) throw(std::invalid_argument, std::out_of_range);

        /** get the (i, j) element of the matrix with the specified key.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the row index
         *  @param[in] j    the column index
         */
        _ELEMENT_TYPE get(const std::string& key, const UInt4 i, const UInt4 j) throw(std::invalid_argument, std::out_of_range) ;

        /** replace the specified row to a new row.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the row index
         *  @param[in] v    the new value
         */
        void replaceRow(const std::string& key, const UInt4 i, const std::vector< _ELEMENT_TYPE >& v) ;

        /** replace the specified column to a new column.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the column index
         *  @param[in] v    the new value
         */
        void replaceCol(const std::string& key, const UInt4 j, const std::vector< _ELEMENT_TYPE >& v) ;

        /** replace the value of the specified element to a new value.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the column index
         *  @param[in] i    the column index
         *  @param[in] v    the new value
         */
        void replace(const std::string& key, const UInt4 i, const UInt4 j, const _ELEMENT_TYPE v) ;

        /** replace the specified cloumn to a new column.
         *  @param[in] key  the key for an entry
         *  @param[in] i    the row index
         *  @param[in] j    the column index
         */

        /** */
        void dump(const UInt4 indent=0, const UInt4 indentDepth=4, const std::string& keyTitle="key", const std::string& sizeTitle="size", const std::string& valueTitle="value");
};

template<typename _ELEMENT_TYPE >
std::vector< _ELEMENT_TYPE > AdvContainerForMatrix< _ELEMENT_TYPE >::getRow(const std::string& key, const UInt4 i) throw(std::invalid_argument, std::out_of_range) {
    if (! this->contain(key)) {
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
        throw std::invalid_argument(std::string("not found the entry with the specified key \"") + key + std::string("\""));
    }
    if ( i >= this->cstd::map[key].size() ) {
        StringTools tools;
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": the row index " << i << " is out of range: key="<< key << std::endl;
        throw std::out_of_range(std::string("the row index ") + tools.UInt4ToString(i) + std::string(" is out of range, key=\"") + key + std::string("\""));
    }
    return this->cstd::map[key].at(i);
};

template<typename _ELEMENT_TYPE >
std::vector< _ELEMENT_TYPE > AdvContainerForMatrix< _ELEMENT_TYPE >::getCol(const std::string& key, const UInt4 j) throw(std::invalid_argument, std::out_of_range) {
    if (! this->contain(key)) {
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
        throw std::invalid_argument(std::string("not found the entry with the specified key \"") + key + std::string("\""));
    }
    if (j >= this->cstd::map[key].at(0).size()) {
        StringTools tools;
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": the column index " << j << " is out of range: key=\""<< key << "\"" << std::endl;
        throw std::out_of_range(std::string("the column index ") + tools.UInt4ToString(j) + std::string(" is out of range, key=\"") + key + std::string("\""));
    }

    std::vector< _ELEMENT_TYPE > *col = new std::vector< _ELEMENT_TYPE >(this->cstd::map[key].size());
    for (UInt4 i=0; i<this->cstd::map[key].size(); ++i) {
        col->at(i) = this->cstd::map[key].at(i).at(j);
    }
    return *col;
}

template<typename _ELEMENT_TYPE >
_ELEMENT_TYPE AdvContainerForMatrix< _ELEMENT_TYPE >::get(const std::string& key, const UInt4 i, const UInt4 j) throw(std::invalid_argument, std::out_of_range) {
    if (! this->contain(key)) {
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
        throw std::invalid_argument(std::string("not found the entry with the specified key \"") + key + std::string("\""));
    }
    if (i >= this->cstd::map[key].size()) {
        StringTools tools;
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": the row index " << i << " is out of range: key="<< key << std::endl;
        throw std::out_of_range(std::string("the row index ") + tools.UInt4ToString(i) + std::string(" is out of range, key=\"") + key + std::string("\""));
    }
    if (j >= this->cstd::map[key].at(0).size()) {
        StringTools tools;
        std::cerr << "Error: AdvContainerForMatrix::get: " << __FILE__ << ":" << __LINE__ << ": the column index " << j << " is out of range: key=\"" << key << "\"" << std::endl;
        throw std::out_of_range(std::string("the column index ") + tools.UInt4ToString(j) + std::string(" is out of range, key=\"") + key + std::string("\""));
    }
    return this->cstd::map[key].at(i).at(j);
};

template<typename _ELEMENT_TYPE>
void AdvContainerForMatrix< _ELEMENT_TYPE >::replaceRow(const std::string& key, const UInt4 i, const std::vector< _ELEMENT_TYPE >& v) {
    if (! this->contain(key)) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceRow: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
    } else if (i >= this->cstd::map[key].size()) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceRow: " << __FILE__ << ":" << __LINE__ << ": the row index " << i << " is out of range: key=\""<< key << "\"" << std::endl;
    } else if ( v.size() != this->cstd::map[key].at(i).size() ) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceRow: " << __FILE__ << ":" << __LINE__ << ": the size of given new row is not equal to the size of the specified row." << std::endl;
    } else {
        this->cstd::map[key].at(i) = v;
    }
};

template<typename _ELEMENT_TYPE>
void AdvContainerForMatrix< _ELEMENT_TYPE >::replaceCol(const std::string& key, const UInt4 j, const std::vector< _ELEMENT_TYPE >& v) {
    if (! this->contain(key)) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceCol: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
    } else if (j >= this->cstd::map[key].size()) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceCol: " << __FILE__ << ":" << __LINE__ << ": the column index " << j << " is out of range: key=\""<< key << "\"" << std::endl;
    } else if ( v.size() != this->cstd::map[key].size() ) {
        std::cerr << "Warning: AdvContainerForMatrix::replaceCol: " << __FILE__ << ":" << __LINE__ << ": the size of the given new column is not equal to the size of the specified column." << std::endl;
    } else {
        for (UInt4 i=0; i<this->cstd::map[key].size(); ++i) {
            this->cstd::map[key].at(i).at(j) = v.at(i);
        }
    }
};

template<typename _ELEMENT_TYPE>
void AdvContainerForMatrix< _ELEMENT_TYPE >::replace(const std::string& key, const UInt4 i, const UInt4 j, const _ELEMENT_TYPE v) {
    if (! this->contain(key)) {
        std::cerr << "Warning: AdvContainerForMatrix::replace: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"." << std::endl;
    } else if (i >= this->cstd::map[key].size()) {
        std::cerr << "Warning: AdvContainerForMatrix::replace: " << __FILE__ << ":" << __LINE__ << ": the row index " << i << " is out of range: key=\""<< key << "\"" << std::endl;
    } else if (j >= this->cstd::map[key].size()) {
        std::cerr << "Warning: AdvContainerForMatrix::replace: " << __FILE__ << ":" << __LINE__ << ": the row index " << j << " is out of range: key=\""<< key << "\"" << std::endl;
    } else {
        this->cstd::map[key].at(i).at(j) = v;
    }
};


template<typename _ELEMENT_TYPE >
void AdvContainerForMatrix< _ELEMENT_TYPE >::dump(const UInt4 indent, const UInt4 indentDepth, const std::string& keyTitle, const std::string& sizeTitle, const std::string& valueTitle) {

    if (! this->cstd::map.empty()) {
        size_t maxKeyWidth = std::max(keyTitle.size(), max_element(this->cstd::map.begin(), this->cstd::map.end(), KeyLengthComp< std::vector< std::vector< _ELEMENT_TYPE > > >())->first.size());

        size_t maxRowWidth = max_element(this->cstd::map.begin(), this->cstd::map.end(), ValueSizeComp<       std::vector< std::vector< _ELEMENT_TYPE > > >())->second.size();
        size_t maxColWidth = max_element(this->cstd::map.begin(), this->cstd::map.end(), ValueColumnSizeComp< std::vector< std::vector< _ELEMENT_TYPE > > >())->second.at(0).size();
        maxRowWidth = maxRowWidth > 0 ? static_cast<size_t>(floor(log10(static_cast<Double>(maxRowWidth)))+1) : 1;
        maxColWidth = maxColWidth > 0 ? static_cast<size_t>(floor(log10(static_cast<Double>(maxColWidth)))+1) : 1;

        OutputTypeTitle ott;
        ott.title(indentDepth*indent, typeid(std::vector< std::vector< _ELEMENT_TYPE > >));
        ott.header(indentDepth*(indent+1), maxKeyWidth, keyTitle, maxRowWidth+maxColWidth+1, sizeTitle, valueTitle);

        for_each(this->cstd::map.begin(), this->cstd::map.end(), OutputMatrixEntry< _ELEMENT_TYPE >(indentDepth*(indent+1), maxKeyWidth, maxRowWidth, maxColWidth));
        std::cout << std::endl;
    }
};

#endif // CONTAINER_FOR_MATRIX
