/* file: merged_numeric_table.h */
/*******************************************************************************
* Copyright 2014-2020 Intel Corporation
*
* 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.
*******************************************************************************/

/*
//++
//  Implementation of merge numeric table.
//--
*/


#ifndef __MERGED_NUMERIC_TABLE_H__
#define __MERGED_NUMERIC_TABLE_H__

#include "data_management/data/numeric_table.h"
#include "services/daal_memory.h"
#include "services/daal_defines.h"
#include "data_management/data/data_serialize.h"

namespace daal
{
namespace data_management
{

namespace interface1
{
/**
 * @ingroup numeric_tables
 * @{
 */
/**
 *  <a name="DAAL-CLASS-DATA_MANAGEMENT__MERGEDNUMERICTABLE"></a>
 *  \brief Class that provides methods to access a collection of numeric tables as if they are joined by columns
 */
class DAAL_EXPORT MergedNumericTable : public NumericTable
{
public:
    DECLARE_SERIALIZABLE_TAG();
    DECLARE_SERIALIZABLE_IMPL();

    /**
     *  Constructor for an empty merge Numeric Table
     *  \DAAL_DEPRECATED_USE{ MergedNumericTable::create }
     */
    MergedNumericTable();

    /**
     *  Constructor for a merge Numeric Table consisting of one table
     *  \param[in]  table       Pointer to the table
     *  \DAAL_DEPRECATED_USE{ MergedNumericTable::create }
     */
    MergedNumericTable(NumericTablePtr table);

    /**
     *  Constructor for a merge Numeric Table consisting of two tables
     *  \param[in]  first      Pointer to the first table
     *  \param[in]  second     Pointer to the second table
     *  \DAAL_DEPRECATED_USE{ MergedNumericTable::create }
     */
    MergedNumericTable(NumericTablePtr first, NumericTablePtr second);

    /**
     * Constructor for an empty merge Numeric Table
     * \param[out] stat  Status of the MergedNumericTable construction
     */
    static services::SharedPtr<MergedNumericTable> create(services::Status *stat = NULL);

    /**
     * Constructs a merge Numeric Table consisting of one table
     * \param[in]  nestedTable  Pointer to the table
     * \param[out] stat         Status of the MergedNumericTable construction
     * \return     Merge Numeric Table of one table
     */
    static services::SharedPtr<MergedNumericTable> create(const NumericTablePtr &nestedTable,
                                                          services::Status *stat = NULL);

    /**
     * Constructs a merge Numeric Table consisting of two tables
     * \param[in]  first   Pointer to the first table
     * \param[in]  second  Pointer to the second table
     * \param[out] stat    Status of the MergedNumericTable construction
     * \return     Merge Numeric Table of two tables
     */
    static services::SharedPtr<MergedNumericTable> create(const NumericTablePtr &first,
                                                          const NumericTablePtr &second,
                                                          services::Status *stat = NULL);

    /**
     *  Adds the table to the right of the merge Numeric Table
     *  \param[in] table Pointer to the table
     */
    services::Status addNumericTable(NumericTablePtr table)
    {
        if (table->getDataLayout() & csrArray)
            return services::Status(services::ErrorIncorrectTypeOfInputNumericTable);

        _tables->push_back(table);

        size_t ncols = getNumberOfColumns();
        size_t cols = table->getNumberOfColumns();

        services::Status s;
        DAAL_CHECK_STATUS(s, setNumberOfColumnsImpl(ncols + cols));

        for (size_t i = 0; i < cols; i++)
        {
            NumericTableFeature &f = table->getDictionarySharedPtr()->operator[](i);
            _ddict->setFeature(f, ncols + i);
        }

        size_t obs = table->getNumberOfRows();
        if (obs != _obsnum)
        {
            if (obs < _obsnum || _tables->size() == 1)
            {
                _obsnum = obs;
            }
            DAAL_CHECK_STATUS(s, setNumberOfRowsImpl(_obsnum));
        }
        return s;
    }

