/**
   @file camera.cpp

   @brief Library for working with a camera

   @author Miroslav Pivovarsky
   Contact: miroslav.pivovarsky@gmail.com

   https://github.com/espressif/esp32-camera


   @bug: no know bug
*/
#include "camera.h"

Camera SystemCamera(&SystemConfig, &SystemLog, FLASH_GPIO_NUM);

/**
   @brief Init camera constructor
   @param Configuration* - pointer to Configuration class
   @param Logs* - pointer to Logs class
   @param uint8_t - flash pin
   @return none
*/
Camera::Camera(Configuration* i_conf, Logs* i_log, int8_t i_FlashPin) {
  config = i_conf;
  log = i_log;

  CameraFlashPin = i_FlashPin;
  StreamOnOff = false;
  frameBufferSemaphore = xSemaphoreCreateMutex();

  /* Allocate memory for the frame buffer */
  FrameBufferDuplicate = (camera_fb_t*)heap_caps_malloc(sizeof(camera_fb_t), MALLOC_CAP_SPIRAM);
  if (FrameBufferDuplicate != NULL) {
    memset(FrameBufferDuplicate, 0, sizeof(camera_fb_t)); 
    FrameBufferDuplicate->buf = NULL;
  }
  /* Allocate memory for the frame buffer */
  FrameBufferExif = (camera_fb_t*)heap_caps_malloc(sizeof(camera_fb_t), MALLOC_CAP_SPIRAM);
  if (FrameBufferExif != NULL) {
    memset(FrameBufferExif, 0, sizeof(camera_fb_t)); 
    FrameBufferExif->buf = NULL;
  }
  StreamSendingPhoto = false;

  PhotoExifData.header = NULL;
  PhotoExifData.len = 0;
  PhotoExifData.offset = 0;
  PhotoSending = false;
  CameraCaptureFailedCounter = 0;
}

/**
   @brief Init the camera module and set the camera configuration
   @param none
   @return none
*/
void Camera::Init() {
  log->AddEvent(LogLevel_Info, F("Init camera lib"));

#if (true == ENABLE_CAMERA_FLASH)
  log->AddEvent(LogLevel_Info, F("Init GPIO"));
#if (true == CAMERA_FLASH_PWM_CTRL)
  ledcAttach(FLASH_GPIO_NUM, FLASH_PWM_FREQ, FLASH_PWM_RESOLUTION);

#elif (true == CAMERA_FLASH_DIGITAL_CTRL)
  pinMode(FLASH_GPIO_NUM, OUTPUT);
  digitalWrite(FLASH_GPIO_NUM, LOW);
#endif
  SetFlashStatus(false);
#endif

  InitCameraModule();
  ApplyCameraCfg();
  GetCameraModel();
}

/**
   @brief Init the camera module
   @param none
   @return none
*/
void Camera::InitCameraModule() {
  log->AddEvent(LogLevel_Info, F("Init camera module"));
  /* Turn-off the 'brownout detector' */
#if (true == ENABLE_BROWN_OUT_DETECTION)
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
#endif
  esp_err_t err;

  CameraConfig.ledc_channel = LEDC_CHANNEL_0;
  CameraConfig.ledc_timer = LEDC_TIMER_0;
  CameraConfig.pin_d0 = Y2_GPIO_NUM;
  CameraConfig.pin_d1 = Y3_GPIO_NUM;
  CameraConfig.pin_d2 = Y4_GPIO_NUM;
  CameraConfig.pin_d3 = Y5_GPIO_NUM;
  CameraConfig.pin_d4 = Y6_GPIO_NUM;
  CameraConfig.pin_d5 = Y7_GPIO_NUM;
  CameraConfig.pin_d6 = Y8_GPIO_NUM;
  CameraConfig.pin_d7 = Y9_GPIO_NUM;
  CameraConfig.pin_xclk = XCLK_GPIO_NUM;
  CameraConfig.pin_pclk = PCLK_GPIO_NUM;
  CameraConfig.pin_vsync = VSYNC_GPIO_NUM;
  CameraConfig.pin_href = HREF_GPIO_NUM;
  CameraConfig.pin_sccb_sda = SIOD_GPIO_NUM;
  CameraConfig.pin_sccb_scl = SIOC_GPIO_NUM;
  CameraConfig.pin_pwdn = PWDN_GPIO_NUM;
  CameraConfig.pin_reset = RESET_GPIO_NUM;
  CameraConfig.xclk_freq_hz = 15000000;       // or 3000000; 16500000; 20000000
  CameraConfig.pixel_format = PIXFORMAT_JPEG; /* YUV422,GRAYSCALE,RGB565,JPEG */

  /* OV2640
    FRAMESIZE_QVGA (320 x 240)
    FRAMESIZE_CIF (352 x 288)
    FRAMESIZE_VGA (640 x 480)
    FRAMESIZE_SVGA (800 x 600)
    FRAMESIZE_XGA (1024 x 768)
    FRAMESIZE_SXGA (1280 x 1024)
    FRAMESIZE_UXGA (1600 x 1200)

    CAMERA_GRAB_WHEN_EMPTY - Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old
    CAMERA_GRAB_LATEST     - Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames
  */

  CameraConfig.frame_size = TFrameSize;             /* FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */
  CameraConfig.jpeg_quality = PhotoQuality;         /* 10-63 lower number means higher quality */
  CameraConfig.fb_count = 1;                        /* picture frame buffer alocation */
  CameraConfig.grab_mode = CAMERA_GRAB_LATEST;      /* CAMERA_GRAB_WHEN_EMPTY or CAMERA_GRAB_LATEST */
#if (true == ENABLE_PSRAM)
  CameraConfig.fb_location = CAMERA_FB_IN_PSRAM;    /* CAMERA_FB_IN_PSRAM or CAMERA_FB_IN_DRAM  */
#else
  CameraConfig.fb_location = CAMERA_FB_IN_DRAM;     /* CAMERA_FB_IN_PSRAM or CAMERA_FB_IN_DRAM  */
#endif
  
  if (CameraConfig.fb_location == CAMERA_FB_IN_DRAM) {
    log->AddEvent(LogLevel_Verbose, F("Camera frame buffer location: DRAM"));
  } else if (CameraConfig.fb_location == CAMERA_FB_IN_PSRAM) {
    log->AddEvent(LogLevel_Verbose, F("Camera frame buffer location: PSRAM"));
  } else {
    log->AddEvent(LogLevel_Verbose, F("Camera frame buffer location: Unknown"));
  }

  /* Camera init */
  err = esp_camera_init(&CameraConfig);
  if (err != ESP_OK) {
    log->AddEvent(LogLevel_Warning, F("Camera init failed. Error: "), String(err, HEX));
    log->AddEvent(LogLevel_Warning, F("Reset ESP32-cam!"));
    ESP.restart();
  } 
}

