/*=========================================================================
 *
 *  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 "itkKLMSegmentationRegion.h"

namespace itk
{
KLMSegmentationRegion::KLMSegmentationRegion() { m_MeanRegionIntensity = 0; }

KLMSegmentationRegion::~KLMSegmentationRegion() = default;

void
KLMSegmentationRegion::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "Mean region intensity   : " << m_MeanRegionIntensity << std::endl;
  os << indent << "Region border object" << std::endl;
} // end PrintSelf

void
KLMSegmentationRegion::SetRegionParameters(MeanRegionIntensityType meanRegionIntensity,
                                           double                  regionArea,
                                           RegionLabelType         label)
{
  // Set the area, mean, and label associated with the region
  this->SetRegionArea(regionArea);
  this->SetMeanRegionIntensity(meanRegionIntensity);
  this->SetRegionLabel(label);
} // end SetRegionParameters

void
KLMSegmentationRegion::CombineRegionParameters(const Self * region)
{
  // Reset the area and mean associated with the merged region

  const MeanRegionIntensityType region1Mean = this->GetMeanRegionIntensity();
  const MeanRegionIntensityType region2Mean = region->GetMeanRegionIntensity();

  const double region1Area = this->GetRegionArea();
  const double region2Area = region->GetRegionArea();

  const double            mergedRegionArea = region1Area + region2Area;
  MeanRegionIntensityType mergedRegionMean = region1Mean * region1Area + region2Mean * region2Area;

  if (mergedRegionArea <= 0)
  {
    itkExceptionStringMacro("Invalid region area");
  }
  mergedRegionMean /= mergedRegionArea;
  this->SetRegionArea(mergedRegionArea);
  this->SetMeanRegionIntensity(mergedRegionMean);
} // end CombineRegionParameters

double
KLMSegmentationRegion::EnergyFunctional(const Self * region)
{
  const MeanRegionIntensityType region1_2MeanDiff = this->m_MeanRegionIntensity - region->m_MeanRegionIntensity;

  // Assuming equal weights to all the channels
  // FIXME: For different channel weights modify this part of the code.
  const double cost = region1_2MeanDiff.squared_magnitude();

  const double region1Area = this->GetRegionArea();
  const double region2Area = region->GetRegionArea();

  const double scaleArea = (region1Area * region2Area) / (region1Area + region2Area);

  return scaleArea * cost;
}

void
KLMSegmentationRegion::DeleteRegionBorder(KLMSegmentationBorder * pBorderCandidate)
{
  if (pBorderCandidate == nullptr)
  {
    itkExceptionStringMacro("Null pointer to segmentation region border");
  }

  auto regionBorderVectorIt = m_RegionBorderVector.begin();
  auto regionBorderVectorItEnd = m_RegionBorderVector.end();

  // Erase the region border that matches the pBorderCandidate
  bool foundBorderCandidate = false;
  while (regionBorderVectorIt != regionBorderVectorItEnd)
  {
    if (*regionBorderVectorIt == pBorderCandidate)
    {
      m_RegionBorderVector.erase(regionBorderVectorIt);
      foundBorderCandidate = true;
      break;
    }
    ++regionBorderVectorIt;
  }

  if (!foundBorderCandidate)
  {
    itkExceptionStringMacro("Border candidate not in region borders list");
  }
} // end DeleteRegionBorder()

void
KLMSegmentationRegion::PushBackRegionBorder(KLMSegmentationBorder * pBorderCandidate)
{
  if (pBorderCandidate == nullptr)
  {
    itkExceptionStringMacro("Null pointer to segmentation region border");
  }
  m_RegionBorderVector.push_back(pBorderCandidate);
}

void
KLMSegmentationRegion::PushFrontRegionBorder(KLMSegmentationBorder * pBorderCandidate)
{
  if (pBorderCandidate == nullptr)
  {
    itkExceptionStringMacro("Null pointer to segmentation region border");
  }
  m_RegionBorderVector.insert(m_RegionBorderVector.begin(), pBorderCandidate);
}

void
KLMSegmentationRegion::InsertRegionBorder(KLMSegmentationBorder * pBorderCandidate)
{
  // Ensure that the border candidate is not a null pointer
  if (pBorderCandidate == nullptr)
  {
    itkExceptionStringMacro("Null pointer to segmentation region border");
  }

  // The m_RegionBorderVec is an ordered vector of pointers to the borders.
  // Ordering is based on regions labels.

  // The region border vector is empty, there is only one region border
  if (m_RegionBorderVector.empty())
  {
    m_RegionBorderVector.push_back(pBorderCandidate);
  }

  // There are many region borders
  else
  {
    // Iterator set to the first element of the region border element
    auto regionBorderVectorIt = m_RegionBorderVector.begin();
    auto regionBorderVectorItEnd = m_RegionBorderVector.end();

    bool insertAtEnd = true;
    while (regionBorderVectorIt != regionBorderVectorItEnd)
    {
      // The region border should be inserted.
      if ((pBorderCandidate->GetRegion1()->GetRegionLabel() <
           (*regionBorderVectorIt)->GetRegion1()->GetRegionLabel()) ||
          (pBorderCandidate->GetRegion1()->GetRegionLabel() ==
             (*regionBorderVectorIt)->GetRegion1()->GetRegionLabel() &&
           pBorderCandidate->GetRegion2()->GetRegionLabel() < (*regionBorderVectorIt)->GetRegion2()->GetRegionLabel()))
      {
        m_RegionBorderVector.insert(regionBorderVectorIt, pBorderCandidate);
        insertAtEnd = false;
        break;
      } // end of if

      ++regionBorderVectorIt;
    } // end of while

    // It should be inserted at the end
    if (insertAtEnd)
    {
      m_RegionBorderVector.push_back(pBorderCandidate);
    }
  } // end of else for the case with many region borders
} // end InsertRegionBorder

void
KLMSegmentationRegion::InsertRegionBorder(RegionBorderVectorIterator RegionBorderVectorIt,
                                          KLMSegmentationBorder *    pBorderCandidate)
{
  // Ensure that the border candidate is not a null pointer
  if (pBorderCandidate == nullptr)
  {
    itkExceptionStringMacro("Null pointer to segmentation region border");
  }

  // The m_RegionBorderVec is an ordered vector of pointers to the
  // borders. Insert a valid region border into the region border vector
  // assuming calling function has correctly identified the new
  // element position.
  m_RegionBorderVector.insert(RegionBorderVectorIt, pBorderCandidate);
} // end InsertRegionBorder

void
KLMSegmentationRegion::ResetRegionLabelAndUpdateBorders(Self * region)
{
  // Assign new equivalence label to the old region
  this->SetRegionLabel(region->GetRegionLabel());

  // Update the region borders

  auto oldRegionBordersIt = this->GetRegionBorderItBegin();
  auto endOfOldRegionBorders = this->GetRegionBorderItEnd();

  // Ensure that all borders associated with the old region
  // are in the correct order
  while (oldRegionBordersIt != endOfOldRegionBorders)
  {
    // Make sure the neighbors now point to the new region, and
    // reorder the region borders of the neighbors since they are
    // no longer in ascending order

    // Ensure that region 1 label is less than  region 2 label
    if ((*oldRegionBordersIt)->GetRegion1()->GetRegionLabel() == (*oldRegionBordersIt)->GetRegion2()->GetRegionLabel())
    {
      itkExceptionStringMacro("Invalid region border list");
    }

    // Correct order
    if ((*oldRegionBordersIt)->GetRegion1()->GetRegionLabel() > (*oldRegionBordersIt)->GetRegion2()->GetRegionLabel())
    {
      // Swap the regions pointed to by the border
      KLMSegmentationRegion * ptmpRegion = (*oldRegionBordersIt)->GetRegion1();
      (*oldRegionBordersIt)->SetRegion1((*oldRegionBordersIt)->GetRegion2());
      (*oldRegionBordersIt)->SetRegion2(ptmpRegion);
    } // end if (correct order)

    // Point to the newly merged region, and since the region labels
    // have been switched (if the region 1 label of a border is found to be
    // greater than the corresponding region 2 label), the list of borders for
    // the neighbor region has changed.

    // The border's region1 is the neighbor
    if ((*oldRegionBordersIt)->GetRegion2() == this)
    {
      // Make the border's region 2 point to the new region
      (*oldRegionBordersIt)->SetRegion2(region);

      // Reorder the region borders of the neighbor:
      // remove the region border, then re-insert it.
      (*oldRegionBordersIt)->GetRegion1()->DeleteRegionBorder(*oldRegionBordersIt);
      (*oldRegionBordersIt)->GetRegion1()->InsertRegionBorder(*oldRegionBordersIt);
    } // end if (the border's region1 is the neighbor)

    // The border's region2 is the neighbor
    else if ((*oldRegionBordersIt)->GetRegion1() == this)
    {
      // Make the border's region 1 point to the new region
      (*oldRegionBordersIt)->SetRegion1(region);

      // Reorder the region borders of the neighbor
      // remove the region border, then re-insert it
      (*oldRegionBordersIt)->GetRegion2()->DeleteRegionBorder(*oldRegionBordersIt);
      (*oldRegionBordersIt)->GetRegion2()->InsertRegionBorder(*oldRegionBordersIt);
    } // end else if (the border's region2 is the neighbor)

    else
    {
      itkExceptionStringMacro("Invalid region border list");
    } // end else

    // Go to the next region border pointed by the iterator
    ++oldRegionBordersIt;
  } // end while
} // end ResetRegionLabelAndUpdateBorders

void
KLMSegmentationRegion::SpliceRegionBorders(Self * region)
{
  // Do the actual union of the borders

  // Copy the container into a temp variable to avoid iterator confusion
  RegionBorderVectorType thisRegionBorder = m_RegionBorderVector;

  // clear output
  m_RegionBorderVector.clear();

  // Initialize the region iterators

  auto       thisRegionBordersIt = thisRegionBorder.begin();
  const auto endOfThisRegionBorders = thisRegionBorder.end();

  auto thatRegionBordersIt = region->GetRegionBorderConstItBegin();
  auto endOfThatRegionBorders = region->GetRegionBorderConstItEnd();

  // Merge the two sets of region borders into the newly merged region

  while ((thisRegionBordersIt != endOfThisRegionBorders) && (thatRegionBordersIt != endOfThatRegionBorders))
  {
    // Ensure that there are no common borders in the new region
    if (((*thisRegionBordersIt)->GetRegion1() == (*thisRegionBordersIt)->GetRegion2()) ||
        ((*thatRegionBordersIt)->GetRegion1() == (*thatRegionBordersIt)->GetRegion2()) ||
        (*thisRegionBordersIt == *thatRegionBordersIt))
    {
      itkExceptionStringMacro("Invalid region border list");
    }

    // The two borders point to the same regions: DUPLICATE case.
    // They must be merged into one border, and any reference to
    // to the duplicate border must be purged.

    if (((*thisRegionBordersIt)->GetRegion1() == (*thatRegionBordersIt)->GetRegion1()) &&
        ((*thisRegionBordersIt)->GetRegion2() == (*thatRegionBordersIt)->GetRegion2()))
    {
      // Add the lengths of the borders
      const double newLength = (*thatRegionBordersIt)->GetBorderLength() + (*thisRegionBordersIt)->GetBorderLength();

      (*thisRegionBordersIt)->SetBorderLength(newLength);

      m_RegionBorderVector.push_back(*thisRegionBordersIt);

      // The border's region1 is the neighbor
      if ((*thatRegionBordersIt)->GetRegion2() == this)
      {
        (*thatRegionBordersIt)->GetRegion1()->DeleteRegionBorder(*thatRegionBordersIt);
      } // end if (the border's region1 is the neighbor )

      // The border's region2 is the neighbor
      else if ((*thatRegionBordersIt)->GetRegion1() == this)
      {
        (*thatRegionBordersIt)->GetRegion2()->DeleteRegionBorder(*thatRegionBordersIt);
      } // end else if (the border's region2 is the neighbor )

      else
      {
        itkExceptionStringMacro("Invalid region border list");
      } // end else

      // Nullify the duplicate border so it can be identified and removed.
      (*thatRegionBordersIt)->SetRegion1(nullptr);
      (*thatRegionBordersIt)->SetRegion2(nullptr);
      (*thatRegionBordersIt)->SetLambda(-1.0);

      ++thisRegionBordersIt;
      ++thatRegionBordersIt;
    } // end if loop for case when two borders point to same region

    // This neighbor region label is less then that neighbor region label
    else if (((*thisRegionBordersIt)->GetRegion1()->GetRegionLabel() <
              (*thatRegionBordersIt)->GetRegion1()->GetRegionLabel()) ||

             (((*thisRegionBordersIt)->GetRegion1()->GetRegionLabel() ==
               (*thatRegionBordersIt)->GetRegion1()->GetRegionLabel()) &&

              ((*thisRegionBordersIt)->GetRegion2()->GetRegionLabel() <
               (*thatRegionBordersIt)->GetRegion2()->GetRegionLabel())))
    {
      m_RegionBorderVector.push_back(*thisRegionBordersIt);
      ++thisRegionBordersIt;
    } // end else if

    // That neighbor region label is less then this neighbor region label
    else if (((*thatRegionBordersIt)->GetRegion1()->GetRegionLabel() <
              (*thisRegionBordersIt)->GetRegion1()->GetRegionLabel()) ||

             (((*thatRegionBordersIt)->GetRegion1()->GetRegionLabel() ==
               (*thisRegionBordersIt)->GetRegion1()->GetRegionLabel()) &&

              ((*thatRegionBordersIt)->GetRegion2()->GetRegionLabel() <
               (*thisRegionBordersIt)->GetRegion2()->GetRegionLabel())))
    {
      m_RegionBorderVector.push_back(*thatRegionBordersIt);
      ++thatRegionBordersIt;
    } // end else if
    else
    {
      itkExceptionStringMacro("Invalid region border");
    } // end else
  } // end of while

  // If any borders remain in thisRegionBordersIt, put them in the back
  while (thisRegionBordersIt != endOfThisRegionBorders)
  {
    m_RegionBorderVector.push_back(*thisRegionBordersIt);
    ++thisRegionBordersIt;
  }

  // If any borders remain in thatRegionBorders, put them to the back
  while (thatRegionBordersIt != endOfThatRegionBorders)
  {
    m_RegionBorderVector.push_back(*thatRegionBordersIt);
    ++thatRegionBordersIt;
  }
} // end SpliceRegionBorders

void
KLMSegmentationRegion::UpdateRegionBorderLambda()
{
  // Check if the number of borders for this region is nullptr
  if (m_RegionBorderVector.empty())
  {
    itkExceptionStringMacro("The region border for computing Lambda is nullptr");
  }

  // Set up the iterator to loop through the region border vector
  auto regionBorderVectorIt = m_RegionBorderVector.begin();
  auto regionBorderVectorItEnd = m_RegionBorderVector.end();

  // Loop through the entire border list and update the Lambda values
  while (regionBorderVectorIt != regionBorderVectorItEnd)
  {
    (*regionBorderVectorIt)->EvaluateLambda();
    ++regionBorderVectorIt;
  } // End while loop
} // end UpdateRegionBorderLambda

void
KLMSegmentationRegion::DeleteAllRegionBorders()
{
  m_RegionBorderVector.clear();
} // end DeleteAllRegionBorders

KLMSegmentationRegion::RegionBorderVectorIterator
KLMSegmentationRegion::GetRegionBorderItBegin()
{
  return m_RegionBorderVector.begin();
} // end GetRegionBorderItBegin

KLMSegmentationRegion::RegionBorderVectorConstIterator
KLMSegmentationRegion::GetRegionBorderConstItBegin()
{
  return m_RegionBorderVector.begin();
} // end GetRegionBorderConstItBegin

KLMSegmentationRegion::RegionBorderVectorIterator
KLMSegmentationRegion::GetRegionBorderItEnd()
{
  return m_RegionBorderVector.end();
} // end GetRegionBorderItEnd

KLMSegmentationRegion::RegionBorderVectorConstIterator
KLMSegmentationRegion::GetRegionBorderConstItEnd()
{
  return m_RegionBorderVector.end();
} // end GetRegionBorderConstItEnd

KLMSegmentationRegion::RegionBorderVectorSizeType
KLMSegmentationRegion::GetRegionBorderSize() const
{
  return m_RegionBorderVector.size();
} // end GetRegionBorderSize

void
KLMSegmentationRegion::PrintRegionInfo()
{
  // If there are border pointers print the results
  std::cout << "------------------------------" << std::endl
            << "Location   : " << this << std::endl
            << "Label      : " << (this->GetRegionLabel()) << std::endl
            << "Area       : " << (this->GetRegionArea()) << std::endl
            << "Mean       : " << (this->GetMeanRegionIntensity()) << std::endl
            << "Num Borders: " << static_cast<int>(m_RegionBorderVector.size()) << std::endl
            << "++++++++++++++++++++++++++++++" << std::endl;

  // If there are border pointers print the results
  auto tempVectorIt = m_RegionBorderVector.begin();
  for (unsigned int k = 0; k < m_RegionBorderVector.size(); ++k)
  {
    const int region1label = (*tempVectorIt)->GetRegion1()->GetRegionLabel();
    const int region2label = (*tempVectorIt)->GetRegion2()->GetRegionLabel();

    std::cout << "Border Ptr :" << *tempVectorIt << "( " << region1label << " - " << region2label << " )"
              << " Lambda = " << (*tempVectorIt)->GetLambda() << std::endl;

    ++tempVectorIt;
  } // end for

  std::cout << "------------------------------" << std::endl;
} // end PrintRegionInfo
} // namespace itk
