/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  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
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  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.
 *
 *=========================================================================*/

#include <iostream>

#include "itkColorTable.h"
#include "itkTestingMacros.h"


template <typename TComponent>
int
ColorTableTestExpectedConditionChecker(typename itk::ColorTable<TComponent>::Pointer colors,
                                       unsigned int                                  colorId,
                                       itk::RGBPixel<TComponent>                     rgbPixel,
                                       const char *                                  name)
{
  if (colors->GetColor(colorId) != rgbPixel)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColor" << std::endl;
    std::cerr << "Expected: " << rgbPixel << ", but got: " << colors->GetColor(colorId) << std::endl;
    return EXIT_FAILURE;
  }
  if (colors->GetColorName(colorId) != name)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColorName" << std::endl;
    std::cerr << "Expected: " << name << ", but got: " << colors->GetColorName(colorId) << std::endl;
    return EXIT_FAILURE;
  }

  std::string rgb = "rgb";
  for (unsigned int i = 0; i < rgb.length(); ++i)
  {
    TComponent resultPixelComponentValue = colors->GetColorComponent(colorId, rgb.at(i));
    if (resultPixelComponentValue != rgbPixel[i])
    {
      std::cerr << "Test failed!" << std::endl;
      std::cerr << "Error in itk::ColorTable::GetColorComponent" << std::endl;
      std::cerr << "Expected: " << rgbPixel.GetNthComponent(i) << ", but got: " << resultPixelComponentValue
                << std::endl;
      return EXIT_FAILURE;
    }
  }

  return EXIT_SUCCESS;
}

template <typename TComponent>
int
ColorTableTestSpecialConditionChecker(typename itk::ColorTable<TComponent>::Pointer colors, unsigned int numberOfColors)
{
  using RGBPixelType = itk::RGBPixel<TComponent>;

  RGBPixelType zeroPixel;
  zeroPixel.Set(0, 0, 0);

  if (colors->GetNumberOfColors() != numberOfColors)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetNumberOfColors" << std::endl;
    std::cerr << "Expected: " << numberOfColors << ", but got: " << colors->GetNumberOfColors() << std::endl;
    return EXIT_FAILURE;
  }

  RGBPixelType pixel = colors->GetColor(numberOfColors);
  if (pixel != zeroPixel)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColor" << std::endl;
    std::cerr << "Expected: " << zeroPixel << ", but got: " << pixel << std::endl;
    return EXIT_FAILURE;
  }

  bool tf = colors->SetColor(numberOfColors, 0, 0, 0, "NoMatterTheName");
  if (tf != false)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::SetColor" << std::endl;
    std::cerr << "Expected: " << false << ", but got: " << tf << std::endl;
    return EXIT_FAILURE;
  }

  std::string name = colors->GetColorName(numberOfColors);
  if (!name.empty())
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColorName" << std::endl;
    std::cerr << "Expected: \"\" (empty string) "
              << ", but got: " << name << std::endl;
    return EXIT_FAILURE;
  }

  char       rgb = 'r';
  TComponent zeroComponent(0);
  TComponent colorComponent = colors->GetColorComponent(numberOfColors, rgb);
  if (colorComponent != zeroComponent)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColorComponent" << std::endl;
    std::cerr << "Expected: 0 "
              << ", but got: " << colorComponent << std::endl;
    return EXIT_FAILURE;
  }
  rgb = 'a';
  colorComponent = colors->GetColorComponent(numberOfColors - 1, rgb);
  if (colorComponent != zeroComponent)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::GetColorComponent" << std::endl;
    std::cerr << "Expected: 0 "
              << ", but got: " << colorComponent << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

template <typename TComponent>
int
ColorTableTestHelper(const char * name, unsigned int numberOfColors)
{
  int testStatus = EXIT_SUCCESS;

  using ColorTableType = itk::ColorTable<TComponent>;
  auto colors = ColorTableType::New();

  std::cout << "Testing for type: " << name << std::endl;

  std::cout << "Random Colors" << std::endl;
  colors->UseRandomColors(numberOfColors);
  colors->Print(std::cout);
  std::cout << std::endl;
  testStatus |= ColorTableTestSpecialConditionChecker<TComponent>(colors, numberOfColors);

  std::cout << "Heat Colors" << std::endl;
  colors->UseHeatColors(numberOfColors);
  colors->Print(std::cout);
  std::cout << std::endl;
  testStatus |= ColorTableTestSpecialConditionChecker<TComponent>(colors, numberOfColors);

  std::cout << "Gray Colors" << std::endl;
  colors->UseGrayColors(numberOfColors);
  colors->Print(std::cout);
  std::cout << std::endl;
  testStatus |= ColorTableTestSpecialConditionChecker<TComponent>(colors, numberOfColors);

  colors->UseDiscreteColors();
  std::cout << "Discrete Colors" << std::endl;
  colors->Print(std::cout);
  std::cout << std::endl;
  testStatus |= ColorTableTestSpecialConditionChecker<TComponent>(colors, 8);

  return testStatus;
}