/**
   @brief Load camera CFG from EEPROM
   @param none
   @return none
*/
void Camera::LoadCameraCfgFromEeprom() {
  log->AddEvent(LogLevel_Info, F("Load camera CFG from EEPROM"));
  PhotoQuality = config->LoadPhotoQuality();
  FrameSize = config->LoadFrameSize();
  TFrameSize = TransformFrameSizeDataType(config->LoadFrameSize());
  brightness = config->LoadBrightness();
  contrast = config->LoadContrast();
  saturation = config->LoadSaturation();
  awb = config->LoadAwb();
  awb_gain = config->LoadAwbGain();
  wb_mode = config->LoadAwbMode();
  aec2 = config->LoadAec2();
  ae_level = config->LoadAeLevel();
  aec_value = config->LoadAecValue();
  gain_ctrl = config->LoadGainCtrl();
  agc_gain = config->LoadAgcGain();
  bpc = config->LoadBpc();
  wpc = config->LoadWpc();
  raw_gama = config->LoadRawGama();
  hmirror = config->LoadHmirror();
  vflip = config->LoadVflip();
  lensc = config->LoadLensCorrect();
  exposure_ctrl = config->LoadExposureCtrl();
  CameraFlashEnable = config->LoadCameraFlashEnable();
  CameraFlashTime = config->LoadCameraFlashTime();
  imageExifRotation = config->LoadCameraImageExifRotation();
}

/**
   @brief transform uint8_t from web interface to framesize_t
   @param uint8_t - int value from web
   @return framesize_t
*/
framesize_t Camera::TransformFrameSizeDataType(uint8_t i_data) {
  framesize_t ret = FRAMESIZE_QVGA;
  switch (i_data) {
    case 0:
      ret = FRAMESIZE_QVGA;
      break;
    case 1:
      ret = FRAMESIZE_CIF;
      break;
    case 2:
      ret = FRAMESIZE_VGA;
      break;
    case 3:
      ret = FRAMESIZE_SVGA;
      break;
    case 4:
      ret = FRAMESIZE_XGA;
      break;
    case 5:
      ret = FRAMESIZE_SXGA;
      break;
    case 6:
      ret = FRAMESIZE_UXGA;
      break;
    default:
      ret = FRAMESIZE_QVGA;
      log->AddEvent(LogLevel_Warning, F("Bad frame size. Set default value. "), String(i_data));
      break;
  }

  return ret;
}

/**
   @brief Function set photo sending status
   @param bool i_data - true = on, false = off
   @return none
*/
void Camera::SetPhotoSending(bool i_data) {
  PhotoSending = i_data;
}

/**
   @brief Function set flash status
   @param bool i_data - true = on, false = off
   @return none
*/
void Camera::SetFlashStatus(bool i_data) {
#if (true == ENABLE_CAMERA_FLASH)
#if (true == CAMERA_FLASH_PWM_CTRL)
  if (true == i_data) {
    ledcWrite(FLASH_GPIO_NUM, FLASH_ON_STATUS);
  } else if (false == i_data) {
    ledcWrite(FLASH_GPIO_NUM, FLASH_OFF_STATUS);
  }

#elif (true == CAMERA_FLASH_DIGITAL_CTRL)
  if (true == i_data) {
    digitalWrite(FLASH_GPIO_NUM, HIGH);
  } else if (false == i_data) {
    digitalWrite(FLASH_GPIO_NUM, LOW);
  }
#endif
#endif
}

