// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2023 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: strandmark@google.com (Petter Strandmark)
//
// Simple class for accessing PGM images.

#ifndef CERES_EXAMPLES_PGM_IMAGE_H_
#define CERES_EXAMPLES_PGM_IMAGE_H_

#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "glog/logging.h"

namespace ceres::examples {

template <typename Real>
class PGMImage {
 public:
  // Create an empty image
  PGMImage(int width, int height);
  // Load an image from file
  explicit PGMImage(std::string filename);
  // Sets an image to a constant
  void Set(double constant);

  // Reading dimensions
  int width() const;
  int height() const;
  int NumPixels() const;

  // Get individual pixels
  Real* MutablePixel(int x, int y);
  Real Pixel(int x, int y) const;
  Real* MutablePixelFromLinearIndex(int index);
  Real PixelFromLinearIndex(int index) const;
  int LinearIndex(int x, int y) const;

  // Adds an image to another
  void operator+=(const PGMImage& image);
  // Adds a constant to an image
  void operator+=(Real a);
  // Multiplies an image by a constant
  void operator*=(Real a);

  // File access
  bool WriteToFile(std::string filename) const;
  bool ReadFromFile(std::string filename);

  // Accessing the image data directly
  bool SetData(const std::vector<Real>& new_data);
  const std::vector<Real>& data() const;

 protected:
  int height_, width_;
  std::vector<Real> data_;
};

// --- IMPLEMENTATION

template <typename Real>
PGMImage<Real>::PGMImage(int width, int height)
    : height_(height), width_(width), data_(width * height, 0.0) {}

template <typename Real>
PGMImage<Real>::PGMImage(std::string filename) {
  if (!ReadFromFile(filename)) {
    height_ = 0;
    width_ = 0;
  }
}

template <typename Real>
void PGMImage<Real>::Set(double constant) {
  for (int i = 0; i < data_.size(); ++i) {
    data_[i] = constant;
  }
}

template <typename Real>
int PGMImage<Real>::width() const {
  return width_;
}

template <typename Real>
int PGMImage<Real>::height() const {
  return height_;
}

template <typename Real>
int PGMImage<Real>::NumPixels() const {
  return width_ * height_;
}

template <typename Real>
Real* PGMImage<Real>::MutablePixel(int x, int y) {
  return MutablePixelFromLinearIndex(LinearIndex(x, y));
}

template <typename Real>
Real PGMImage<Real>::Pixel(int x, int y) const {
  return PixelFromLinearIndex(LinearIndex(x, y));
}

template <typename Real>
Real* PGMImage<Real>::MutablePixelFromLinearIndex(int index) {
  CHECK(index >= 0);
  CHECK(index < width_ * height_);
  CHECK(index < data_.size());
  return &data_[index];
}

template <typename Real>
Real PGMImage<Real>::PixelFromLinearIndex(int index) const {
  CHECK(index >= 0);
  CHECK(index < width_ * height_);
  CHECK(index < data_.size());
  return data_[index];
}

template <typename Real>
int PGMImage<Real>::LinearIndex(int x, int y) const {
  return x + width_ * y;
}

// Adds an image to another
template <typename Real>
void PGMImage<Real>::operator+=(const PGMImage<Real>& image) {
  CHECK(data_.size() == image.data_.size());
  for (int i = 0; i < data_.size(); ++i) {
    data_[i] += image.data_[i];
  }
}

// Adds a constant to an image
template <typename Real>
void PGMImage<Real>::operator+=(Real a) {
  for (int i = 0; i < data_.size(); ++i) {
    data_[i] += a;
  }
}

// Multiplies an image by a constant
template <typename Real>
void PGMImage<Real>::operator*=(Real a) {
  for (int i = 0; i < data_.size(); ++i) {
    data_[i] *= a;
  }
}

template <typename Real>
bool PGMImage<Real>::WriteToFile(std::string filename) const {
  std::ofstream outputfile(filename.c_str());
  outputfile << "P2" << std::endl;
  outputfile << "# PGM format" << std::endl;
  outputfile << " # <width> <height> <levels> " << std::endl;
  outputfile << " # <data> ... " << std::endl;
  outputfile << width_ << ' ' << height_ << " 255 " << std::endl;

  // Write data
  int num_pixels = width_ * height_;
  for (int i = 0; i < num_pixels; ++i) {
    // Convert to integer by rounding when writing file
    outputfile << static_cast<int>(data_[i] + 0.5) << ' ';
  }

  return bool(outputfile);  // Returns true/false
}

namespace {

// Helper function to read data from a text file, ignoring "#" comments.
template <typename T>
bool GetIgnoreComment(std::istream* in, T& t) {
  std::string word;
  bool ok;
  do {
    ok = true;
    (*in) >> word;
    if (word.length() > 0 && word[0] == '#') {
      // Comment; read the whole line
      ok = false;
      std::getline(*in, word);
    }
  } while (!ok);

  // Convert the string
  std::stringstream sin(word);
  sin >> t;

  // Check for success
  if (!in || !sin) {
    return false;
  }
  return true;
}
}  // namespace

template <typename Real>
bool PGMImage<Real>::ReadFromFile(std::string filename) {
  std::ifstream inputfile(filename.c_str());

  // File must start with "P2"
  char ch1, ch2;
  inputfile >> ch1 >> ch2;
  if (!inputfile || ch1 != 'P' || (ch2 != '2' && ch2 != '5')) {
    return false;
  }

  // Read the image header
  int two_fifty_five;
  if (!GetIgnoreComment(&inputfile, width_) ||
      !GetIgnoreComment(&inputfile, height_) ||
      !GetIgnoreComment(&inputfile, two_fifty_five)) {
    return false;
  }
  // Assert that the number of grey levels is 255.
  if (two_fifty_five != 255) {
    return false;
  }

  // Now read the data
  int num_pixels = width_ * height_;
  data_.resize(num_pixels);
  if (ch2 == '2') {
    // Ascii file
    for (int i = 0; i < num_pixels; ++i) {
      int pixel_data;
      bool res = GetIgnoreComment(&inputfile, pixel_data);
      if (!res) {
        return false;
      }
      data_[i] = pixel_data;
    }
    // There cannot be anything else in the file (except comments). Try reading
    // another number and return failure if that succeeded.
    int temp;
    bool res = GetIgnoreComment(&inputfile, temp);
    if (res) {
      return false;
    }
  } else {
    // Read the line feed character
    if (inputfile.get() != '\n') {
      return false;
    }
    // Binary file
    // TODO(strandmark): Will not work on Windows (linebreak conversion).
    for (int i = 0; i < num_pixels; ++i) {
      unsigned char pixel_data = inputfile.get();
      if (!inputfile) {
        return false;
      }
      data_[i] = pixel_data;
    }
    // There cannot be anything else in the file. Try reading another byte
    // and return failure if that succeeded.
    inputfile.get();
    if (inputfile) {
      return false;
    }
  }

  return true;
}

template <typename Real>
bool PGMImage<Real>::SetData(const std::vector<Real>& new_data) {
  // This function cannot change the dimensions
  if (new_data.size() != data_.size()) {
    return false;
  }
  std::copy(new_data.begin(), new_data.end(), data_.begin());
  return true;
}

template <typename Real>
const std::vector<Real>& PGMImage<Real>::data() const {
  return data_;
}

}  // namespace ceres::examples

#endif  // CERES_EXAMPLES_PGM_IMAGE_H_
