#include <xavia/sensorFactory.h>
#include <xavia/iSensor.h>
#include <xavia/iCamera.h>
#include <xavia/iImage.h>
#include <xavia/error.h>
#include <xavia/logs.h>

#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <atomic>

namespace xsdk = xavia::sdk;

std::atomic<int> imageCount{0};

/* CAMERA IMAGE ACQUISITION EXAMPLE
 * 
 * This example demonstrates how to basic camera usage and image acquisition.
 * The steps are:
 * 1. Set up a callback function to handle incoming images.
 * 2. Build the sensor and get the camera.
 * 3. Start the camera.
 * 4. Process images as they arrive in the callback.
 * 5. Stop the camera when done.
 */

void imageCallback(std::unique_ptr<xsdk::IImage> image)
{
  // step 4. Process incoming image
  ++imageCount;
  const auto& metadata = image->GetMetaData();
  const auto bytesPerPixel = image->GetPixelData().size() / (metadata.width * metadata.height);
  
  std::wcout << L"Image #" << metadata.frameNumber << L": ";
  std::wcout << metadata.width << L"x" << metadata.height;
  auto pPx = image->GetPixelData().data() + ((metadata.width * 100 + 200) * bytesPerPixel); // access pixel value at (100, 200) as an example
  if(bytesPerPixel == 3)
  {
    // RGB8 format
    std::wcout << L" - pixel value at (100, 200) (rgb): " << pPx[0] << L"," << pPx[1] << L"," << pPx[2];
  }
  else if(bytesPerPixel == 6)
  {
    // RGB12 format
    auto pPx16 = reinterpret_cast<const std::uint16_t*>(pPx);
    std::wcout << L" - pixel value at (100, 200) (rgb12): " << pPx16[0] << L"," << pPx16[1] << L"," << pPx16[2];
  }
  std::wcout << L" - ImageSize: " << image->GetPixelData().size();
  std::wcout << L" - bytes per pixel: " << bytesPerPixel << std::endl;

  // keep this function short. If processing takes too long, frames will be dropped. 
  // If you need to do heavy processing, consider offloading the data to another thread or queue.
}

void errorCallback(const xsdk::Exception& ex)
{
  std::wcerr << L"Error: " << ex.what() << std::endl;
}

int main(int argc, char** argv)
{
  try
  {
    std::wcout << L"=== Camera Example ===" << std::endl;

    // logging is not mandatory, but we need to convert to std::wcout.
    xsdk::SetLogCallBack([](const xsdk::Severity& sev, const std::wstring& msg)
                       { std::wcout << xsdk::GetLogTypeString(sev) << L": " << msg << std::endl; });
    xsdk::SetLogSeverity(xsdk::Severity::XM_INFO);
    
    xsdk::SensorFactory factory;
    factory.SetErrorCallback(errorCallback); // optional
    
    // step 1. Set up image callbacks
    // setting this callback is required to activate the camera part of the sdk.
    factory.SetImageCallback(imageCallback);

    // step 2. Build the sensor and get the camera
    auto sensor = factory.Build();
    auto camera = sensor->GetCamera();

    // always check if the camera is available before trying to use it.
    // The camera may not be available due to various reasons.
    if (!camera)
    {
      throw std::runtime_error("Camera not available");
    }
    std::wcout << L"Connected to Camera ID: " << camera->GetCameraIdW() << std::endl;

    // step 3. Start the camera
    camera->Start();

    std::this_thread::sleep_for(std::chrono::seconds(5));

    // step 5. Stop the camera
    camera->Stop();

    std::wcout << L"Total images received: " << imageCount << std::endl;
  }
  catch (const std::exception& ex)
  {
    std::wcerr << L"Exception: " << ex.what() << std::endl;
    std::wcout << L"!!! Camera Example Completed with errors !!!" << std::endl;
    return EXIT_FAILURE;
  }

  std::wcout << L"=== Camera Example Completed ===" << std::endl;
  return EXIT_SUCCESS;
}