/**
   @brief Function get flash status
   @param none
   @return bool - true = on, false = off
*/
bool Camera::GetFlashStatus() {
#if (true == ENABLE_CAMERA_FLASH)
#if (true == CAMERA_FLASH_PWM_CTRL)
  if (ledcRead(FLASH_GPIO_NUM) == FLASH_OFF_STATUS) {
    return false;
  } else if (ledcRead(FLASH_GPIO_NUM) == FLASH_ON_STATUS) {
    return true;
  }

#elif (true == CAMERA_FLASH_DIGITAL_CTRL)
  if (digitalRead(FLASH_GPIO_NUM) == LOW) {
    return false;
  } else if (digitalRead(FLASH_GPIO_NUM) == HIGH) {
    return true;
  }
#endif
#endif
  return false;
}

/**
   @brief Function set camer acfg
   @param none
   @return none
*/
void Camera::ApplyCameraCfg() {
  log->AddEvent(LogLevel_Info, F("Set camera CFG"));

  /* sensor configuration */
  sensor = esp_camera_sensor_get();
  sensor->set_brightness(sensor, brightness);         // -2 to 2
  sensor->set_contrast(sensor, contrast);             // -2 to 2
  sensor->set_saturation(sensor, saturation);         // -2 to 2
  sensor->set_special_effect(sensor, 0);              // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  sensor->set_whitebal(sensor, awb);                  // automatic white balancing 0 = disable , 1 = enable
  sensor->set_awb_gain(sensor, awb_gain);             // automatic white balancing gain 0 = disable , 1 = enable
  sensor->set_wb_mode(sensor, wb_mode);               // white balancing mode 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  sensor->set_exposure_ctrl(sensor, exposure_ctrl);   // exposition controll 0 = disable , 1 = enable
  sensor->set_aec2(sensor, aec2);                     // enable exposition controll 0 = disable , 1 = enable
  sensor->set_ae_level(sensor, ae_level);             // automatic exposition level -2 to 2
  sensor->set_aec_value(sensor, aec_value);           // expozition time - 0 to 1200
  sensor->set_gain_ctrl(sensor, gain_ctrl);           // automatic gain controll 0 = disable , 1 = enable
  sensor->set_agc_gain(sensor, agc_gain);             // automatic gain controll level 0 to 30
  sensor->set_gainceiling(sensor, (gainceiling_t)0);  // maximum gain 0 to 6
  sensor->set_bpc(sensor, bpc);                       // bad pixel correction 0 = disable , 1 = enable
  sensor->set_wpc(sensor, wpc);                       // white pixel correction 0 = disable , 1 = enable
  sensor->set_raw_gma(sensor, raw_gama);              // raw gama correction 0 = disable , 1 = enable
  sensor->set_lenc(sensor, lensc);                    // lens correction 0 = disable , 1 = enable
  sensor->set_hmirror(sensor, hmirror);               // horizontal mirror 0 = disable , 1 = enable
  sensor->set_vflip(sensor, vflip);                   // vertical flip 0 = disable , 1 = enable
  sensor->set_dcw(sensor, 1);                         // 0 = disable , 1 = enable
  sensor->set_colorbar(sensor, 0);                    // external collor lines, 0 = disable , 1 = enable
}

/**
   @brief Function for reinit camera module
   @param none
   @return none
*/
void Camera::ReinitCameraModule() {
  esp_err_t err = esp_camera_deinit();
  if (err != ESP_OK) {
    log->AddEvent(LogLevel_Warning, F("Camera error deinit camera module. Error: "), String(err, HEX));
  }
  delay(100);
  InitCameraModule();
  ApplyCameraCfg();
}

/**
   @brief Function for get camera model and type
   @param void
   @return none
*/
void Camera::GetCameraModel() {
  log->AddEvent(LogLevel_Info, F("Get camera model and type"));
  if (sensor == NULL) {
    log->AddEvent(LogLevel_Error, F("Camera sensor is NULL"));
    return;
  } 
  
  camera_sensor_info_t *info = esp_camera_sensor_get_info(&sensor->id);
  if (info == NULL) {
    log->AddEvent(LogLevel_Error, F("Camera sensor info is NULL"));
    return;
  }

  CameraType = (camera_pid_t) sensor->id.PID;
  CameraName = info->name;
  log->AddEvent(LogLevel_Info, F("Camera type: "), String(CameraType));
  log->AddEvent(LogLevel_Info, F("Camera name: "), String(CameraName));
  log->AddEvent(LogLevel_Info, F("Camera model: "), String(info->model));
  log->AddEvent(LogLevel_Info, F("Camera PID: "), String(info->pid));
  log->AddEvent(LogLevel_Info, F("Camera MAX framesize: "), String(info->max_size));
  log->AddEvent(LogLevel_Info, F("Camera support jpeg: "), String(info->support_jpeg));
}

