/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2019 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes
#include <random>


/////////////////////// Qt includes
#include <QDebug>
#include <QColorDialog>


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "ColorSelector.hpp"


namespace msxps
{
namespace minexpert
{


// Fill-in the colors that can be used. Set them to unused to start with.
using color_usage_pair = std::pair<QColor, bool>;

std::vector<color_usage_pair> ColorSelector::m_usageOfColors = {
  color_usage_pair(QColor().fromRgb(0xff0000), false),
  color_usage_pair(QColor().fromRgb(0x0000ff), false),
  color_usage_pair(QColor().fromRgb(0x19ff00), false),
  color_usage_pair(QColor().fromRgb(0xff00ff), false),
  color_usage_pair(QColor().fromRgb(0xffa400), false),
  color_usage_pair(QColor().fromRgb(0x00ffd7), false),
  color_usage_pair(QColor().fromRgb(0x7a00ff), false),
  color_usage_pair(QColor().fromRgb(0xff009c), false),
  color_usage_pair(QColor().fromRgb(0x0072ff), false),
  color_usage_pair(QColor().fromRgb(0xf08080), false),
  color_usage_pair(QColor().fromRgb(0x4682b4), false),
  color_usage_pair(QColor().fromRgb(0x556b2f), false),
  color_usage_pair(QColor().fromRgb(0x7dbee7), false)};


ColorSelector::ColorSelector()
{
  // List all the available colors.

  // for(auto &&pair : ColorSelector::m_usageOfColors)
  // qDebug() << "Mapped color:" << pair.first.rgb();
}


ColorSelector::~ColorSelector()
{
}


void
ColorSelector::addColor(const QColor &color)
{

  if(!color.isValid())
    {
      // qDebug() << "The color to add is invalid.";
      return;
    }

  auto result = std::find_if(
    m_usageOfColors.begin(),
    m_usageOfColors.end(),
    [color](const color_usage_pair &item) { return item.first == color; });

  if(result == m_usageOfColors.end())
    {
      m_usageOfColors.push_back(color_usage_pair(color, false));
    }
  else
    {
      // qDebug() << "The color to be added is already present in the pool.
      // Doing "
      //"nothing.";
    }
}


QColor
ColorSelector::getColor(bool make_random)
{
  // qDebug();

  // We are asked to provide a color. Either we have one available, or we need
  // to provide the user with the opportunity to choose a new one or to
  // construct a new one randomly.

  // Search if there is still a color in the pool that is *not* used.
  bool is_used = false;
  auto result  = std::find_if(
    m_usageOfColors.begin(),
    m_usageOfColors.end(),
    [is_used](const color_usage_pair &item) { return item.second == is_used; });

  QColor color;

  if(result != m_usageOfColors.end())
    {
      // qDebug() << "The color from the map is fine. Set it to used.";

      // Something was returned. By necessity it is not used (find_if above).
      color = result->first;

      // qDebug() << "Was it used?" << isUsedColor(color);

      // Now set it as a used color.
      result->second = true;

      // qDebug() << "The returned color is:" << color.rgb();

      // qDebug() << "And now is it used?" << isUsedColor(color);

      return color;
    }
  else
    {
      // No color was available (pair value false), we need to let the user
      // choose one with the color chooser or make one random color.

      if(make_random)
        {
          while(1)
            {
              color = getRandomColor();

              // We need to ensure that the random color is not used already.

              if(!isUsedColor(color))
                {

                  // qDebug()
                  //<< "The color randomly generated is fine. Set it to used.";

                  // At this point we will return a new color that we set as
                  // used in the pool map.

                  m_usageOfColors.push_back(color_usage_pair(color, true));

                  return color;
                }
              else
                {

                  // Otherwise keep creating random colors.
                  // qDebug() << "The color randomly generated was used already
                  // !";
                }
            }
        }

      // At this point we know the caller wants to provide the user the
      // opportunity to select a color from the QColorDialog window.
      return chooseColor();
    }

  return QColor();
}


QColor
ColorSelector::chooseColor()
{
  // The user wants to actually choose a color directly from the Qt color
  // selector dialog.

  QColor color;

  while(1)
    {
      color = QColorDialog::getColor(
        Qt::white /* initial color of the returned color */,
        nullptr,
        "Please select a color for the plot");

      if(!color.isValid())
        {
          // The user cancels the operation.

          // qDebug() << "The color selection was cancelled by the user.";

          return color;
        }

      // Now test if the color that was selected exists already in the
      // pool of colors.

      if(isUsedColor(color))
        {

          // We'll continue forever :-). Until the user either select an
          // unused color or abandons the process.

          // qDebug() << "The color selected by the user is already used or "
          //"is invalid, please choose another one.";
        }
      else
        {

          // The color was not used, so we will return it but first insert
          // it in the map a a used color.

          // qDebug()
          //<< "The color selected by the user is not used, so use it!";

          m_usageOfColors.push_back(color_usage_pair(color, true));

          return color;
        }
    }

  return color;
}


QColor
ColorSelector::getRandomColor()
{
  // Generate random H,S,V triplets.
  // stackoverflow.com/questions/5008804/generating-random-integer-from-a-range

  std::random_device rd; // only used once to initialise (seed) engine

  std::mt19937 rng(
    rd()); // random-number engine used (Mersenne-Twister in this case)

  std::uniform_int_distribution<int> uni_h(0, 359); // guaranteed unbiased

  auto random_h = uni_h(rng);

  std::uniform_int_distribution<int> uni_s(125, 255);

  auto random_s = uni_s(rng);

  std::uniform_int_distribution<int> uni_v(125, 255);

  auto random_v = uni_s(rng);

  // Now create the color.

  return QColor::fromHsv(random_h, random_s, random_v);
}


QColor
ColorSelector::getColor(const QColor &color)
{
  // We are asked to provide a specific color. The only unacceptable situation
  // would be that the color is in the pool and that it is already used.

  auto result = std::find_if(
    m_usageOfColors.begin(),
    m_usageOfColors.end(),
    [color](const color_usage_pair &item) { return item.first == color; });

  if(result == m_usageOfColors.end())
    {
      // The color is not in the pool, easy, just add it in the used state.

      m_usageOfColors.push_back(color_usage_pair(color, true));

      return color;
    }
  else
    {
      // The color is in the pool, check if it is used or not.

      if(!result->second)
        {
          // The color is not used. Set it to the used state.

          result->second = true;

          return color;
        }
    }

  // The color is already used, return an invalid color so the caller knows
  // it.

  return QColor();
}


void
ColorSelector::releaseColor(const QColor &color)
{

  if(!color.isValid())
    return;

  // Confirm that the color was effectively used.

  auto result = std::find_if(
    m_usageOfColors.begin(),
    m_usageOfColors.end(),
    [color](const color_usage_pair &item) { return item.first == color; });

  if(result == m_usageOfColors.end())
    {
      // qDebug() << "The color to release does not appear to be in the pool."
      //<< "Setting it to the pool in unused status.";

      m_usageOfColors.push_back(color_usage_pair(color, false));

      return;
    }

  if(result->second == false)
    {
      // qDebug() << "The color to release does not appear to be in use.";

      return;
    }

  // The color was found and is used, just set it to unused status.

  result->second = false;
}


bool
ColorSelector::isUsedColor(const QColor &color)
{

  auto result = std::find_if(
    m_usageOfColors.begin(),
    m_usageOfColors.end(),
    [color](const color_usage_pair &item) { return item.first == color; });

  if(result != m_usageOfColors.end())
    {
      return result->second;
    }

  return false;
}


} // namespace minexpert

} // namespace msxps
