#include "cmos.h"
#include <QFile>
#include <QTextStream>

CMOS::CMOS() {
  this->cap = Capabilities();
  this->flp = Flip();
  this->bin = Binning();
  this->skip = Skip();
  this->lim = Limits();
  this->roi = ROI();
  this->img = ImageParameters();
  this->AEC = false;
  this->AGC = false;
  this->HDR = false;
  this->test = false;
  this->brightness = 0;
  this->manual_gain = 0;
  this->exposure_time = 0;
}

void CMOS::processSensorData(uint8_t type, QByteArray &data) {
  switch (type) {
  case 0:
    if (data.length() > 0) {
      this->sensorName = QString(data);
      emit sensorDataUpdated(0);
    }
    break;
  case 1:
    if (data.length() == 1) {
      this->shut = data.at(0) == 0 ? global : rolling;
      emit sensorDataUpdated(1);
    }
    break;
  case 2:
    if (this->cap.setCapabilities(data)) {
      emit sensorDataUpdated(2);
    }
    break;
  case 3:
    if (this->lim.setWidth(data)) {
      emit sensorDataUpdated(3);
    }
    break;
  case 4:
    if (this->lim.setHeight(data)) {
      emit sensorDataUpdated(4);
    }
    break;
  case 5:
    if (this->lim.setBin(data)) {
      emit sensorDataUpdated(5);
    }
    break;
  case 6:
    if (this->lim.setSkip(data)) {
      emit sensorDataUpdated(6);
    }
    break;
  case 7:
    if (this->lim.setGain(data)) {
      emit sensorDataUpdated(7);
    }
    break;
  case 8:
    if (this->lim.setBright(data)) {
      emit sensorDataUpdated(8);
    }
    break;
  case 9:
    if (this->img.setWidth(data)) {
      emit sensorDataUpdated(9);
    }
    break;
  case 10:
    if (this->img.setHeight(data)) {
      emit sensorDataUpdated(10);
    }
    break;
  case 11:
    if (this->img.setRGB(data)) {
      emit sensorDataUpdated(11);
    }
    break;
  case 12:
    if (this->roi.setEnabled(data)) {
      emit sensorDataUpdated(12);
    }
    break;
  case 13:
    if (this->roi.seetROIParams(data)) {
      emit sensorDataUpdated(13);
    }
    break;
  case 14:
    if (data.length() == 1) {
      this->AEC = data[0];
      emit sensorDataUpdated(14);
    }
    break;
  case 15:
    if (data.length() == 1) {
      this->AGC = data[0];
      emit sensorDataUpdated(15);
    }
    break;
  case 16:
    if (data.length() == 1) {
      this->HDR = data[0];
      emit sensorDataUpdated(16);
    }
    break;
  case 17:
    if (data.length() == 1) {
      this->brightness = static_cast<uint8_t>(data[0]);
      emit sensorDataUpdated(17);
    }
    break;
  case 18:
    if (data.length() == 1) {
      this->manual_gain = static_cast<uint8_t>(data[0]);
      emit sensorDataUpdated(18);
    }
    break;
  case 19: {
    if (data.length() == 4) {
      uint8_t exp[4] = {
          static_cast<uint8_t>(data[0]), static_cast<uint8_t>(data[1]),
          static_cast<uint8_t>(data[2]), static_cast<uint8_t>(data[3])};
      this->exposure_time = (*reinterpret_cast<uint32_t *>(exp));
      emit sensorDataUpdated(19);
    }
    break;
  }
  case 20:
    if (this->bin.setBinning(data)) {
      emit sensorDataUpdated(20);
      // Emit width / height lims
      emit sensorDataUpdated(3);
      emit sensorDataUpdated(4);
      // Emit ROI paramas
      emit sensorDataUpdated(13);
    }
    break;
  case 21:
    if (this->skip.setSkip(data)) {
      emit sensorDataUpdated(21);
      // Emit width / height lims
      emit sensorDataUpdated(3);
      emit sensorDataUpdated(4);
      // Emit ROI paramas
      emit sensorDataUpdated(13);
    }
    break;
  case 22:
    if (this->flp.setFlip(data)) {
      emit sensorDataUpdated(22);
    }
    break;
  case 23:
    if (data.length() == 1) {
      this->test = data[0];
      emit sensorDataUpdated(23);
    }
    break;
  default:
    break;
  }
}

float CMOS::gainToReal(uint8_t gain) {
  float g = 1;
  uint32_t i = 0;
  uint16_t limit = this->lim.gainLims.at(i);
  uint16_t prevLim = this->lim.minGain;
  float stepSizeReal = this->lim.gainStepSizeReal.at(i);
  uint16_t stepSize = this->lim.gainStepSize.at(i);
  while (gain > limit) {
    g += stepSizeReal * ((limit - prevLim) / stepSize);
    prevLim = this->lim.gainLims.at(i);
    i++;
    limit = this->lim.gainLims.at(i);
    stepSizeReal = this->lim.gainStepSizeReal.at(i);
    stepSize = this->lim.gainStepSize.at(i);
  }
  g += stepSizeReal * ((gain - prevLim) / stepSize);
  return g;
}