/**
   @brief Capture Photo and Save it to string array
   @param none
   @return none
*/
void Camera::CapturePhoto() {

  /* Check if photo is already sending */
  if (true == PhotoSending) {
    log->AddEvent(LogLevel_Info, F("Sending photo"));
    return;
  }

  /* Check if stream is on */
  if (false == StreamOnOff) {
    if (!xSemaphoreTake(frameBufferSemaphore, portMAX_DELAY)) {
      log->AddEvent(LogLevel_Error, F("Failed to take frame buffer semaphore"));
      return;
    }

    CameraCaptureSuccess = false;
    /* check flash, and enable FLASH LED */
    if (true == CameraFlashEnable) {
      SetFlashStatus(true);
      delay(CameraFlashTime);
    }

    if (FrameBuffer) {
      esp_camera_fb_return(FrameBuffer);
    }

    /* Capturing a training photo. Without this sequence, the camera will not obtain the current photo but photo from the previous cycle. */
    FrameBuffer = esp_camera_fb_get();
    if (FrameBuffer) {
      esp_camera_fb_return(FrameBuffer);
      log->AddEvent(LogLevel_Verbose, F("Camera capture training photo"));
    } else {
      esp_camera_fb_return(FrameBuffer);
      log->AddEvent(LogLevel_Error, F("Camera capture failed training photo"));
      //ReinitCameraModule();
    }

    int attempts = 0;
    const int maxAttempts = 5;
    PhotoExifData.header = NULL;
    do {
      log->AddEvent(LogLevel_Info, F("Taking photo..."));

      FrameBuffer = esp_camera_fb_get();
      if (!FrameBuffer) {
        CameraCaptureFailedCounter++;
        log->AddEvent(LogLevel_Error, F("Camera capture failed! photo. Attempt: "), String(CameraCaptureFailedCounter));
        xSemaphoreGive(frameBufferSemaphore);  // Release semaphore before returning
        return;
      }

      char buf[150] = { '\0' };
      uint8_t ControlFlag = (uint8_t)FrameBuffer->buf[15];
      sprintf(buf, "The picture has been saved. Size: %d bytes, Photo resolution: %zu x %zu", FrameBuffer->len, FrameBuffer->width, FrameBuffer->height);
      log->AddEvent(LogLevel_Info, buf);

      if (ControlFlag != 0x00) {
        log->AddEvent(LogLevel_Error, "Camera capture failed! flag: " + String(ControlFlag, HEX));
        FrameBuffer->len = 0;

      } else {
        log->AddEvent(LogLevel_Info, F("Photo OK! "), String(ControlFlag, HEX));
        CameraCaptureFailedCounter = 0;

        /* generate exif header */
        update_exif_from_cfg(imageExifRotation);
        get_exif_header(FrameBuffer, &PhotoExifData.header, &PhotoExifData.len);
        PhotoExifData.offset = get_jpeg_data_offset(FrameBuffer);
        CameraCaptureSuccess = true;
        
        if (PhotoExifData.header != NULL) {
          log->AddEvent(LogLevel_Info, F("Exif header OK! Len: "), String(PhotoExifData.len));
        } else {
          log->AddEvent(LogLevel_Error, F("Exif header failed! "), String(PhotoExifData.len));
        }
      }

      attempts++;
      if (attempts >= maxAttempts) {
        log->AddEvent(LogLevel_Error, F("Failed to capture a valid photo after max attempts"));
        break;
      }
    } while (!(FrameBuffer->len > 100));

    /* Disable flash */
    if (true == CameraFlashEnable) {
      SetFlashStatus(false);
    }
    xSemaphoreGive(frameBufferSemaphore);

  } else {
    /* Stream is on, set flag for sending photo */
    if (xSemaphoreTake(frameBufferSemaphore, portMAX_DELAY)) {
      StreamSendingPhoto = true;
      xSemaphoreGive(frameBufferSemaphore);
    }
  }

  /* Reinit camera module if photo capture failed */
  if (CameraCaptureFailedCounter > CAMERA_MAX_FAIL_CAPTURE) { 
    log->AddEvent(LogLevel_Error, F("Camera capture failed! photo max attempts"));
    CameraCaptureFailedCounter = 0;
    ReinitCameraModule();
  }
}

