#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <cmath>
#include "functions.h"

using namespace std;

Pixel** createImage(int width, int height) {
  cout << "Start createImage... " << endl;
  
  // Create a one dimensional array on the heap of pointers to Pixels 
  //    that has width elements (i.e. the number of columns)
  Pixel** image = new Pixel*[width];
  
  bool fail = false;
  
  for (int i=0; i < width; ++i) { // loop through each column
    // assign that column to a one dimensional array on the heap of Pixels
    //  that has height elements (i.e. the number of rows)
    image[i] = new Pixel[height];
    
    if (image[i] == nullptr) { // failed to allocate
      fail = true;
    }
  }
  
  if (fail) { // if any allocation fails, clean up and avoid memory leak
    // deallocate any arrays created in for loop
    for (int i=0; i < width; ++i) {
      delete [] image[i]; // deleting nullptr is not a problem
    }
    delete [] image; // delete array of pointers
    return nullptr;
  }
  
  // initialize cells
  //cout << "Initializing cells..." << endl;
  for (int row=0; row<height; ++row) {
    for (int col=0; col<width; ++col) {
      //cout << "(" << col << ", " << row << ")" << endl;
      image[col][row] = { 0, 0, 0 };
    }
  }
  cout << "End createImage... " << endl;
  return image;
}

void deleteImage(Pixel** image, int width) {
  cout << "Start deleteImage..." << endl;
  // avoid memory leak by deleting the array
  for (int i=0; i<width; ++i) {
    delete [] image[i]; // delete each individual array placed on the heap
  }
  delete [] image;
  image = nullptr;
}

// implement for part 1

int* createSeam(int length) {
  int* array = new int[length];
  return array            ;
}

void deleteSeam(int* seam) {
  delete [] seam;
} 

bool loadImage(string filename, Pixel** image, int width, int height) {
  fstream fin;
  string type;
  int max;
  int filewidth;
  int fileheight;
  int R;
  int G;
  int B;
  string check = "";
  bool open = false;
  fin.open(filename);
  if (fin.is_open()) {
    open = true;
  }
  else {
    cout << "Error: Failed to open input file - " << filename << endl;
    return false;
  }
  fin >> type;
  if (type != "P3" && type != "p3") {
    cout << "Error: type is " << type << " instead of P3" << endl;
    return false;
  }

  fin >> filewidth;
  if (fin.fail() || fin.bad()) {
    cout << "Error: read non-integer value" << endl;
    return false;
  }
  fin >> fileheight;
  if (fin.fail() || fin.bad()) {
    cout << "Error: read non-integer value" << endl;
    return false;
  }
  
  fin >> max;

  if (filewidth != width) {
    cout << "Error: input width (" << width << ") does not match value in file (" << filewidth << ")" << endl;
    return false;
  }
  if (fileheight != height) {
    cout << "Error: input height (" << height << ") does not match value in file (" << fileheight << ")" << endl;
    return false;
  }

  for (int i = 0; i < height; ++i) {
    for (int j = 0; j < width; ++j) {
      fin >> R;
      if (fin.eof() && fin.fail()) {
        cout << "Error: not enough color values" << endl;
        return false;
      }
      if (fin.fail() || fin.bad()) {
        cout << "Error: read non-integer value" << endl;
        return false;
      }
      
      fin >> G;
      if (fin.eof() && fin.fail()) {
        cout << "Error: not enough color values" << endl;
        return false;
      }
      if (fin.fail() || fin.bad()) {
        cout << "Error: read non-integer value" << endl;
        return false;
      }
      
      fin >> B;
      if (fin.eof() && fin.fail()) {
        cout << "Error: not enough color values" << endl;
        return false;
      }
      if (fin.fail() || fin.bad()) {
        cout << "Error: read non-integer value" << endl;
        return false;
      }
      

      if ((R < 0) || (R > max)) {
        cout << "Error: invalid color value " << R << endl;
        return false;
      }
      if ((G < 0) || (G > max) || fin.fail()) {
        cout << "Error: invalid color value " << G << endl;
        return false;
      }
      if ((B < 0) || (B > max) || fin.fail()) {
        cout << "Error: invalid color value " << B << endl;
        return false;
      }
      image[j][i] = {R, G, B};
    }
  }
  
  fin >> check;
  if (check.size() > 0) {
    cout << "Error: too many color values" << endl;
    return false;
  }
  /*if (!fin.eof() && (!fin.fail() || !fin.bad())) {
    cout << "Error: too many color values" << endl;
    return false;
  }*/

  return open;
}

