// +-------------------------------- DISCLAIMER ---------------------------------+
// |                                                                             |
// | This program is provided to you free of charge "as such".                   |
// | You should not assume that this program is error-free or suitable for any   |
// | purpose whatsoever.                                                         |
// |                                                                             |
// | EURESYS does not give any representation, warranty or undertaking that this |
// | program is free of any defect or error or suitable for any purpose. EURESYS |
// | shall not be liable, in contract, in torts or otherwise, for any damages,   |
// | loss, costs, expenses or other claims for compensation, including those     |
// | asserted by third parties, arising out of or in connection with the use of  |
// | this program.                                                               |
// +-----------------------------------------------------------------------------+

#include "CameraHandler.h"

#include "ProcessingHandler.h"

#include "EasyDeepLearning.h"

#include <sstream>
#include <iomanip>

using namespace Euresys::Open_eVision;
using namespace Euresys::Open_eVision::EasyDeepLearning;


ProcessingHandler::ProcessingHandler(const std::string& deepLearningModel, CameraHandler* cameraHandler) : abort_(false), model_(deepLearningModel), cameraHandler_(cameraHandler)
{
}

ProcessingHandler::~ProcessingHandler()
{
  if (processingThread_.joinable())
  {
    abort_ = true;
    // waits for ProcessingHandler::run to end

    // notifies canProcessImage_ once in case a problem occured with the camera thread
    cameraHandler_->processImageMutex_.lock();
    cameraHandler_->canProcessImage_.notify_one();
    cameraHandler_->processImageMutex_.unlock();

    processingThread_.join();
  }
  std::cout << "Destroyed ProcessingHandler" << std::endl;
}

void ProcessingHandler::start()
{
  // run main loop in a separate thread
  processingThread_ = std::thread(&ProcessingHandler::run, this);
}

void ProcessingHandler::run()
{
  try
  {
    std::shared_ptr<EDeepLearningTool> tool;

    // Look model using the factory method of EDeepLearningTool
    tool = std::move(EDeepLearningTool::Create(model_));

    Easy::SetMaxNumberOfProcessingThreads(std::max(1, Easy::GetNumberOfAvailableProcessorCores() - 1));

    EDeepLearningExecutionSettings settings;
    for (const auto& device: settings.GetAvailableDevices())
    {
      if (device.GetDeviceType() == EasyDeepLearning::EDeepLearningDeviceType_GPU)
      {
        settings.SetDevice(device);
        break;
      }
    }

    tool->SetExecutionSettings(settings);

    EImageC24 src;

    while (!abort_)
    {
      {
        std::unique_lock<std::mutex> lock(cameraHandler_->processImageMutex_);
        cameraHandler_->canProcessImage_.wait(lock);
        src.InitializeFromUnalignedBuffer(cameraHandler_->width_, cameraHandler_->height_, cameraHandler_->data_, cameraHandler_->pitch_);
      }

      switch (tool->GetToolType())
      {
      case EDeepLearningToolType_EasyClassify:
        ProcessImage(src, std::dynamic_pointer_cast<EClassifier>(tool));
        break;
      case EDeepLearningToolType_EasySegmentSupervised:
        ProcessImage(src, std::dynamic_pointer_cast<ESupervisedSegmenter>(tool));
        break;
      case EDeepLearningToolType_EasySegmentUnsupervised:
        ProcessImage(src, std::dynamic_pointer_cast<EUnsupervisedSegmenter>(tool));
        break;
      case EDeepLearningToolType_EasyLocate:
      case EDeepLearningToolType_EasyLocateInterestPoint:
        ProcessImage(src, std::dynamic_pointer_cast<ELocatorBase>(tool));
        break;
      }
    }
  }
  catch (EException e)
  {
    std::cerr << "OeV exception caught: " << e.what() << std::endl;
    abort_ = true;
  }
}


void ProcessingHandler::ProcessImage(const EImageC24& img, std::shared_ptr<EClassifier> classifier)
{
  EClassificationResult r = classifier->Classify(img);

  std::stringstream resultDisplay;
  resultDisplay << "Label: " << r.GetBestLabel() << " (" << std::setprecision(4) << r.GetBestProbability() * 100.f << "%)";

  std::cout << resultDisplay.str() << std::endl;
}

void ProcessingHandler::ProcessImage(const EImageC24& img, std::shared_ptr<EUnsupervisedSegmenter> segmenter)
{
  EUnsupervisedSegmenterResult r = segmenter->Apply(img);

  std::string resultDisplay;
  if (r.IsGood())
  {
    std::cout << "Good" << std::endl;
  }
  else
  {
    std::cout << "Not good" << std::endl;
  }
}

void ProcessingHandler::ProcessImage(const EImageC24& img, std::shared_ptr<ESupervisedSegmenter> segmenter)
{
  ESupervisedSegmenterResult r = segmenter->Apply(img);

  std::cout << r.GetNumBlobs() + " blobs detected";
}

void ProcessingHandler::ProcessImage(const EImageC24& img, std::shared_ptr<ELocatorBase> locator)
{
  ELocatorResult r = locator->Apply(img);

  std::cout << r.GetNumDetectedObjects() + " objects detected";

  if (r.GetNumLabels() > 1 && r.GetNumDetectedObjects() > 0)
  {
    for (int i = 0; i < r.GetNumLabels(); i++)
    {
      std::cout << " - Label '" << r.GetLabel(i) << "' : " << r.GetNumDetectedObjects(i) << " objects detected." << std::endl;
    }
  }
}