/**
   @brief Capture Stream
   @param camera_fb_t * - pointer to camera_fb_t
   @return none
*/
void Camera::CaptureStream(camera_fb_t* i_buf) {
  if (xSemaphoreTake(frameBufferSemaphore, portMAX_DELAY)) {
    do {
      /* capture final photo */
      FrameBuffer = esp_camera_fb_get();
      if (!FrameBuffer) {
        log->AddEvent(LogLevel_Error, F("Camera capture failed! stream"));
        i_buf = NULL;
        return;
      }

      /* check if photo is correctly saved */
    } while (!(FrameBuffer->len > 100));

    /* check if the photo is rotated */
    bool ExifStatus = false;

#if (true == CAMERA_EXIF_ROTATION_STREAM)
    if (1 != imageExifRotation) { /* 1 = image rotation 0 degree */
      /* generate exif header */
      update_exif_from_cfg(imageExifRotation);
      get_exif_header(FrameBuffer, &PhotoExifData.header, &PhotoExifData.len);
      PhotoExifData.offset = get_jpeg_data_offset(FrameBuffer);
      if (PhotoExifData.header != NULL) {
        /* memory allocation release */
        if (FrameBufferExif != NULL) {
          if (FrameBufferExif->buf != NULL) {
            free(FrameBufferExif->buf);
            FrameBufferExif->buf = NULL; /* Set to NULL after freeing */
          }

          free(FrameBufferExif);
          FrameBufferExif = NULL; /* Set to NULL after freeing */
        }

        /* Allocate memory for the duplicate frame structure */
        FrameBufferExif = (camera_fb_t*)heap_caps_malloc(sizeof(camera_fb_t), MALLOC_CAP_SPIRAM);

        /* Calculate the total size of the buffer */
        size_t totalSize = PhotoExifData.len + FrameBuffer->len - PhotoExifData.offset;

        /* Allocate memory for the image data */
        FrameBufferExif->buf = (uint8_t*)heap_caps_malloc(totalSize, MALLOC_CAP_SPIRAM);
        if (FrameBufferExif->buf == NULL) {
          log->AddEvent(LogLevel_Error, F("Failed to allocate memory for EXIF buffer"));
          return;
        }

        /* copy the EXIF data to the buffer */
        memcpy(FrameBufferExif->buf, PhotoExifData.header, PhotoExifData.len);

        /* copy the image data to the buffer */
        memcpy(FrameBufferExif->buf + PhotoExifData.len, FrameBuffer->buf + PhotoExifData.offset, FrameBuffer->len - PhotoExifData.offset);

        /* Set the length of the buffer */
        FrameBufferExif->len = totalSize;

        *i_buf = *FrameBufferExif;
        ExifStatus = true;
        
      } else {
        /* copy the frame buffer to the buffer */
        *i_buf = *FrameBuffer;
      }

    } else {
      /* copy the frame buffer to the buffer */
      *i_buf = *FrameBuffer;
    }
#else
    *i_buf = *FrameBuffer;
#endif

    /* copy the frame buffer to the duplicate frame buffer. For sending photo to Prusa Connect */
    if (false == StreamSendingPhoto) {
      /* memory allocation release */
      if (FrameBufferDuplicate != NULL) {
        if (FrameBufferDuplicate->buf != NULL) {
          free(FrameBufferDuplicate->buf);
          FrameBufferDuplicate->buf = NULL; /* Set to NULL after freeing */
        }

        free(FrameBufferDuplicate);
        FrameBufferDuplicate = NULL; /* Set to NULL after freeing */
      }
      
      /* Allocate memory for the duplicate frame structure */
      FrameBufferDuplicate = (camera_fb_t*)heap_caps_malloc(sizeof(camera_fb_t), MALLOC_CAP_SPIRAM);

      /* Copy the metadata */
      if (true == ExifStatus) {
        memcpy(FrameBufferDuplicate, FrameBufferExif, sizeof(camera_fb_t));
      } else {
        memcpy(FrameBufferDuplicate, FrameBuffer, sizeof(camera_fb_t));
      }

      /* Allocate memory for the image data */
      if (true == ExifStatus) {
        FrameBufferDuplicate->buf = (uint8_t*)heap_caps_malloc(FrameBufferExif->len, MALLOC_CAP_SPIRAM);
      } else {
        FrameBufferDuplicate->buf = (uint8_t*)heap_caps_malloc(FrameBuffer->len, MALLOC_CAP_SPIRAM);
      }

      /* Check if memory allocation was successful */
      if (!FrameBufferDuplicate->buf) {
        /* Handle error */
        free(FrameBufferDuplicate);
        Serial.println("Failed to allocate memory for the duplicate frame buffer");
      } else {
        /* Copy the image data */
        if (true == ExifStatus) {
          memcpy(FrameBufferDuplicate->buf, FrameBufferExif->buf, FrameBufferExif->len);
          
        } else {
          memcpy(FrameBufferDuplicate->buf, FrameBuffer->buf, FrameBuffer->len);
        }
      }
    }

    xSemaphoreGive(frameBufferSemaphore);
  }
}

/**
   @brief Capture Return Frame Buffer
   @param none
   @return none
*/
void Camera::CaptureReturnFrameBuffer() {
  esp_camera_fb_return(FrameBuffer);
}