bool outputImage(string filename, const Pixel*const* image, int width, int height) {
  ofstream fout(filename);
  int max = 255;
  bool open = true;
  if (!fout.is_open()) {
    open = false;
    cout << "Error: failed to open output file - " << filename << endl; 
    return open;
  }
  fout << "P3" << endl;
  fout << width << " ";
  fout << height << endl;
  fout << max << endl;

  for (int i = 0; i < height; ++i) {
    for (int j = 0; j < width; ++j) {
      fout << image[j][i].r << " ";
      fout << image[j][i].g << " ";
      fout << image[j][i].b << " ";
      fout << endl;
    }
  }
  fout.close();

  return open;
}

int energy(const Pixel*const* image, int x, int y, int width, int height) {
  int energy = 0;
  int Rx = 0;
  int Gx = 0;
  int Bx = 0;
  int Ry = 0;
  int Gy = 0;
  int By = 0;
  int deltax = 0;
  int deltay = 0;
  if (x != 0 && x != (width - 1)) {   // non border for x
    Rx = abs((image[x-1][y].r) - (image[x+1][y].r));
    Gx = abs((image[x-1][y].g) - (image[x+1][y].g));
    Bx = abs((image[x-1][y].b) - (image[x+1][y].b));
    deltax = Rx*Rx + Gx*Gx + Bx*Bx;
  }
  else if (x == 0 && x != (width-1)) {  // border where x is the leftmost value
    Rx = abs((image[width-1][y].r) - (image[x+1][y].r));
    Gx = abs((image[width-1][y].g) - (image[x+1][y].g));
    Bx = abs((image[width-1][y].b) - (image[x+1][y].b));
    deltax = Rx*Rx + Gx*Gx + Bx*Bx;
  }
  else if (x != 0 && x == (width - 1)) {  // border value where x is the rightmost value
    Rx = abs((image[x-1][y].r) - (image[0][y].r));
    Gx = abs((image[x-1][y].g) - (image[0][y].g));
    Bx = abs((image[x-1][y].b) - (image[0][y].b));
    deltax = Rx*Rx + Gx*Gx + Bx*Bx;
  }

  if (y != 0 && y != (height- 1)) {   // non border for y
    Ry = abs((image[x][y-1].r) - (image[x][y+1].r));
    Gy = abs((image[x][y-1].g) - (image[x][y+1].g));
    By = abs((image[x][y-1].b) - (image[x][y+1].b));
    deltay = Ry*Ry + Gy*Gy + By*By;
  }
  else if (y == 0 && y != (height- 1)) {   // non border for y
    Ry = abs((image[x][height-1].r) - (image[x][y+1].r));
    Gy = abs((image[x][height-1].g) - (image[x][y+1].g));
    By = abs((image[x][height-1].b) - (image[x][y+1].b));
    deltay = Ry*Ry + Gy*Gy + By*By;
  }
  else if (y != 0 && y == (height- 1)) {   // non border for y
    Ry = abs((image[x][y-1].r) - (image[x][0].r));
    Gy = abs((image[x][y-1].g) - (image[x][0].g));
    By = abs((image[x][y-1].b) - (image[x][0].b));
    deltay = Ry*Ry + Gy*Gy + By*By;
  }
  energy = deltay + deltax;

  return energy;
}

// implement for part 2

// uncomment for part 2