uint8_t CMOS::gainToSensor(float gain) {
  uint32_t i = 0;
  float g = this->gainToReal(static_cast<uint8_t>(this->lim.gainLims.at(i)));
  uint16_t down = this->lim.minGain;
  while (gain > g) {
    down = this->lim.gainLims.at(i);
    i++;
    g = this->gainToReal(static_cast<uint8_t>(this->lim.gainLims.at(i)));
  }
  float downLimit = this->gainToReal(static_cast<uint8_t>(down));
  uint8_t addGain = static_cast<uint8_t>(
      ((gain - downLimit) / this->lim.gainStepSizeReal.at(i)) *
      this->lim.gainStepSize.at(i));
  return static_cast<uint8_t>(down) + addGain;
}

void CMOS::refreshROI(void) { emit sensorDataUpdated(13); }
void CMOS::refreshAEC(void) { emit sensorDataUpdated(14); }
void CMOS::refreshAGC(void) { emit sensorDataUpdated(15); }
void CMOS::refreshHDR(void) { emit sensorDataUpdated(16); }
void CMOS::refreshBright(void) { emit sensorDataUpdated(17); }
void CMOS::refreshGain(void) { emit sensorDataUpdated(18); }
void CMOS::refreshExposure(void) { emit sensorDataUpdated(19); }
void CMOS::refreshBinning(void) { emit sensorDataUpdated(20); }
void CMOS::refreshSkip(void) { emit sensorDataUpdated(21); }
void CMOS::refreshFlip(void) { emit sensorDataUpdated(22); }

bool CMOS::saveCMOS(QString name) {
  name.append("." + this->sensorName);
  QFile file(name);
  if (!file.open(QIODevice::WriteOnly)) {
    return false;
  }
  QTextStream out(&file);
  out << CMOSTypeText << ": " << this->sensorName << endValue;
  out << CMOSShutterText << ": " << this->shut << endValue;
  out << CMOCapabText << ": " << this->cap << endValue;
  out << CMOSLimitsText << ": " << this->lim << endValue;
  if (this->cap.aec) {
    out << CMOSAECText << ": " << this->AEC << endValue;
  }
  if (this->cap.agc) {
    out << CMOSAGCText << ": " << this->AGC << endValue;
  }
  if (this->cap.hdr) {
    out << CMOSHDRText << ": " << this->HDR << endValue;
  }
  if (this->cap.test) {
    out << CMOSTestText << ": " << this->test << endValue;
  }
  if (this->cap.aec || this->cap.agc) {
    out << CMOSBrightText << ": " << this->brightness << endValue;
  }
  out << CMOSGainText << ": " << this->manual_gain << endValue;
  out << CMOSExpText << ": " << this->exposure_time << endValue;
  out << CMOSImageParamsText << ": " << this->img << endValue;
  if (this->cap.flip) {
    out << CMOSFlipText << ": " << this->flp << endValue;
  }
  if (this->cap.bin) {
    out << CMOSBinText << ": " << this->bin << endValue;
  }
  if (this->cap.skip) {
    out << CMOSSkipText << ": " << this->skip << endValue;
  }
  if (this->cap.roi) {
    out << CMOSROIText << ": " << this->roi << endValue;
  }
  file.close();
  return true;
}

