#include "pylonwrapper.h"

PylonWrapper::PylonWrapper(const std::string &fullName):
  valid(false), fullName(fullName), camera(nullptr), withLayout(false) {
  qDebug() << "Constructor with name";
  Pylon::PylonInitialize();
}

PylonWrapper::PylonWrapper(const CameraLayout &layout): valid(false), withLayout(true), camera(nullptr) {
  qDebug() << "Constructor with layout";
  this->fullName = layout.devices[0];
  this->layout = layout;
  Pylon::PylonInitialize();
}

PylonWrapper::~PylonWrapper() {
  qDebug() << "wrapper destructor";
  if (camera != nullptr){
    if (camera->IsOpen()) {
        qDebug() << "Camera open, closing it!";
    camera->Close();
    }
    delete camera;
    camera = nullptr;
  }
  terminate();
  qDebug() << "Successfully deleted camera";
}

void PylonWrapper::terminate() {
  qDebug() << "Terminate";
  try {
    Pylon::PylonTerminate();
  } catch (const Pylon::GenericException &e) {
    std::cerr << e.GetDescription() << std::endl;
  }
}

bool PylonWrapper::isOpen() {
  return valid;
}

double PylonWrapper::maxFrameRate() {
  double max_rate = -1;
  if (valid) {
     GenApi::INodeMap& nodemap = camera->GetNodeMap();
     GenApi::INode* n = nodemap.GetNode( "AcquisitionFrameRate" );
     Pylon::CFloatParameter framerate( n );
     return framerate.GetMax();
  }
  return max_rate;
}

bool PylonWrapper::frameRate(uint new_framerate) {
  if (valid) {
      GenApi::INodeMap& nodemap = camera->GetNodeMap();
      GenApi::INode* n = nodemap.GetNode( "AcquisitionFrameRateEnable" );
      Pylon::CBooleanParameter enableframerate(n);
      enableframerate.SetValue(true);

      n = nodemap.GetNode( "AcquisitionFrameRate" );
      Pylon::CFloatParameter framerate( n );
      framerate.SetValue( new_framerate );
      return true;
    }
  return false;
}

double PylonWrapper::frameRate() {
  double rate = -1.;
  if (valid) {
      GenApi::INodeMap& nodemap = camera->GetNodeMap();
      GenApi::INode* n = nodemap.GetNode( "AcquisitionFrameRate" );
      Pylon::CFloatParameter framerate( n );
      rate = framerate.GetValue();
    }
  return rate;
}

double PylonWrapper::exposureTime() {
  double time = -1.;
  if (valid) {
      GenApi::INodeMap& nodemap = camera->GetNodeMap();
      GenApi::INode* n = nodemap.GetNode( "ExposureTime" );
      Pylon::CFloatParameter exposure_time( n );
      time = exposure_time.GetValue();
    }
  return time;
}

bool PylonWrapper::exposureTime(double exposure_time) {
    if (valid) {
        GenApi::INodeMap& nodemap = camera->GetNodeMap();
        double d = GenApi::CFloatPtr(nodemap.GetNode("ExposureTime"))->GetValue();
        GenApi::INode* n = nodemap.GetNode( "ExposureTime" );
        try {
            GenApi::CEnumerationPtr(nodemap.GetNode( "ExposureTimeMode" ))->FromString("Standard");
        } catch (...) {
            // setting the exposure mode fails with certain cameras.
        }
        Pylon::CFloatParameter exp_time( n );
        exp_time.SetValue( exposure_time );
        return true;
      }
    return false;
}

double PylonWrapper::gain() {
  double gain = -1.;
  if (valid) {
      GenApi::INodeMap& nodemap = camera->GetNodeMap();
      GenApi::INode* n = nodemap.GetNode( "Gain" );
      Pylon::CFloatParameter detector_gain( n );
      gain = detector_gain.GetValue();
    }
  return gain;
}

bool PylonWrapper::gain(double gain_db) {
    if (valid) {
        GenApi::INodeMap& nodemap = camera->GetNodeMap();
        GenApi::CFloatPtr(nodemap.GetNode("Gain"))->SetValue(gain_db);
        return true;
      }
    return false;
}

ImageSettings PylonWrapper::getImageSettings() {
  ImageSettings settings;
  if (valid) {
      Pylon::CEnumParameter pixelFormat( camera->GetNodeMap(), "PixelFormat" );
      Pylon::CPixelTypeMapper pixelTypeMapper( &pixelFormat );
      Pylon::EPixelType pixelType = pixelTypeMapper.GetPylonPixelTypeFromNodeValue( pixelFormat.GetIntValue() );
      Pylon::CIntegerParameter width( camera->GetNodeMap(), "Width");
      Pylon::CIntegerParameter height( camera->GetNodeMap(), "Height");
      settings.pixelType = pixelType;
      settings.width = (uint32_t)width.GetValue();
      settings.height = (uint32_t)height.GetValue();
      settings.orientation = Pylon::EImageOrientation::ImageOrientation_TopDown;
    }
  return settings;
}