    //the descriptions of the methods below are inherited from the base class
    services::Status resize(size_t nrow) DAAL_C11_OVERRIDE
    {
        for (size_t i = 0;i < _tables->size(); i++)
        {
            NumericTable* nt = (NumericTable*)(_tables->operator[](i).get());
            services::Status s = nt->resize(nrow);
            if(!s)
                return s;
        }
        _obsnum = nrow;
        return services::Status();
    }

    MemoryStatus getDataMemoryStatus() const DAAL_C11_OVERRIDE
    {
        if (_tables->size() == 0)
        {
            return notAllocated;
        }

        for (size_t i = 0;i < _tables->size(); i++)
        {
            NumericTable* nt = (NumericTable*)(_tables->operator[](i).get());
            if (nt->getDataMemoryStatus() == notAllocated)
            {
                return notAllocated;
            }
        }

        return internallyAllocated;
    }

    services::Status getBlockOfRows(size_t vector_idx, size_t vector_num,
                          ReadWriteMode rwflag, BlockDescriptor<double>& block) DAAL_C11_OVERRIDE
    {
        return getTBlock<double>(vector_idx, vector_num, rwflag, block);
    }
    services::Status getBlockOfRows(size_t vector_idx, size_t vector_num,
                          ReadWriteMode rwflag, BlockDescriptor<float>& block) DAAL_C11_OVERRIDE
    {
        return getTBlock<float>(vector_idx, vector_num, rwflag, block);
    }
    services::Status getBlockOfRows(size_t vector_idx, size_t vector_num,
                          ReadWriteMode rwflag, BlockDescriptor<int>& block) DAAL_C11_OVERRIDE
    {
        return getTBlock<int>(vector_idx, vector_num, rwflag, block);
    }

    services::Status releaseBlockOfRows(BlockDescriptor<double>& block) DAAL_C11_OVERRIDE
    {
        return releaseTBlock<double>(block);
    }
    services::Status releaseBlockOfRows(BlockDescriptor<float>& block) DAAL_C11_OVERRIDE
    {
        return releaseTBlock<float>(block);
    }
    services::Status releaseBlockOfRows(BlockDescriptor<int>& block) DAAL_C11_OVERRIDE
    {
        return releaseTBlock<int>(block);
    }

    services::Status getBlockOfColumnValues(size_t feature_idx, size_t vector_idx, size_t value_num,
                                  ReadWriteMode rwflag, BlockDescriptor<double>& block) DAAL_C11_OVERRIDE
    {
        return getTFeature<double>(feature_idx, vector_idx, value_num, rwflag, block);
    }
    services::Status getBlockOfColumnValues(size_t feature_idx, size_t vector_idx, size_t value_num,
                                  ReadWriteMode rwflag, BlockDescriptor<float>& block) DAAL_C11_OVERRIDE
    {
        return getTFeature<float>(feature_idx, vector_idx, value_num, rwflag, block);
    }
    services::Status getBlockOfColumnValues(size_t feature_idx, size_t vector_idx, size_t value_num,
                                  ReadWriteMode rwflag, BlockDescriptor<int>& block) DAAL_C11_OVERRIDE
    {
        return getTFeature<int>(feature_idx, vector_idx, value_num, rwflag, block);
    }

    services::Status releaseBlockOfColumnValues(BlockDescriptor<double>& block) DAAL_C11_OVERRIDE
    {
        return releaseTFeature<double>(block);
    }
    services::Status releaseBlockOfColumnValues(BlockDescriptor<float>& block) DAAL_C11_OVERRIDE
    {
        return releaseTFeature<float>(block);
    }
    services::Status releaseBlockOfColumnValues(BlockDescriptor<int>& block) DAAL_C11_OVERRIDE
    {
        return releaseTFeature<int>(block);
    }

