#include "takephoto.h"

// 模块名称
static const char MODULE_NAME[] = "TakePhoto";

// 保存照片
static int SavePhoto(HANDLE hCam, MV_FRAME_OUT *pFrame, EImgType imgType, const char *imgFile);

// 拍照回调
typedef struct
{
  HANDLE      hCam;
  EImgType    saveImgType;
  const char *saveImgFilename;
  bool        isExposureAuto; // 自动曝光:是/否
  struct timespec expTime0;   // 曝光开始的时间
  float       lastExpTime;    // 上次的曝光时长
  HANDLE      hESig;          // 任务结束的通知
  int         rCode;          // 任务结束返回值
} PthotoProcCtx;

static int PhotoProc(unsigned long wParam, unsigned long lParam) // 线程回调函数,执行一次拍照任务
{
  PthotoProcCtx *ctx = (PthotoProcCtx *)wParam; MV_FRAME_OUT frame = { 0 }; int ret;

  // 1, 触发一次拍照
getp:
  ret = MV_CC_SetCommandValue(ctx->hCam, "TriggerSoftware");

  // 2, 等待获取图像
  if(MV_OK == ret)
  {
    memset(&frame, 0, sizeof(MV_FRAME_OUT));
    ret = MV_CC_GetImageBuffer(ctx->hCam, &frame, 1000);
  }

  // 3, 导出图像文件
  if(MV_OK == ret)
  {
    sw_log_debug("[%s] +++GetOneFrame+++, Width[%d], Height[%d], FrameNum[%d], FrameLen[%d], PixelType[0x%08X]", MODULE_NAME, \
                  frame.stFrameInfo.nWidth, frame.stFrameInfo.nHeight, frame.stFrameInfo.nFrameNum, frame.stFrameInfo.nFrameLen, \
                  (unsigned int)frame.stFrameInfo.enPixelType);

    if(ctx->isExposureAuto)
    { // 自动曝光模式下, 等待曝光稳定或超时
      MVCC_FLOATVALUE stExposureTime = { 0 }; float curExpTime = 0;
      ret = MV_CC_GetExposureTime(ctx->hCam, &stExposureTime);
      if(MV_OK == ret) curExpTime = stExposureTime.fCurValue;

      struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
      long elapsed = (now.tv_sec - ctx->expTime0.tv_sec)*1000 + (now.tv_nsec-ctx->expTime0.tv_nsec)/(1000*1000);
      sw_log_debug("[%s] +++GetOneFrame+++, 等待曝光完成, ExposureTime = %.2fus, ElapsedTime = %ldms", MODULE_NAME, curExpTime, elapsed);

      if((curExpTime != ctx->lastExpTime) && elapsed < 3*60*1000)
      {
        MV_CC_FreeImageBuffer(ctx->hCam, &frame);
        ctx->lastExpTime = curExpTime;
        goto getp; // 继续取图像
      }
    }

    ret = SavePhoto(ctx->hCam, &frame, ctx->saveImgType, ctx->saveImgFilename);
    MV_CC_FreeImageBuffer(ctx->hCam, &frame);
  }

  // 4, 控制线程退出
  switch(ret)
  {
  case MV_E_NODATA: // 无数据时, 线程继续运行
    ret = 1; break;
  default: // 成功或发生其他错误，线程结束运行
    ctx->rCode = ret; sw_signal_give(ctx->hESig); ret = -1; break;
  }

  return ret;
}

