#include "writer.h"
#include <chrono>
#include <fstream>
#include <pylon/VideoWriter.h>

void Writer::setVideoSpecs(VideoSpecs specs) {
  videoSpecs = specs;
  specs_valid = true;
}

void Writer::setProjectMetadata(ProjectMetadata mdata) {
  qDebug() << "received metadata";
  metadata = mdata;
  metadata_valid = true;
}

void Writer::writeMetadata(nix::Section &s){
  s.createProperty("project", nix::Value(this->metadata.project().toStdString()));
  s.createProperty("experimenter", nix::Value(this->metadata.experimenter().toStdString()));
  s.createProperty("experiment", nix::Value(this->metadata.experiment().toStdString()));
  s.createProperty("condition", nix::Value(this->metadata.condition().toStdString()));
  s.createProperty("comment", nix::Value(this->metadata.comment().toStdString()));
  s.createProperty("subject", nix::Value(this->metadata.subject().toStdString()));
  s.createProperty("setup", nix::Value(this->metadata.setup().toStdString()));
}

void Writer::run() {
  qDebug() << "writer running!";
  size_t count = 0;
  size_t chunksize = 256;
  // size_t framecount = 0;
  std::ofstream myFile;
  if (videoSpecs.format == VideoFormat::mp4 && !Pylon::CVideoWriter::IsSupported()) {
    qWarning() << "VideoWriter is not supported at the moment. Please install the pylon Supplementary Package for MPEG-4 which is available on the Basler website.";
    // Releases all pylon resources.
    // PylonTerminate();
    // Return with error code 1.
    emit writingDone();
    return;
  }
  qDebug() << "checks done!";

  Pylon::CVideoWriter videoWriter;
  if (specs_valid) {
      stop_request = false;
      stopNow = false;
      if (videoSpecs.format == VideoFormat::raw) {
        myFile.open(videoSpecs.filename, std::ios::out | std::ios::binary);
        myFile.write((char*)&videoSpecs.width, 4);
        myFile.write((char*)&videoSpecs.height, 4);
      } else {
        qDebug() << "setting parameters for video";
        videoWriter.SetParameter((uint32_t)videoSpecs.width, (uint32_t)videoSpecs.height, videoSpecs.pixelType, (double)videoSpecs.fps, videoSpecs.quality);
        videoWriter.Open(videoSpecs.filename.c_str());
      }

      nix::File nix_file =nix::File::open(videoSpecs.filename + ".nix", nix::FileMode::Overwrite, "hdf5", nix::Compression::DeflateNormal);
      nix::Block b = nix_file.createBlock("Recording", "nix.recording");
      nix::Section s = nix_file.createSection("Recording", "nix.recording");
      b.metadata(s);
      nix::Value v(nix::util::timeToStr(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())));
      s.createProperty("date", v);

      nix::Value fn(videoSpecs.filename);
      s.createProperty("moviefile", fn);
      nix::Section sw_sec = s.createSection("PylonRecorder", "nix.software");
      sw_sec.createProperty("version", nix::Value(1));

      nix::Section hw_sec = s.createSection("Basler ACA2040-120um", "nix.hardware.camera");
      hw_sec.createProperty("type", nix::Value("monochrome"));
      hw_sec.createProperty("manufacturer", nix::Value("Basler AG"));
      nix::Property p = hw_sec.createProperty("framerate", nix::Value(static_cast<int>(videoSpecs.fps)));
      p.unit("Hz");
      nix::Property p1 = hw_sec.createProperty("exposure time", nix::Value(static_cast<int>(videoSpecs.exposureTime)));
      p1.unit("us");
      nix::Property p2 = hw_sec.createProperty("detector gain", nix::Value(static_cast<int>(videoSpecs.detectorGain)));
      p2.unit("dB");

      if (metadata_valid) {
        writeMetadata(s);
      }

      nix::NDSize initial_shape(1, chunksize);
      nix::DataArray frametimes = b.createDataArray("frametimes", "nix.imaging.frametimes", nix::DataType::String, initial_shape);
      frametimes.label("time");
      frametimes.appendSetDimension();
      nix::DataArray frameindices = b.createDataArray("frameindex", "nix.imaging.frameid", nix::DataType::Int64, initial_shape);
      frameindices.appendSetDimension();

      std::vector<std::string> stamps_buffer(chunksize);
      std::vector<int64_t> ids_buffer(chunksize);

      nix::NDSize offset(1, 0);
      nix::NDSize current_shape(initial_shape);
      nix::NDSize chunk_shape(1, chunksize);

      qDebug() << "preparations done, starting loop!";
      while ((!stop_request || buffer->bufferLoad() > 0) && !stopNow) {
          if (buffer->bufferLoad() > 0 ) {
            size_t framecount = 0;
            MyImage *img = buffer->read(framecount);
            if (img != nullptr) {
              if (videoSpecs.format == VideoFormat::raw) {
                myFile.write((char*)img->data(), img->size());
              } else {
                Pylon::CPylonImage pyImage;
                try {
                  pyImage.AttachUserBuffer(img->data(), videoSpecs.width * videoSpecs.height, videoSpecs.pixelType, videoSpecs.width, videoSpecs.height, 0, videoSpecs.orientation);
                  videoWriter.Add(pyImage);
                } catch (const Pylon::GenericException &e) {
                  std::cerr << "Writer::run: An exception occurred." << std::endl << e.GetDescription() << std::endl;
                }
              }
              if (count < chunksize) {
                try {
                  stamps_buffer[count] = nix::util::timeToStr(img->timestamp());
                } catch (...) {
                  std::cerr << "Bad time to string conversion " << img->timestamp() << std::endl;
                  stamps_buffer[count] = "invalid";
                }
                ids_buffer[count] = img->index();
                count ++;
              } else {
                frametimes.setData(nix::DataType::String, stamps_buffer.data(), chunk_shape, offset);
                frameindices.setData(nix::DataType::Int64, ids_buffer.data(), chunk_shape, offset);
                current_shape += initial_shape;
                frametimes.dataExtent(current_shape);
                frameindices.dataExtent(current_shape);
                offset[0] += chunksize;
                count = 0;
              }
            }
          } else {
            while (buffer->bufferLoad() < 1 && !stop_request) {
              msleep(5);
            }
          }
        }
        if (count > 0) {
            chunk_shape[0] = count;
            frametimes.setData(nix::DataType::String, stamps_buffer.data(), chunk_shape, offset);
            frameindices.setData(nix::DataType::Int64, ids_buffer.data(), chunk_shape, offset);
        }
        videoWriter.Close();
        myFile.close();
        nix_file.close();
    } else {
      std::cerr << "Got no video specifications, not writing!" << std::endl;
    }
  emit writingDone();
}