int loadVerticalSeam(Pixel** image, int start_col, int width, int height, int* seam) {
  int seamenergy = energy(image, start_col, 0, width, height);
  seam[0] = start_col;
  for (int i = 1; i < height; ++i) {
    bool can_left = false;
    bool can_right = false;
    int below_energy = 0;
    int right_energy = 0;
    int left_energy = 0;
    below_energy = energy(image, start_col, i, width, height);
    if (start_col != 0) {
      left_energy = energy(image, (start_col - 1), i, width, height);
      can_left = true;
    }
    if (start_col != (width - 1)) {
      right_energy = energy(image, (start_col + 1), i, width, height);
      can_right = true;
    }

    if (can_right && can_left) {
      if (below_energy <= left_energy && below_energy <= right_energy) {
        seamenergy += below_energy;
      }
      else if (right_energy < below_energy && right_energy <= left_energy) {
        seamenergy += right_energy;
        start_col += 1;
      }
      else if (left_energy < below_energy && left_energy < right_energy) {
        seamenergy += left_energy;
        start_col -= 1; 
      }
    }

    else if (can_left && (!can_right)) {
      if (below_energy <= left_energy) {
        seamenergy += below_energy;
      }
      else {
        seamenergy += left_energy;
        start_col -= 1;
      }
    }

    else if (can_right && (!can_left)) {
      if (below_energy <= right_energy) {
        seamenergy += below_energy;
      }
      else {
        seamenergy += right_energy;
        start_col += 1;
      }
    }

    else if (!can_right && !can_left) {
      seamenergy += below_energy;
    }
    seam[i] = start_col;
  }
  return seamenergy;
}

int loadHorizontalSeam(Pixel** image, int start_row, int width, int height, int* seam) {
  int seamenergy = energy(image, 0, start_row, width, height);
  seam[0] = start_row;
  for (int i = 1; i < width; ++i) {
    bool can_left = false;
    bool can_right = false;
    int below_energy = 0;
    int right_energy = 0;
    int left_energy = 0;
    below_energy = energy(image, i, start_row, width, height);
    if (start_row != 0) {
      left_energy = energy(image, i, (start_row - 1), width, height);
      can_left = true;
    }
    if (start_row != (height - 1)) {
      right_energy = energy(image, i, (start_row + 1), width, height);
      can_right = true;
    }

    if (can_right && can_left) {
      if (below_energy <= left_energy && below_energy <= right_energy) {
        seamenergy += below_energy;
      }
      else if (right_energy < below_energy && right_energy < left_energy) {
        seamenergy += right_energy;
        start_row += 1;
      }
      else if (left_energy < below_energy && left_energy <= right_energy) {
        seamenergy += left_energy;
        start_row -= 1; 
      }
    }

    else if (can_left && (!can_right)) {
      if (below_energy <= left_energy) {
        seamenergy += below_energy;
      }
      else {
        seamenergy += left_energy;
        start_row -= 1;
      }
    }

    else if (can_right && (!can_left)) {
      if (below_energy <= right_energy) {
        seamenergy += below_energy;
      }
      else {
        seamenergy += right_energy;
        start_row += 1;
      }
    }

    else if (!can_right && !can_left) {
      seamenergy += below_energy;
    }
    seam[i] = start_row;
  }
  return seamenergy;
}

int* findMinVerticalSeam(Pixel** image, int width, int height) {
  int* seam = createSeam(height);
  int prev_energy = loadVerticalSeam(image, 0, width, height, seam);
  int curr_energy = 0;
  int minindex = 0;
  int* finalseam = createSeam(height);
  for (int i = 1; i < height; ++i) {
    curr_energy = loadVerticalSeam(image, i, width, height, seam);
    if (curr_energy < prev_energy) {
      minindex = i;
      prev_energy = curr_energy;
    }
  }

  prev_energy = loadVerticalSeam(image, minindex, width, height, finalseam);
  return finalseam;
}

int* findMinHorizontalSeam(Pixel** image, int width, int height) {
  int* seam = createSeam(width);
  int prev_energy = loadVerticalSeam(image, 0, width, height, seam);
  int curr_energy = 0;
  int minindex = 0;
  int* finalseam = createSeam(width);
  for (int i = 1; i < width; ++i) {
    curr_energy = loadHorizontalSeam(image, i, width, height, seam);
    if (curr_energy < prev_energy) {
      minindex = i;
      prev_energy = curr_energy;
    }
  }

  prev_energy = loadHorizontalSeam(image, minindex, width, height, finalseam);
  return finalseam;
}

void removeVerticalSeam(Pixel** image, int width, int height, int* verticalSeam) {
  for (int i = 0; i < height; ++i) {
    for (int j = verticalSeam[i]; j < (width - 1); ++j) {
      image[j][i] = image[j+1][i];
    }
  }
}


void removeHorizontalSeam(Pixel** image, int width, int height, int* horizontalSeam) {
  for (int i = 0; i < width; ++i) {
    for (int j = horizontalSeam[i]; j < (height - 1); ++j) {
      image[i][j] = image[i][j+1];
    }
  }
} 