// 单次执行相机拍照, 并保存到文件, 成功返回: 0值, 失败返回:非0值
// "imgType"         - 获取图像类型
// "saveImgFilename" - 保存的文件名
// "timeout"         - 等待超时时间, 单位：秒: < 0 表示无超时;
//                     无论超时怎么设置, 首次尝试拍照一定会执行
//                     , 但时间不确定; 拍照成功、超时或发生错误
//                     时会自动结束任务(相机持续无数据的时间).
// "pImgMark"        - 输出本次拍照图像的水印信息, 可以设置NULL
int TakePhoto(EImgType imgType, const char *saveImgFilename, int timeout, SImgMark *pImgMark)
{
  int fd; char runDir[MAX_PATH_CHARS] = { 0 }, lockFile[MAX_PATH_CHARS+32] = { 0 };
  int ret; SImgMark imgMark = { 0 }; HANDLE hCam = NULL; PthotoProcCtx ctx = { 0 };
  MV_CC_DEVICE_INFO_LIST devList = { 0 }; MV_CC_DEVICE_INFO *pDevInfo = NULL; MVCC_ENUMVALUE exposureMode =  { 0 };

  // 1, 占用锁定, 避免同时间拍照
  xGetSelfRunningInfo(runDir, NULL);
  if(runDir[strlen(runDir)-1] == '/') sprintf(lockFile, "%s%s", runDir, "status/");
  else sprintf(lockFile, "%s/%s", runDir, "status/");
  if(!sw_dir_exists(lockFile)) sw_dir_create(lockFile);
  strcat(lockFile, "takephoto.lock");

  fd = open(lockFile, O_CREAT | O_RDWR | __O_CLOEXEC, 0666);
  if(-1 == fd) { return -1; }

  if(-1 == flock(fd, LOCK_EX | LOCK_NB)) { close(fd); return -2; }

  // 2, 查找相机, 获取其设备信息
  ret = MV_CC_Initialize();
  if(MV_OK != ret)
  {
    sw_log_error("[%s] 相机SDK初始化失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  ret = MV_CC_EnumDevices(MV_USB_DEVICE, &devList);
  if(MV_OK != ret)
  {
    sw_log_error("[%s] USB口枚举相机失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }
  if(devList.nDeviceNum != 1)
  {
    ret = -3;
    sw_log_error("[%s] USB口相机数量错误, deviceNum=%u!!", MODULE_NAME, devList.nDeviceNum);
    goto end_p;
  }

  pDevInfo = devList.pDeviceInfo[0];
  if(!pDevInfo)
  {
    ret = -4;
    sw_log_error("[%s] unexpected internal error, unable to obtain detailed information about the industrial camera!!", MODULE_NAME);
    goto end_p;
  }
  strcpy(imgMark.camModelName, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chModelName);
  strcpy(imgMark.camSerialNum, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber);

  // 3, 打开相机, 设置其触发模式
  ret = MV_CC_CreateHandleWithoutLog(&hCam, pDevInfo);
  if(MV_OK == ret) ret = MV_CC_OpenDevice(hCam, MV_ACCESS_Exclusive, 0);
  if(MV_OK == ret) ret = MV_USB_SetTransferWays(hCam, 1); // 设置传输通道个数(RTU硬件资源有限, 需要降低通道数)
  if(MV_OK != ret)
  {
    sw_log_error("[%s] USB口相机打开失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  ret = MV_CC_SetEnumValue(hCam, "TriggerMode", 1); // 允许触发模式
  if(MV_OK == ret) ret = MV_CC_SetEnumValue(hCam, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE); // 设置软件触发
  if(MV_OK != ret)
  {
    sw_log_error("[%s] USB口相机设置失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  // 4, 开始拍照, 等待完成后输出
  ret =  MV_CC_GetExposureAutoMode(hCam, &exposureMode);
  if(MV_OK != ret)
  {
    sw_log_error("[%s] 获取-曝光模式失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }
  if(exposureMode.nCurValue != MV_EXPOSURE_AUTO_MODE_OFF) { ctx.isExposureAuto = true; clock_gettime(CLOCK_MONOTONIC, &ctx.expTime0); }
  else ctx.isExposureAuto = false;

  ret = MV_CC_StartGrabbing(hCam);
  if(MV_OK != ret)
  {
    sw_log_error("[%s] USB口相机取流失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  ctx.hCam = hCam;
  ctx.saveImgType = imgType;
  ctx.saveImgFilename = saveImgFilename;
  ctx.hESig = sw_signal_create();
  if(!ctx.hESig)
  {
    ret = -5;
    sw_log_error("[%s] SIG信号量创建失败!!", MODULE_NAME);
    goto end_p;
  }
  ctx.rCode = MV_OK;

  HANDLE hThrd = sw_thrd_create("PhotoProc", THREAD_DEFAULT_PRIORITY, THREAD_DEFAULT_STACK_SIZE, PhotoProc, (unsigned long)&ctx, 0);
  if(!hThrd)
  {
    ret = -6;
    sw_signal_destroy(ctx.hESig);
    sw_log_error("[%s] 拍照线程-创建失败!!", MODULE_NAME);
    goto end_p;
  }
  sw_thrd_resume(hThrd);

  ret = sw_signal_wait(ctx.hESig, timeout*1000); // 阻塞等待拍照任务结束或超时
  if(0 == ret) ret = ctx.rCode;
  else
  {
    ret = -7;
    sw_log_error("[%s] 拍照过程-等待超时!!", MODULE_NAME);
  }

  sw_thrd_destroy(hThrd, WAITTHRD_SAFEEXIT_TIMEOUT);
  sw_signal_destroy(ctx.hESig);

  // 5, 成功拍照, 输出相机的信息
  if(MV_OK == ret)
  {
    MVCC_FLOATVALUE fv = { 0 };
    MV_CC_GetFloatValue(hCam, "ExposureTime", &fv);
    imgMark.imgExposureTime = fv.fCurValue;
    if(pImgMark) memcpy(pImgMark, &imgMark, sizeof(SImgMark));
  }

  // 6, 任务结束, 释放相关的资源
end_p:
  if(hCam)
  {
    MV_CC_StopGrabbing(hCam);
    MV_CC_CloseDevice(hCam);
    MV_CC_DestroyHandle(hCam);
  }
  MV_CC_Finalize();

  flock(fd, LOCK_UN);
  close(fd);
  sw_file_delete(lockFile);

  return ret;
}

// 保存照片
static int SavePhoto(HANDLE hCam, MV_FRAME_OUT *pFrame, EImgType imgType, const char *imgFile)
{
  MV_SAVE_IMAGE_PARAM_EX3 saveParams = { 0 }; MVCC_INTVALUE_EX iv;
  char *filename1 = (char *)imgFile, *filename2 = NULL; int ret; char ext[5];

  if(!hCam || !pFrame || !filename1 || strlen(filename1) <= 0) return -8;

  switch(imgType)
  {
   case IMG_TYPE_BMP: // 保存为.bmp格式
    imgType = MV_Image_Bmp;
    strcpy(ext, ".bmp");
    break;
   case IMG_TYPE_JPG: // 保存为.jpg格式
    imgType = MV_Image_Jpeg;
    strcpy(ext, ".jpg");
    break;
   default: return -9;
  }

  ret = MV_CC_GetIntValueEx(hCam, "PayloadSize", &iv);
  if(MV_OK != ret) return ret;

  saveParams.pData        = pFrame->pBufAddr;
  saveParams.nDataLen     = pFrame->stFrameInfo.nFrameLen;
  saveParams.enPixelType  = pFrame->stFrameInfo.enPixelType;
  saveParams.nHeight      = pFrame->stFrameInfo.nHeight; 
  saveParams.nWidth       = pFrame->stFrameInfo.nWidth;

  saveParams.enImageType  = imgType;
  saveParams.nJpgQuality  = 90; // JPG编码质量(50-99], 其它格式无效, 影响JPG图像文件大小
  saveParams.iMethodValue = 1;

  if(imgType == IMG_TYPE_JPG) saveParams.nBufferSize  = (unsigned int)(iv.nCurValue * 1);
  else saveParams.nBufferSize  = (unsigned int)(iv.nCurValue * 4);
  saveParams.pImageBuffer = (unsigned char *)sw_heap_malloc(saveParams.nBufferSize);
  if(!saveParams.pImageBuffer) return -10;

  ret = MV_CC_SaveImageEx3(hCam, &saveParams);
  if(MV_OK != ret) goto end_p;

  if(!xstrcasestr(filename1, "right", ext))
  {
    filename2 = (char *)sw_heap_malloc(strlen(filename1) +sizeof(ext));
    if(filename2) sprintf(filename2, "%s%s", filename1, ext);
    else { ret = -11; goto end_p; }
    ret = sw_file_update(filename2, "wb", (const char *)saveParams.pImageBuffer, saveParams.nImageLen);
    sw_heap_free(filename2);
  }
  else
  {
    ret = sw_file_update(filename1, "wb", (const char *)saveParams.pImageBuffer, saveParams.nImageLen);
  }

  if(ret == saveParams.nImageLen) ret = MV_OK;
  else ret = -12;

end_p:
  sw_heap_free(saveParams.pImageBuffer);
  return ret;
}

// 获取系统当前USBFS内存大小(MB), 成功返回: >=0, 失败返回: -1值
int GetSysUsbfsMemCurrentSize()
{
  const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  char buf[MAX_LINE_CHARS]; int val = -1;
  FILE *fp = fopen(usbfsFile, "r");
  if(fp)
  {
    if(fgets(buf, sizeof(buf), fp)) sscanf(buf, "%d", &val);
    fclose(fp);
  }
  return val;
}

// 设置系统新的USBFS内存大小(MB), 成功返回: 0值, 失败返回: -1值
int SetSysUsbfsMemSize(int val)
{
  const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  char buf[MAX_LINE_CHARS]; sprintf(buf, "%d\n", val);
  if(val >= 0 && sw_file_update(usbfsFile, "w", buf, strlen(buf)) == strlen(buf)) return 0;
  else return -1;
}

// 获取当前连接在USB口上的相机数, 失败返回: <0值, 成功返回: >=0
int GetUsbCameraCount()
{
  MV_CC_DEVICE_INFO_LIST devList = { 0 };
  int ret;

  ret = MV_CC_Initialize();
  if(MV_OK != ret)
  {
    sw_log_error("[%s] 相机SDK初始化失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  ret = MV_CC_EnumDevices(MV_USB_DEVICE, &devList);
  if(MV_OK != ret)
  {
    sw_log_error("[%s] USB口枚举相机失败, errCode=0x%x!!", MODULE_NAME, ret);
    goto end_p;
  }

  ret = devList.nDeviceNum;

end_p:
  MV_CC_Finalize();

  return ret;
}