bool PylonWrapper::grabFrame(MyImage &img) {
  Pylon::CGrabResultPtr frame;
  qDebug() << "grabFrame";
  if (valid) {
    qDebug() << "Setting width" << layout.rois[0].width;
    Pylon::CIntegerParameter(camera->GetNodeMap(), "Width").SetValue(layout.rois[0].width);
    qDebug() << "Setting height" << layout.rois[0].height;
    Pylon::CIntegerParameter(camera->GetNodeMap(), "Height").SetValue(layout.rois[0].height);
    qDebug() << "Setting xoffset" << layout.rois[0].x;
    Pylon::CIntegerParameter(camera->GetNodeMap(), "OffsetX").SetValue(layout.rois[0].x);
    qDebug() << "Setting yoffset" << layout.rois[0].y;
    Pylon::CIntegerParameter(camera->GetNodeMap(), "OffsetY").SetValue(layout.rois[0].y);

    camera->StartGrabbing();
    camera->RetrieveResult( 5000, frame, Pylon::TimeoutHandling_ThrowException);
    camera->StopGrabbing();
  }
  img.setFrame(frame);
  return frame.IsValid();
}

void PylonWrapper::resetCamera() {
  int64_t dfltWidth = 2048;
  int64_t dfltHeight = 1536;
  qDebug() << "resetting camera to default ROI (" << dfltWidth << ", " << dfltHeight << ")";
  try {
    GenApi::INodeMap& nodemap = camera->GetNodeMap();
    // std::cerr << "MaxWidth: " << Pylon::CIntegerParameter(nodemap, "WidthMax").GetValue() << std::endl;
    Pylon::CIntegerParameter(nodemap, "Width").SetValue(dfltWidth, false);
    // std::cerr << "MaxHeight: " << Pylon::CIntegerParameter(nodemap, "HeightMax").GetValue() << std::endl;
    Pylon::CIntegerParameter(nodemap, "Height").SetValue(dfltHeight, false);
    Pylon::CIntegerParameter(nodemap, "OffsetX").SetValue(0);
    Pylon::CIntegerParameter(nodemap, "OffsetY").SetValue(0);
  } catch (const Pylon::GenericException &e) {
    std::string message = e.GetDescription();
    std::cerr << "An exception occurred." << std::endl << e.GetDescription() << std::endl;
    valid = false;
  }
}

bool PylonWrapper::openCamera(std::string &message) { 
  qDebug() << "opening camera";
  try {
    camera = new Pylon::CInstantCamera();
    Pylon::String_t fname(fullName.c_str());
    Pylon::IPylonDevice *pDevice = Pylon::CTlFactory::GetInstance().CreateDevice(fname);
    camera->Attach(pDevice);
    camera->Open();
    valid = camera->IsOpen();
    message = "Successfully opened camera!";
  } catch (const Pylon::GenericException &e) {
    message = e.GetDescription();
    std::cerr << "An exception occurred." << std::endl << e.GetDescription() << std::endl;
    valid = false;
    return valid;
  }
  resetCamera();
  if (!withLayout) {
    qDebug() << "opening camera without layout, creating a new one";
    ImageSettings s = getImageSettings();
    layout = CameraLayout();
    layout.devices.push_back(fullName);
    ROI r = ROI();
    r.x = 0;
    r.y = 0;
    r.width = s.width;
    r.height = s.height;
    layout.rois.push_back(r);
    layout.layout = Layout::horizontal;
    layout.mode = CameraMode::single;
  }
  return valid;
}

void PylonWrapper::closeCamera() {
  qDebug() << "Close camera!";
  if (camera->IsOpen()) {
      try {
        camera->Close();
        valid = false;
        delete camera;
        camera = nullptr;
      } catch (const Pylon::GenericException &e) {
        qWarning() << "An exception occurred." << e.GetDescription();
      }
    }
}

Pylon::CInstantCamera *PylonWrapper::getCamera() {
  return camera;
}

QString PylonWrapper::userName() {
  GenApi::INodeMap& nodemap = camera->GetNodeMap();
  QString username = Pylon::CStringParameter(nodemap, "DeviceUserID").GetValue().c_str();
  return username;
}