int
itkColorTableTest(int argc, char * argv[])
{
  if (argc != 2)
  {
    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " numberOfColors" << std::endl;
    return EXIT_FAILURE;
  }

  int testStatus = EXIT_SUCCESS;

  // Exercise basic object methods
  // Done outside the helper function in the test because GCC is limited
  // when calling overloaded base class functions.
  using ColorTableType = itk::ColorTable<unsigned char>;
  auto colors = ColorTableType::New();

  ITK_EXERCISE_BASIC_OBJECT_METHODS(colors, ColorTable, Object);

  auto numberOfColors = static_cast<unsigned int>(std::stoi(argv[1]));

  testStatus |= ColorTableTestHelper<unsigned char>("unsigned char", numberOfColors);
  testStatus |= ColorTableTestHelper<char>("char", numberOfColors);
  testStatus |= ColorTableTestHelper<unsigned short>("unsigned short", numberOfColors);
  testStatus |= ColorTableTestHelper<short>("short", numberOfColors);
  testStatus |= ColorTableTestHelper<unsigned int>("unsigned int", numberOfColors);
  testStatus |= ColorTableTestHelper<int>("int", numberOfColors);
  testStatus |= ColorTableTestHelper<unsigned long>("unsigned long", numberOfColors);
  testStatus |= ColorTableTestHelper<long>("long", numberOfColors);
  testStatus |= ColorTableTestHelper<unsigned long long>("unsigned long long", numberOfColors);
  testStatus |= ColorTableTestHelper<long long>("long long", numberOfColors);
  testStatus |= ColorTableTestHelper<float>("float", numberOfColors);
  testStatus |= ColorTableTestHelper<double>("double", numberOfColors);

  // Find the closest color for a few colors
  unsigned int id;
  using RGBPixelType = itk::RGBPixel<unsigned char>;
  RGBPixelType pixel;
  colors->UseRandomColors(10000);
  pixel.Set(255, 0, 0);
  id = colors->GetClosestColorTableId(pixel[0], pixel[1], pixel[2]);
  std::cout << "Pixel : " << pixel << " is closest to id: " << id << " which has the color: " << colors->GetColor(id)
            << " and name " << colors->GetColorName(id) << std::endl;

  colors->UseDiscreteColors();
  pixel.Set(255, 0, 0);
  id = colors->GetClosestColorTableId(pixel[0], pixel[1], pixel[2]);
  std::cout << "Pixel : " << pixel << " is closest to id: " << id << " which has the color: " << colors->GetColor(id)
            << " and name " << colors->GetColorName(id) << std::endl;

  colors->UseGrayColors();
  pixel.Set(17, 17, 17);
  id = colors->GetClosestColorTableId(pixel[0], pixel[1], pixel[2]);
  std::cout << "Pixel : " << pixel << " is closest to id: " << id << " which has the color: " << colors->GetColor(id)
            << " and name " << colors->GetColorName(id) << std::endl;

  // Check for degenerate case
  colors->UseGrayColors(1);
  colors->Print(std::cout);

  // Exercise the SetColorMethod
  colors->UseRandomColors(4);
  const char * name = "Background";
  bool         tf = colors->SetColor(0, 0, 0, 0, name);
  if (tf != true)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::SetColor" << std::endl;
    std::cerr << "Expected: " << true << ", but got: " << tf << std::endl;
    return EXIT_FAILURE;
  }
  pixel.Set(0, 0, 0);
  unsigned int colorId = 0;
  testStatus |= ColorTableTestExpectedConditionChecker<unsigned char>(colors, colorId, pixel, name);

  name = "Red";
  tf = colors->SetColor(1, 255, 0, 0, name);
  if (tf != true)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::SetColor" << std::endl;
    std::cerr << "Expected: " << true << ", but got: " << tf << std::endl;
    return EXIT_FAILURE;
  }
  pixel.Set(255, 0, 0);
  colorId = 1;
  testStatus |= ColorTableTestExpectedConditionChecker<unsigned char>(colors, colorId, pixel, name);

  name = "Yellow";
  tf = colors->SetColor(2, 255, 255, 0, name);
  if (tf != true)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::SetColor" << std::endl;
    std::cerr << "Expected: " << true << ", but got: " << tf << std::endl;
    return EXIT_FAILURE;
  }
  pixel.Set(255, 255, 0);
  colorId = 2;
  testStatus |= ColorTableTestExpectedConditionChecker<unsigned char>(colors, colorId, pixel, name);

  pixel.Set(255, 255, 255);
  name = "White";
  tf = colors->SetColor(3, pixel, name);
  if (tf != true)
  {
    std::cerr << "Test failed!" << std::endl;
    std::cerr << "Error in itk::ColorTable::SetColor" << std::endl;
    std::cerr << "Expected: " << true << ", but got: " << tf << std::endl;
    return EXIT_FAILURE;
  }
  colorId = 3;
  testStatus |= ColorTableTestExpectedConditionChecker<unsigned char>(colors, colorId, pixel, name);

  colors->Print(std::cout);

  std::cout << "Test finished " << std::endl;
  return testStatus;
}