bool CMOS::loadCMOS(QString name) {
  QFile file(name);
  if (!file.open(QIODevice::ReadOnly)) {
    file.close();
    return false;
  }
  QTextStream in(&file);
  QString line;
  QStringList list;
  QStringList listVector;
  string_code_objects loadObjectName = obj_simple;
  QString value;
  CMOS *temp = new CMOS();
  QList<uint8_t> read;
  while ((line = in.readLine()) != nullptr) {
    if (!line.contains("}")) {
      list = line.split(":");
      if (list.length() != 2) {
        file.close();
        return false;
      }
      if (list.at(1).contains("{")) {
        loadObjectName = getObjectCode(list.at(0).trimmed());
      } else {
        // trimm value
        value = list.at(1);
        value = value.remove(0, 1);
        value = value.remove(value.length() - 1, 1);

        switch (loadObjectName) {

        case obj_capab:
          read.append(2);
          switch (getCapabCode(list.at(0).trimmed())) {
          case capab_RGB:
            temp->cap.rgb = value.toInt();
            break;
          case capab_AEC:
            temp->cap.aec = value.toInt();
            break;
          case capab_AGC:
            temp->cap.agc = value.toInt();
            break;
          case capab_HDR:
            temp->cap.hdr = value.toInt();
            break;
          case capab_ROI:
            temp->cap.roi = value.toInt();
            break;
          case capab_bin:
            temp->cap.bin = value.toInt();
            break;
          case capab_skip:
            temp->cap.skip = value.toInt();
            break;
          case capab_flip:
            temp->cap.flip = value.toInt();
            break;
          case capab_test:
            temp->cap.test = value.toInt();
            break;
          case capab_invalid:
            file.close();
            return false;
          }
          break;
        case obj_limits:
          switch (getLimsCode(list.at(0).trimmed())) {
          case lims_minWidth:
            temp->lim.minWidth = static_cast<uint16_t>(value.toUInt());
            read.append(3);
            break;
          case lims_maxWidth:
            temp->lim.maxWidth = static_cast<uint16_t>(value.toUInt());
            break;
          case lims_minHeight:
            temp->lim.minHeight = static_cast<uint16_t>(value.toUInt());
            read.append(4);
            break;
          case lims_maxHeight:
            temp->lim.maxHeight = static_cast<uint16_t>(value.toUInt());
            break;
          case lims_minBright:
            temp->lim.minBright = static_cast<uint8_t>(value.toUInt());
            read.append(8);
            break;
          case lims_maxBright:
            temp->lim.maxBright = static_cast<uint8_t>(value.toUInt());
            break;
          case lims_minGain:
            temp->lim.minGain = static_cast<uint8_t>(value.toUInt());
            break;
          case lims_maxGain:
            temp->lim.maxGain = static_cast<uint8_t>(value.toUInt());
            read.append(7);
            break;
          case lims_gainStepR:
            listVector = value.split(" ");
            foreach (QString val, listVector) {
              temp->lim.gainStepSizeReal.push_back(val.toFloat());
            }
            break;
          case lims_gainStep:
            listVector = value.split(" ");
            foreach (QString val, listVector) {
              temp->lim.gainStepSize.push_back(
                  static_cast<uint16_t>(val.toUInt()));
            }
            break;
          case lims_gainLim:
            listVector = value.split(" ");
            foreach (QString val, listVector) {
              temp->lim.gainLims.push_back(static_cast<uint16_t>(val.toUInt()));
            }
            break;
          case lims_bin:
            listVector = value.split(" ");
            foreach (QString val, listVector) {
              temp->lim.binSteps.push_back(static_cast<uint8_t>(val.toUInt()));
            }
            if (listVector.length() > 0 && listVector.at(0).compare("")) {
              read.append(5);
            } else {
              temp->lim.binSteps.clear();
            }
            break;
          case lims_skip:
            listVector = value.split(" ");
            foreach (QString val, listVector) {
              temp->lim.skipSteps.push_back(static_cast<uint8_t>(val.toUInt()));
            }
            if (listVector.length() > 0 && listVector.at(0).compare("")) {
              read.append(6);
            } else {
              temp->lim.skipSteps.clear();
            }
            break;
          case lims_invalid:
            file.close();
            return false;
          }
          break;
        case obj_simple:
          switch (getSimpleCode(list.at(0).trimmed())) {
          case simple_CMOStype:
            temp->sensorName = value;
            read.append(0);
            break;
          case simple_CMOShutter:
            temp->shut = value.toInt() == 0 ? global : rolling;
            read.append(1);
            break;
          case simple_AEC:
            temp->AEC = value.toInt();
            read.append(14);
            break;
          case simple_AGC:
            temp->AGC = value.toInt();
            read.append(15);
            break;
          case simple_HDR:
            temp->HDR = value.toInt();
            read.append(16);
            break;
          case simple_test:
            temp->test = value.toInt();
            read.append(23);
            break;
          case simple_bright:
            temp->brightness = static_cast<uint8_t>(value.toUInt());
            read.append(17);
            break;
          case simple_gain:
            temp->manual_gain = static_cast<uint8_t>(value.toUInt());
            read.append(18);
            break;
          case simple_exp:
            temp->exposure_time = value.toUInt();
            read.append(19);
            break;
          case simple_invalid:
            file.close();
            return false;
          }
          break;
        case obj_imgParams:
          switch (getImageCode(list.at(0).trimmed())) {
          case img_width:
            temp->img.width = static_cast<uint16_t>(value.toUInt());
            read.append(9);
            break;
          case img_height:
            temp->img.height = static_cast<uint16_t>(value.toUInt());
            read.append(10);
            break;
          case img_rgb:
            temp->img.rgb = value.toInt();
            read.append(11);
            break;
          case img_invalid:
            file.close();
            return false;
          }
          break;
        case obj_roi:
          switch (getROICode(list.at(0).trimmed())) {
          case roi_enabled:
            temp->roi.enabled = value.toInt();
            read.append(12);
            break;
          case roi_width:
            read.append(13);
            temp->roi.width = static_cast<uint16_t>(value.toUInt());
            break;
          case roi_height:
            temp->roi.height = static_cast<uint16_t>(value.toUInt());
            break;
          case roi_startX:
            temp->roi.beginX = static_cast<uint16_t>(value.toUInt());
            break;
          case roi_startY:
            temp->roi.beginY = static_cast<uint16_t>(value.toUInt());
            break;
          case roi_invalid:
            file.close();
            return false;
          }
          break;
        case obj_flip:
          read.append(22);
          switch (getFlipCode(list.at(0).trimmed())) {
          case flip_row:
            temp->flp.row = value.toInt();
            break;
          case flip_col:
            temp->flp.collum = value.toInt();
            break;
          case flip_invalid:
            file.close();
            return false;
          }
          break;
        case obj_binnig:
          read.append(20);
          switch (getBinCode(list.at(0).trimmed())) {
          case bin_row:
            temp->bin.row = static_cast<uint8_t>(value.toUInt());
            break;
          case bin_col:
            temp->bin.collum = static_cast<uint8_t>(value.toUInt());
            break;
          case bin_invalid:
            file.close();
            return false;
          }
          break;
        case obj_skip:
          read.append(21);
          switch (getSkipCode(list.at(0).trimmed())) {
          case skip_row:
            temp->skip.row = static_cast<uint8_t>(value.toUInt());
            break;
          case skip_col:
            temp->skip.collum = static_cast<uint8_t>(value.toUInt());
            break;
          case skip_invalid:
            file.close();
            return false;
          }
          break;
        case obj_invalid:
          file.close();
          return false;
        }
      }
    } else {
      loadObjectName = obj_simple;
    }
  }
  this->copyParameters(temp);
  delete (temp);
  foreach (uint8_t code, read) { emit sensorDataUpdated(code); }
  file.close();
  return true;
}