/**
   @brief Set Stream Status
   @param bool - true = on, false = off
   @return none
*/
void Camera::SetStreamStatus(bool i_status) {
  StreamOnOff = i_status;
  if (FrameBuffer) {
    esp_camera_fb_return(FrameBuffer);
  }
  log->AddEvent(LogLevel_Info, F("Camera video stream: "), String(StreamOnOff));
}

/**
   @brief Get Stream Status
   @param none
   @return bool - true = on, false = off
*/
bool Camera::GetStreamStatus() {
  return StreamOnOff;
}

bool Camera::GetCameraCaptureSuccess() {
  return CameraCaptureSuccess;
}

/**
   @brief Set Frame Size
   @param uint16_t - frame size
   @return none
*/
void Camera::StreamSetFrameSize(uint16_t i_data) {
  StreamAverageSize = (StreamAverageSize + i_data) / 2;
}

/**
   @brief Set average Frame Fps
   @param float - frame fps
   @return none
*/
void Camera::StreamSetFrameFps(float i_data) {
  StreamAverageFps = (StreamAverageFps + i_data) / 2.0;
}

/**
   @brief Get average stream Frame Size
   @param none
   @return uint16_t - frame size
*/
uint16_t Camera::StreamGetFrameAverageSize() {
  return StreamAverageSize;
}

/**
   @brief Get average stream Frame Fps
   @param none
   @return float - frame fps
*/
float Camera::StreamGetFrameAverageFps() {
  return StreamAverageFps;
}

/**
   @brief Clear Frame Data
   @param none
   @return none
*/
void Camera::StreamClearFrameData() {
  StreamAverageFps = 0.0;
  StreamAverageSize = 0;
}

/**
   @brief Set Sending Photo for Stream
   @param bool - sending photo
   @return none
*/
void Camera::StreamSetSendingPhoto(bool i_data) {
  StreamSendingPhoto = i_data;
}

/**
   @brief Get Photo
   @param none
   @return String - photo
*/
String Camera::GetPhoto() {
  Photo = "";
  for (size_t i = 0; i < FrameBuffer->len; i++) {
    Photo += (char)FrameBuffer->buf[i];
  }
  return Photo;
}

/**
   @brief Get Photo Frame Buffer
   @param none
   @return camera_fb_t* - photo frame buffer
*/
camera_fb_t* Camera::GetPhotoFb() {
  return FrameBuffer;
}

/**
   @brief Get Photo Frame Buffer Duplicate
   @param none
   @return camera_fb_t* - photo frame buffer duplicate
*/
camera_fb_t* Camera::GetPhotoFbDuplicate() {
  return FrameBufferDuplicate;
}

/**
   @brief Get Photo Exif Data
   @param none
   @return PhotoExifData_t* - photo exif data
*/
PhotoExifData_t* Camera::GetPhotoExifData() {
  return &PhotoExifData;
}

/**
   @brief Copy Photo
   @param camera_fb_t* - pointer to camera_fb_t
   @return none
*/
void Camera::CopyPhoto(camera_fb_t* i_data) {
  *i_data = *FrameBuffer;
}

/**
   @brief Copy Photo
   @param String* - pointer to string
   @return none
*/
void Camera::CopyPhoto(String* i_data) {
  Photo = "";
  for (size_t i = 0; i < FrameBuffer->len; i++) {
    Photo += (char)FrameBuffer->buf[i];
  }
  *i_data = Photo;
}

/**
 * @brief Copy photo from frame buffer to string array with range
 * 
 * @param i_data - pointer to string 
 * @param i_from - start index
 * @param i_to - end index
 */
void Camera::CopyPhoto(String* i_data, int i_from, int i_to) {
  *i_data = "";

  for (int i = i_from; i < i_to; i++) {
    *i_data += (char)FrameBuffer->buf[i];
  }
}

/**
 * @brief Copy photo from frame buffer to char array with range
 * 
 * @param i_data - pointer to char array
 * @param i_from - start index
 * @param i_to - end index
 */
void Camera::CopyPhoto(char* i_data, int i_from, int i_to) {
  int length = i_to - i_from;
  memcpy(i_data, FrameBuffer->buf + i_from, length);
  i_data[length + 1] = '\0';
}

/**
 * @brief Get Photo Size
 * 
 * @return int - photo size
 */
int Camera::GetPhotoSize() {
  log->AddEvent(LogLevel_Verbose, F("Photo size: "), String(FrameBuffer->len));
  return (int)FrameBuffer->len;
}

/**
   @brief Set Photo Quality
   @param uint8_t - photo quality
   @return none
*/
void Camera::SetPhotoQuality(uint8_t i_data) {
  config->SavePhotoQuality(i_data);
  PhotoQuality = i_data;
  ReinitCameraModule();
}

/**
   @brief Set Frame Size
   @param uint8_t - frame size
   @return none
*/
void Camera::SetFrameSize(uint8_t i_data) {
  config->SaveFrameSize(i_data);
  FrameSize = i_data;
  TFrameSize = TransformFrameSizeDataType(i_data);
  ReinitCameraModule();
}

