/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/

#include "cdo_options.h"
#include "cdo_omp.h"
#include "remap_vars.h"
#include "remap_store_link.h"
#include <algorithm>  //  std::sort

bool
is_sorted_list(size_t n, const size_t *list)
{
  for (size_t i = 1; i < n; ++i)
    if (list[i] < list[i - 1]) return false;
  return true;
}

static void
sort_indexWeights(size_t numWeights, IndexWeight *indexWeights)
{
  std::sort(indexWeights, indexWeights + numWeights, [](const IndexWeight &a, const IndexWeight &b) { return a.index < b.index; });
}

static void
sort_indexWeights4(IndexWeight4 *indexWeights)
{
  std::sort(indexWeights, indexWeights + 4, [](const IndexWeight4 &a, const IndexWeight4 &b) { return a.index < b.index; });
}

void
store_weightlinks(int doAlloc, size_t numWeights, const size_t *indices, const double *weights, size_t cellIndex,
                  std::vector<WeightLinks> &weightLinks)
{
  auto &currentLink = weightLinks[cellIndex];
  currentLink.nlinks = 0;
  currentLink.offset = 0;

  if (numWeights)
  {
    auto *indexWeights = doAlloc ? new IndexWeight[numWeights] : currentLink.indexWeights;

    for (size_t i = 0; i < numWeights; ++i)
    {
      indexWeights[i].index = indices[i];
      indexWeights[i].weight = weights[i];
    }

    if (numWeights > 1 && !is_sorted_list(numWeights, indices)) sort_indexWeights(numWeights, indexWeights);

    currentLink.nlinks = numWeights;

    if (doAlloc) { currentLink.indexWeights = indexWeights; }
  }
}

void
store_weightlinks_bicubic(const size_t *indices, double (&weights)[4][4], size_t cellIndex, std::vector<WeightLinks4> &weightLinks)
{
  auto &currentLink = weightLinks[cellIndex];
  currentLink.nlinks = 0;
  currentLink.offset = 0;

  auto *indexWeights = currentLink.indexWeights;

  for (int i = 0; i < 4; ++i)
  {
    indexWeights[i].index = indices[i];
    for (int k = 0; k < 4; ++k) { indexWeights[i].weight[k] = weights[i][k]; }
  }

  if (!is_sorted_list(4, indices)) sort_indexWeights4(indexWeights);

  currentLink.nlinks = 4;
}

void
weight_links_to_remap_links(int doAlloc, size_t gridSize, std::vector<WeightLinks> &weightLinks, RemapVars &rv)
{
  size_t nlinks = 0;
  for (size_t i = 0; i < gridSize; ++i)
  {
    if (weightLinks[i].nlinks)
    {
      weightLinks[i].offset = nlinks;
      nlinks += weightLinks[i].nlinks;
    }
  }

  rv.maxLinks = nlinks;
  rv.numLinks = nlinks;

  if (!nlinks) return;

  rv.srcCellIndices.resize(nlinks);
  rv.tgtCellIndices.resize(nlinks);
  rv.weights.resize(nlinks * rv.numWeights);

  auto useLinksArrays = (Threading::ompNumMaxThreads > 1 && rv.numLinksPerValue == -1 && gridSize > cdoMinLoopSize);
  if (useLinksArrays) rv.linksOffset.resize(gridSize);
  if (useLinksArrays) rv.linksPerValue.resize(gridSize);

#ifdef _OPENMP
#pragma omp parallel for if (gridSize > cdoMinLoopSize) schedule(static) default(shared)
#endif
  for (size_t i = 0; i < gridSize; ++i)
  {
    auto &link = weightLinks[i];
    if (link.nlinks)
    {
      if (useLinksArrays) { rv.linksOffset[i] = link.offset; }
      if (useLinksArrays) { rv.linksPerValue[i] = link.nlinks; }
      auto *const indexWeights = link.indexWeights;
      for (size_t ilink = 0; ilink < link.nlinks; ++ilink)
      {
        rv.srcCellIndices[link.offset + ilink] = indexWeights[ilink].index;
        rv.tgtCellIndices[link.offset + ilink] = i;
        rv.weights[(link.offset + ilink) * rv.numWeights] = indexWeights[ilink].weight;
      }

      if (doAlloc) { delete[] link.indexWeights; }
    }
    else
    {
      if (useLinksArrays) { rv.linksOffset[i] = 0; }
      if (useLinksArrays) { rv.linksPerValue[i] = 0; }
    }
  }

  if (!doAlloc) { delete[] weightLinks[0].indexWeights; }
}

void
weight_links_4_to_remap_links(size_t gridSize, std::vector<WeightLinks4> &weightLinks, RemapVars &rv)
{
  size_t nlinks = 0;
  for (size_t i = 0; i < gridSize; ++i)
  {
    if (weightLinks[i].nlinks)
    {
      weightLinks[i].offset = nlinks;
      nlinks += weightLinks[i].nlinks;
    }
  }

  rv.maxLinks = nlinks;
  rv.numLinks = nlinks;

  if (!nlinks) return;

  rv.srcCellIndices.resize(nlinks);
  rv.tgtCellIndices.resize(nlinks);
  rv.weights.resize(4 * nlinks);

#ifdef _OPENMP
#pragma omp parallel for default(shared)
#endif
  for (size_t i = 0; i < gridSize; ++i)
  {
    auto &link = weightLinks[i];
    if (link.nlinks)
    {
      auto *const indexWeights = link.indexWeights;
      for (size_t ilink = 0; ilink < link.nlinks; ++ilink)
      {
        rv.srcCellIndices[link.offset + ilink] = indexWeights[ilink].index;
        rv.tgtCellIndices[link.offset + ilink] = i;
        for (size_t k = 0; k < 4; ++k) { rv.weights[(link.offset + ilink) * 4 + k] = indexWeights[ilink].weight[k]; }
      }
    }
  }

  delete[] weightLinks[0].indexWeights;
}

void
weight_links_alloc(size_t numNeighbors, size_t gridSize, std::vector<WeightLinks> &weightLinks)
{
  weightLinks[0].indexWeights = new IndexWeight[numNeighbors * gridSize];
  for (size_t i = 1; i < gridSize; ++i) { weightLinks[i].indexWeights = weightLinks[0].indexWeights + numNeighbors * i; }
}

void
weight_links_4_alloc(size_t gridSize, std::vector<WeightLinks4> &weightLinks)
{
  weightLinks[0].indexWeights = new IndexWeight4[4 * gridSize];
  for (size_t i = 1; i < gridSize; ++i) { weightLinks[i].indexWeights = weightLinks[0].indexWeights + 4 * i; }
}

void
set_numLinksPerValue(long &numLinksPerValue, long numWeights)
{
  if (numLinksPerValue == -1) { numLinksPerValue = numWeights; }
  else if (numLinksPerValue > 0 && numLinksPerValue != numWeights) { numLinksPerValue = 0; }
}

void
check_numLinksPerValue(std::vector<long> &numLinksPerValueList)
{
  if (numLinksPerValueList[0] > 0)
    for (int i = 1, n = numLinksPerValueList.size(); i < n; ++i)
      if (numLinksPerValueList[0] != numLinksPerValueList[i])
      {
        numLinksPerValueList[0] = 0;
        break;
      }
}