    services::Status allocateBasicStatistics() DAAL_C11_OVERRIDE
    {
        size_t ncols = getNumberOfColumns();
        size_t ntables = _tables->size();
        services::SharedPtr<MergedNumericTable> minMergeNT (new MergedNumericTable());
        services::SharedPtr<MergedNumericTable> maxMergeNT (new MergedNumericTable());
        services::SharedPtr<MergedNumericTable> sumMergeNT (new MergedNumericTable());
        services::SharedPtr<MergedNumericTable> sumSqMergeNT (new MergedNumericTable());
        for (size_t i = 0; i < ntables; i++) {
            NumericTable* nt = (NumericTable*)(_tables->operator[](i).get());
            nt->allocateBasicStatistics();
            minMergeNT->addNumericTable(nt->basicStatistics.get(NumericTable::minimum));
            maxMergeNT->addNumericTable(nt->basicStatistics.get(NumericTable::maximum));
            sumMergeNT->addNumericTable(nt->basicStatistics.get(NumericTable::sum));
            sumSqMergeNT->addNumericTable(nt->basicStatistics.get(NumericTable::sumSquares));
        }
        if (basicStatistics.get(NumericTable::minimum).get() == NULL ||
            basicStatistics.get(NumericTable::minimum)->getNumberOfColumns() != ncols)
        {
            basicStatistics.set(NumericTable::minimum, minMergeNT);
        }
        if (basicStatistics.get(NumericTable::maximum).get() == NULL ||
            basicStatistics.get(NumericTable::maximum)->getNumberOfColumns() != ncols)
        {
            basicStatistics.set(NumericTable::maximum, maxMergeNT);
        }
        if (basicStatistics.get(NumericTable::sum).get() == NULL ||
            basicStatistics.get(NumericTable::sum)->getNumberOfColumns() != ncols)
        {
            basicStatistics.set(NumericTable::sum, sumMergeNT);
        }
        if (basicStatistics.get(NumericTable::sumSquares).get() == NULL ||
            basicStatistics.get(NumericTable::sumSquares)->getNumberOfColumns() != ncols)
        {
            basicStatistics.set(NumericTable::sumSquares, sumSqMergeNT);
        }
        return services::Status();
    }

protected:
    template<typename Archive, bool onDeserialize>
    services::Status serialImpl( Archive *arch )
    {
        NumericTable::serialImpl<Archive, onDeserialize>( arch );

        arch->setSharedPtrObj(_tables);

        return services::Status();
    }


private:
    template<typename T>
    void internal_inner_repack( size_t pos, size_t cols, size_t rows, size_t ncols, T *src, T *dst )
    {
        size_t i, j;

        for(i = 0; i < rows; i++)
        {
            for(j = 0; j < cols; j++)
            {
                dst[i * ncols + j + pos] = src[i * cols + j];
            }
        }
    }

    template<typename T>
    void internal_outer_repack( size_t pos, size_t cols, size_t rows, size_t ncols, T *src, T *dst )
    {
        size_t i, j;

        for(i = 0; i < rows; i++)
        {
            for(j = 0; j < cols; j++)
            {
                dst[i * cols + j] = src[i * ncols + j + pos];
            }
        }
    }

protected:

    template <typename T>
    services::Status getTBlock( size_t idx, size_t nrows, int rwFlag, BlockDescriptor<T>& block )
    {
        services::Status s;
        size_t ncols = getNumberOfColumns();
        size_t nobs = getNumberOfRows();
        block.setDetails( 0, idx, rwFlag );

        if (idx >= nobs)
        {
            block.resizeBuffer( ncols, 0 );
            return services::Status();
        }

        nrows = ( idx + nrows < nobs ) ? nrows : nobs - idx;

        if( !block.resizeBuffer( ncols, nrows ) )
            return services::Status(services::ErrorMemoryAllocationFailed);

        if( rwFlag & (int)readOnly )
        {
            size_t cols = 0;
            BlockDescriptor<T> innerBlock;
            for (size_t k = 0; k < _tables->size(); k++)
            {
                NumericTable* nt = (NumericTable*)(_tables->operator[](k).get());
                size_t lcols = nt->getNumberOfColumns();

                s |= nt->getBlockOfRows(idx, nrows, readOnly, innerBlock);

                internal_inner_repack<T>( cols, lcols, nrows, ncols, innerBlock.getBlockPtr(), block.getBlockPtr());

                s |= nt->releaseBlockOfRows(innerBlock);

                cols += lcols;
            }
        }
        return s;
    }

