//                                               -*- C++ -*-
/**
 *  @file  DesignProxy.cxx
 *  @brief Design matrix cached evaluation
 *
 *  Copyright 2005-2015 Airbus-EDF-IMACS-Phimeca
 *
 *  This library is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @author schueller
 *  @date   2010-11-09 13:44:00 +0100 (Tue, 09 Nov 2010)
 */

#include "Exception.hxx"
#include "PersistentObjectFactory.hxx"
#include "ResourceMap.hxx"
#include "DesignProxy.hxx"
#include "PenalizedLeastSquaresAlgorithm.hxx"

BEGIN_NAMESPACE_OPENTURNS

CLASSNAMEINIT(DesignProxy);

/* Default constructor */
DesignProxy::DesignProxy ()
  : Object()
  , lastPhiIndex_(0)
  , firstFreeColumnIndex_(0)
{}

/* Parameters constructor */
DesignProxy::DesignProxy (const NumericalSample & x,
                          const Basis & basis)
  : Object()
  , x_(x)
  , basis_(basis)
  , lastPhiIndex_(0)
  , firstFreeColumnIndex_(0)
{
  initialize();
}


void DesignProxy::initialize()
{
  // allocate cache
  UnsignedInteger cacheSize = ResourceMap::GetAsUnsignedInteger("DesignProxy-DefaultCacheSize");
  UnsignedInteger nbRows = x_.getSize();
  if (nbRows == 0) throw InvalidArgumentException(HERE) << "Cannot initialize a DesignProxy with an empty sample";

  UnsignedInteger nbCols = cacheSize / nbRows;
  if (nbCols <= 0) nbCols = 1;
  designCache_ = Matrix(nbRows, nbCols);

  // initial indirection table
  columnsIndirections_ = Indices(nbCols, nbCols);

  // Initial set of free columns
  freeColumns_ = Indices(nbCols);
  freeColumns_.fill();

  xIndirections_ = Indices(nbRows);
  xIndirections_.fill();
}


/* Virtual constructor */
DesignProxy * DesignProxy::clone() const
{
  return new DesignProxy( *this );
}


/* String converter */
String DesignProxy::__repr__() const
{
  return OSS() << "class=" << GetClassName();
}


Matrix DesignProxy::computeDesign(const Indices & indices) const
{
  const UnsignedInteger sampleSize = x_.getSize();
  UnsignedInteger basisSize = indices.getSize();

  Matrix psiAk(sampleSize, basisSize);

  for (UnsignedInteger j = 0; j < basisSize; ++ j )
  {
    for (UnsignedInteger i = 0; i < sampleSize; ++ i )
    {
      psiAk(i, j) = operator()(i, indices[j]);
    }
  }
  return psiAk;
}



/* Accessor to the design matrix values */
NumericalScalar DesignProxy::operator()(const UnsignedInteger xIndex,
                                        const UnsignedInteger phiIndex) const
{
  // redirections
  if (xIndex >= x_.getSize()) throw InvalidDimensionException(HERE) << "In DesignProxy::operator()(const UnsignedInteger xIndex, const UnsignedInteger phiIndex) const";
  const UnsignedInteger rowIndex = xIndirections_[xIndex];

  // resize indirections if needed
  const UnsignedInteger indirectionsSize = columnsIndirections_.getSize();
  if (phiIndex >= indirectionsSize)
  {
    UnsignedInteger newIndirectionsSize = indirectionsSize;
    do
    {
      newIndirectionsSize *= 2;
    }
    while (phiIndex >= newIndirectionsSize);

    Indices newColumnsIndirections(newIndirectionsSize, designCache_.getNbColumns());
    for (UnsignedInteger i = 0; i < indirectionsSize; ++ i)
    {
      newColumnsIndirections[i] = columnsIndirections_[i];
    }
    columnsIndirections_ = newColumnsIndirections;
  }

  // First, check if the value is already available
  UnsignedInteger columnIndex = columnsIndirections_[phiIndex];
  if (columnIndex < designCache_.getNbColumns()) return designCache_(rowIndex, columnIndex);

  // Second, check if there is still room to store something in the cache
  if (firstFreeColumnIndex_ >= designCache_.getNbColumns())
  {
    // no more room in the cache, check if we can reuse the last column
    if ((lastPhiIndex_ != phiIndex) || (lastColumn_.getSize() <= 0))
    {
      lastColumn_ = basis_[phiIndex](x_);
      lastPhiIndex_ = phiIndex;
    }
    return lastColumn_[rowIndex][0];
  }

  // Third, fill-in the first free column
  const UnsignedInteger freeIndex = freeColumns_[firstFreeColumnIndex_];
  columnsIndirections_[phiIndex] = freeIndex;
  const NumericalSample phiX(basis_[phiIndex](x_));
  for (UnsignedInteger i = 0; i < designCache_.getNbRows(); ++ i)
  {
    designCache_(i, freeIndex) = phiX[i][0];
  }
  ++ firstFreeColumnIndex_;

  return designCache_(rowIndex, freeIndex);
}



NumericalSample DesignProxy::getInputSample() const
{
  return x_;
}


Basis DesignProxy::getBasis() const
{
  return basis_;
}


void DesignProxy::setXIndirections(const Indices & xIndirections) const
{
  if (xIndirections.getSize() != x_.getSize()) throw InvalidArgumentException(HERE) << "Incorrect x indirections size";
  xIndirections_ = xIndirections;
}


Indices DesignProxy::getXIndirections() const
{
  return xIndirections_;
}


END_NAMESPACE_OPENTURNS