void CMOS::copyParameters(CMOS *toCopy) {
  this->sensorName = toCopy->sensorName;
  this->AEC = toCopy->AEC;
  this->AGC = toCopy->AGC;
  this->HDR = toCopy->HDR;
  this->test = toCopy->test;
  this->brightness = toCopy->brightness;
  this->manual_gain = toCopy->manual_gain;
  this->exposure_time = toCopy->exposure_time;

  this->cap.aec = toCopy->cap.aec;
  this->cap.agc = toCopy->cap.agc;
  this->cap.bin = toCopy->cap.bin;
  this->cap.flip = toCopy->cap.flip;
  this->cap.hdr = toCopy->cap.hdr;
  this->cap.rgb = toCopy->cap.rgb;
  this->cap.roi = toCopy->cap.roi;
  this->cap.test = toCopy->cap.test;
  this->cap.skip = toCopy->cap.skip;

  this->flp.collum = toCopy->flp.collum;
  this->flp.row = toCopy->flp.row;

  this->bin.collum = toCopy->bin.collum;
  this->bin.row = toCopy->bin.row;

  this->skip.collum = toCopy->skip.collum;
  this->skip.row = toCopy->skip.row;

  this->lim.gainLims.clear();
  foreach (uint16_t val, toCopy->lim.gainLims) {
    this->lim.gainLims.push_back(val);
  }
  this->lim.gainStepSize.clear();
  foreach (uint16_t val, toCopy->lim.gainStepSize) {
    this->lim.gainStepSize.push_back(val);
  }
  this->lim.gainStepSizeReal.clear();
  foreach (float val, toCopy->lim.gainStepSizeReal) {
    this->lim.gainStepSizeReal.push_back(val);
  }
  this->lim.binSteps.clear();
  foreach (uint8_t val, toCopy->lim.binSteps) {
    this->lim.binSteps.push_back(val);
  }
  this->lim.skipSteps.clear();
  foreach (uint8_t val, toCopy->lim.skipSteps) {
    this->lim.skipSteps.push_back(val);
  }
  this->lim.maxBright = toCopy->lim.maxBright;
  this->lim.maxGain = toCopy->lim.maxGain;
  this->lim.maxHeight = toCopy->lim.maxHeight;
  this->lim.maxWidth = toCopy->lim.maxWidth;
  this->lim.minBright = toCopy->lim.minBright;
  this->lim.minGain = toCopy->lim.minGain;
  this->lim.minHeight = toCopy->lim.minHeight;
  this->lim.minWidth = toCopy->lim.minWidth;

  this->roi.beginX = toCopy->roi.beginX;
  this->roi.beginY = toCopy->roi.beginY;
  this->roi.enabled = toCopy->roi.enabled;
  this->roi.width = toCopy->roi.width;
  this->roi.height = toCopy->roi.height;

  this->img.height = toCopy->img.height;
  this->img.width = toCopy->img.width;
  this->img.rgb = toCopy->img.rgb;
}