/**
   @brief Set Brightness
   @param int8_t - brightness
   @return none
*/
void Camera::SetBrightness(int8_t i_data) {
  config->SaveBrightness(i_data);
  brightness = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Contrast
   @param int8_t - contrast
   @return none
*/
void Camera::SetContrast(int8_t i_data) {
  config->SaveContrast(i_data);
  contrast = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Saturation
   @param int8_t - saturation
   @return none
*/
void Camera::SetSaturation(int8_t i_data) {
  config->SaveSaturation(i_data);
  saturation = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic white balance
   @param bool - automatic white balance
   @return none
*/
void Camera::SetAwb(bool i_data) {
  config->SaveAwb(i_data);
  awb = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic white balance gain
   @param bool - automatic white balance gain
   @return none
*/
void Camera::SetAwbGain(bool i_data) {
  config->SaveAwbGain(i_data);
  awb_gain = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic white balance mode
   @param uint8_t - automatic white balance mode
   @return none
*/
void Camera::SetAwbMode(uint8_t i_data) {
  config->SaveAwbMode(i_data);
  wb_mode = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic exposure control 2
   @param bool - automatic exposure control 2
   @return none
*/
void Camera::SetAec2(bool i_data) {
  config->SaveAec2(i_data);
  aec2 = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic exposure level
   @param int8_t - automatic exposure level
   @return none
*/
void Camera::SetAeLevel(int8_t i_data) {
  config->SaveAeLevel(i_data);
  ae_level = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic exposition control value
   @param uint16_t - automatic exposure control value
   @return none
*/
void Camera::SetAecValue(uint16_t i_data) {
  config->SaveAecValue(i_data);
  aec_value = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Gain Ctrl
   @param bool - gain ctrl
   @return none
*/
void Camera::SetGainCtrl(bool i_data) {
  config->SaveGainCtrl(i_data);
  gain_ctrl = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set automatic gain control gain
   @param uint8_t - automatic gain control gain
   @return none
*/
void Camera::SetAgcGain(uint8_t i_data) {
  config->SaveAgcGain(i_data);
  agc_gain = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set bad pixel correction
   @param bool - bad pixel correction
   @return none
*/
void Camera::SetBpc(bool i_data) {
  config->SaveBpc(i_data);
  bpc = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set white pixel correction
   @param bool - white pixel correction
   @return none
*/
void Camera::SetWpc(bool i_data) {
  config->SaveWpc(i_data);
  wpc = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Raw Gama
   @param bool - raw gama
   @return none
*/
void Camera::SetRawGama(bool i_data) {
  config->SaveRawGama(i_data);
  raw_gama = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set horizontal Mirror
   @param bool - horizontal mirror
   @return none
*/
void Camera::SetHMirror(bool i_data) {
  config->SaveHmirror(i_data);
  hmirror = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set V Flip
   @param bool - vflip
   @return none
*/
void Camera::SetVFlip(bool i_data) {
  config->SaveVflip(i_data);
  vflip = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Lens correction
   @param bool - lens correction
   @return none
*/
void Camera::SetLensC(bool i_data) {
  config->SaveLensCorrect(i_data);
  lensc = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Exposure Ctrl
   @param bool - exposure ctrl
   @return none
*/
void Camera::SetExposureCtrl(bool i_data) {
  config->SaveExposureCtrl(i_data);
  exposure_ctrl = i_data;
  ApplyCameraCfg();
}

/**
   @brief Set Camera Flash Enable
   @param bool - flash enable/disable
   @return none
*/
void Camera::SetCameraFlashEnable(bool i_data) {
  config->SaveCameraFlashEnable(i_data);
  CameraFlashEnable = i_data;
}

/**
   @brief Set Camera Flash Time
   @param uint16_t - flash time
   @return none
*/
void Camera::SetCameraFlashTime(uint16_t i_data) {
  config->SaveCameraFlashTime(i_data);
  CameraFlashTime = i_data;
}

void Camera::SetCameraImageRotation(uint8_t i_data) {
  config->SaveCameraImageExifRotation(i_data);
  imageExifRotation = i_data;
}

/**
   @brief Get Photo Quality
   @param none
   @return uint8_t - photo quality
*/
uint8_t Camera::GetPhotoQuality() {
  return PhotoQuality;
}

/**
   @brief Get Frame Size
   @param none
   @return uint8_t - frame size
*/
uint8_t Camera::GetFrameSize() {
  return FrameSize;
}

/**
 * @brief transform framesize_t to uint16_t width
 * 
    FRAMESIZE_QVGA (320 x 240)
    FRAMESIZE_CIF (352 x 288)
    FRAMESIZE_VGA (640 x 480)
    FRAMESIZE_SVGA (800 x 600)
    FRAMESIZE_XGA (1024 x 768)
    FRAMESIZE_SXGA (1280 x 1024)
    FRAMESIZE_UXGA (1600 x 1200)
 * 
 * @return uint16_t 
 */
uint16_t Camera::GetFrameSizeWidth() {
  uint16_t ret = 0;

  switch (FrameSize) {
    case 0:
      ret = 320;
      break;
    case 1:
      ret = 352;
      break;
    case 2:
      ret = 640;
      break;
    case 3:
      ret = 800;
      break;
    case 4:
      ret = 1024;
      break;
    case 5:
      ret = 1280;
      break;
    case 6:
      ret = 1600;
      break;
    default:
      ret = 320;
      break;
  }

  return ret;
}

/**
 * @brief transform framesize_t to uint16_t height
 * 
    FRAMESIZE_QVGA (320 x 240)
    FRAMESIZE_CIF (352 x 288)
    FRAMESIZE_VGA (640 x 480)
    FRAMESIZE_SVGA (800 x 600)
    FRAMESIZE_XGA (1024 x 768)
    FRAMESIZE_SXGA (1280 x 1024)
    FRAMESIZE_UXGA (1600 x 1200)
 * 
 * @return uint16_t 
 */
uint16_t Camera::GetFrameSizeHeight() {
  uint16_t ret = 0;
  switch (FrameSize) {
    case 0:
      ret = 240;
      break;
    case 1:
      ret = 288;
      break;
    case 2:
      ret = 480;
      break;
    case 3:
      ret = 600;
      break;
    case 4:
      ret = 768;
      break;
    case 5:
      ret = 1024;
      break;
    case 6:
      ret = 1200;
      break;
    default:
      ret = 240;
      break;
  }

  return ret;
}

/**
   @brief Get Brightness
   @param none
   @return int8_t - brightness
*/
int8_t Camera::GetBrightness() {
  return brightness;
}

/**
   @brief Get Contrast
   @param none
   @return int8_t - contrast
*/
int8_t Camera::GetContrast() {
  return contrast;
}

/**
   @brief Get Saturation
   @param none
   @return int8_t - saturation
*/
int8_t Camera::GetSaturation() {
  return saturation;
}

/**
   @brief Get Auto white balance status
   @param none
   @return bool - Auto white balance status
*/
bool Camera::GetAwb() {
  return awb;
}

/**
   @brief Get Auto white balance gain status
   @param none
   @return bool -  Auto white balance gain status
*/
bool Camera::GetAwbGain() {
  return awb_gain;
}

/**
   @brief Get Auto white balance mode
   @param none
   @return uint8_t - Auto white balance mode
*/
uint8_t Camera::GetAwbMode() {
  return wb_mode;
}

/**
   @brief Get automatic exposure control 2 status
   @param none
   @return bool - Automatic exposure control 2 status
*/
bool Camera::GetAec2() {
  return aec2;
}

/**
   @brief Get automatic exposure level
   @param none
   @return int8_t - Automatic exposure level
*/
int8_t Camera::GetAeLevel() {
  return ae_level;
}

/**
   @brief Get automatic exposure control value
   @param none
   @return uint16_t - Automatic exposure control value
*/
uint16_t Camera::GetAecValue() {
  return aec_value;
}

/**
   @brief Get Gain Ctrl status
   @param none
   @return bool - Gain control status
*/
bool Camera::GetGainCtrl() {
  return gain_ctrl;
}

/**
   @brief Get Agc Gaint
   @param none
   @return uint8_t - agc gain
*/
uint8_t Camera::GetAgcGaint() {
  return agc_gain;
}

/**
   @brief Get Bpc
   @param none
   @return bool - bpc status
*/
bool Camera::GetBpc() {
  return bpc;
}

/**
   @brief Get Wpc
   @param none
   @return bool - wpc status
*/
bool Camera::GetWpc() {
  return wpc;
}

/**
   @brief Get Raw Gama value
   @param none
   @return bool - raw gamma value
*/
bool Camera::GetRawGama() {
  return raw_gama;
}

/**
   @brief Get horizontal Mirror status
   @param none
   @return bool - horizontal mirror status
*/
bool Camera::GetHMirror() {
  return hmirror;
}

/**
   @brief Get vertical Flip status
   @param none
   @return bool - vertical flip status
*/
bool Camera::GetVFlip() {
  return vflip;
}

/**
   @brief Get Lens correction status
   @param none
   @return bool - lens correction status
*/
bool Camera::GetLensC() {
  return lensc;
}

/**
   @brief Get exposure control status
   @param none
   @return bool - exposure control status
*/
bool Camera::GetExposureCtrl() {
  return exposure_ctrl;
}

/**
   @brief Get Camera Flash Enable status
   @param none
   @return bool - camera flash enable status
*/
bool Camera::GetCameraFlashEnable() {
  return CameraFlashEnable;
}

/**
 * @brief Get camera flash time
 * @param none
 * @return uint16_t - camera flash time
 */
uint16_t Camera::GetCameraFlashTime() {
  return CameraFlashTime;
}

uint8_t Camera::GetCameraImageRotation() {
  return imageExifRotation;
}

/* EOF */