    template <typename T>
    services::Status releaseTBlock(BlockDescriptor<T>& block)
    {
        services::Status s;
        if(block.getRWFlag() & (int)writeOnly)
        {
            size_t ncols = getNumberOfColumns();
            size_t nrows = block.getNumberOfRows();
            size_t offset = block.getRowsOffset();
            size_t cols = 0;
            BlockDescriptor<T> innerBlock;
            for (size_t k = 0; k < _tables->size(); k++)
            {
                NumericTable* nt = (NumericTable*)(_tables->operator[](k).get());
                size_t lcols = nt->getNumberOfColumns();

                s |= nt->getBlockOfRows(offset, nrows, writeOnly, innerBlock);

                internal_outer_repack<T>( cols, lcols, nrows, ncols, block.getBlockPtr(), innerBlock.getBlockPtr());

                s |= nt->releaseBlockOfRows(innerBlock);

                cols += lcols;
            }
        }
        block.reset();
        return s;
    }

    template <typename T>
    services::Status getTFeature(size_t feat_idx, size_t idx, size_t nrows, int rwFlag, BlockDescriptor<T>& block)
    {
        services::Status s;
        size_t ncols = getNumberOfColumns();
        size_t nobs = getNumberOfRows();
        block.setDetails( feat_idx, idx, rwFlag );

        if (idx >= nobs)
        {
            block.resizeBuffer( 1, 0 );
            return services::Status();
        }

        nrows = ( idx + nrows < nobs ) ? nrows : nobs - idx;
        if( !block.resizeBuffer( 1, nrows ) )
            return services::Status(services::ErrorMemoryAllocationFailed);

        if( rwFlag & (int)readOnly )
        {
            T* buffer = block.getBlockPtr();
            for (size_t k = 0; k < _tables->size(); k++)
            {
                NumericTable* nt = (NumericTable*)(_tables->operator[](k).get());
                size_t lcols = nt->getNumberOfColumns();

                if (lcols > feat_idx)
                {
                    BlockDescriptor<T> innerBlock;
                    s |= nt->getBlockOfColumnValues(feat_idx, idx, nrows, readOnly, innerBlock);
                    T* location = innerBlock.getBlockPtr();
                    for (size_t i = 0; i < nrows; i++)
                    {
                        buffer[i] = location[i];
                    }
                    s |= nt->releaseBlockOfColumnValues(innerBlock);
                    break;
                }

                feat_idx -= lcols;
            }
        }
        return s;
    }

    template <typename T>
    services::Status releaseTFeature( BlockDescriptor<T>& block )
    {
        services::Status s;
        if (block.getRWFlag() & (int)writeOnly)
        {
            size_t feat_idx = block.getColumnsOffset();
            size_t idx = block.getRowsOffset();
            size_t nrows = block.getNumberOfRows();
            T* buffer = block.getBlockPtr();
            for (size_t k = 0; k < _tables->size(); k++)
            {
                NumericTable* nt = (NumericTable*)(_tables->operator[](k).get());
                size_t lcols = nt->getNumberOfColumns();

                if (lcols > feat_idx)
                {
                    BlockDescriptor<T> innerBlock;
                    s |= nt->getBlockOfColumnValues(feat_idx, idx, nrows, writeOnly, innerBlock);
                    T* location = innerBlock.getBlockPtr();
                    for (size_t i = 0; i < nrows; i++)
                    {
                        location[i] = buffer[i];
                    }
                    s |= nt->releaseBlockOfColumnValues(innerBlock);
                    break;
                }

                feat_idx -= lcols;
            }
        }
        block.reset();
        return s;
    }

    services::Status setNumberOfRowsImpl(size_t nrow) DAAL_C11_OVERRIDE;

    services::Status allocateDataMemoryImpl(daal::MemType type = daal::dram) DAAL_C11_OVERRIDE;

    void freeDataMemoryImpl() DAAL_C11_OVERRIDE;

protected:
    DataCollectionPtr _tables;

    MergedNumericTable(services::Status &st);

    MergedNumericTable(const NumericTablePtr &table, services::Status &st);

    MergedNumericTable(const NumericTablePtr &first, const NumericTablePtr &second, services::Status &st);
};
typedef services::SharedPtr<MergedNumericTable> MergedNumericTablePtr;
/** @} */
} // namespace interface1
using interface1::MergedNumericTable;
using interface1::MergedNumericTablePtr;

} // namespace data_management
} // namespace daal

#endif
