#include <iostream>
#include <iomanip>
#include <cassert>
#include <vector>
#include "unit.h"
#include "block.h"
#include "jenga.h"

// ==========================================================================================
// IMPORTANT: Carefully read this file,
//            but do not make any changes to the code.
//
//            This file contains the implementation of the Jenga member
//            functions related to Print.  You should place your Jenga class
//            declaration in jenga.h and the implementation of all other Jenga
//            member functions > 1 line in jenga.cpp.
// ==========================================================================================


// The Jenga::Print member function can be called with zero arguments
// or with 3 arguments.  If zero arguments are specified the default
// values of (2, false, false) should be used.  Here's how to declare
// the Print function inside your Jenga class in the jenga.h file to
// specify those default argument values:
//
//   void Print(int size=2, bool center_of_mass=false, bool links=false) const;


void Jenga::Print(int size, bool center_of_mass, bool links) const {

  // error check the arguments
  if (size % 2 == 1 || size < 2) {
    std::cerr << "ERROR: size must be even and >= 2" << std::endl;
    exit(1);
  }
  if (links == true && size < 4) {
    std::cerr << "ERROR: cannot label links with a size smaller than 4" << std::endl;
    exit(1);
  }
  if (center_of_mass == true && size < 4) {
    std::cerr << "ERROR: cannot label center of mass with a size smaller than 4" << std::endl;
    exit(1);
  }

  // print the top bar
  std::string top_bar_labels = "   " + std::string(grid_width*size+3,' ');
  std::string top_bar        = "   " + std::string(grid_width*size+3,'.');
  if (links == true) {
    for (int i = 0; i < grid_width; i++) {
      if (top[i] != NULL) {
        top_bar_labels[(i+0.5)*size+4] = top[i]->getBlock()->getLetter();
        top_bar_labels[(i+0.5)*size+5] = '0' + top[i]->getWhichUnit();
        top_bar[(i+0.5)*size+4]        = 'v';
      }
    }
  }
  if (links == true) { std::cout << top_bar_labels << std::endl; }
  std::cout << top_bar << std::endl;

  // allocate a 2D grid of characters that will be filled in
  // by the Block and Unit Print helper functions
  std::vector<std::string> grid((max_height+1)*size+1,
                                std::string(grid_width*size+1,' '));
      
  // call the Block and Unit Print helper functions
  for (unsigned int i = 0; i < blocks.size(); i++) {
    PrintBlock(size, links, grid,blocks[i]);
  }

  // add labels for the Block center of mass
  if (center_of_mass) {
    for (unsigned int i = 0; i < blocks.size(); i++) {
      float m;
      float COM;
      Block *b = blocks[i];
      b->getCenterOfMass(m,COM);
      int row = b->getRow();
      int COM_pos = int(COM * size);
      grid[row*size][COM_pos] = std::toupper(b->getLetter()); //'*';
    }
  }

  // print the rows of the grid
  for (int i = grid.size()-1; i >= 0; i--) {
    // add a number the rows
    if (i % size == size/2) {
      std::cout << std::setw(2) << i / size;
    } else {
      std::cout << "  ";
    }
    // print the grid
    std::cout << " ." << grid[i] << "." << std::endl;
  }
  
  // print the ground bar
  std::string ground_bar        = "   " + std::string(grid_width*size+3,'=');
  std::string ground_bar_labels = "   " + std::string(grid_width*size+3,' ');
  if (links == true) {
    for (int i = 0; i < grid_width; i++) {
      if (ground[i] != NULL) {
        ground_bar[(i+0.5)*size+4]        = '^';
        ground_bar_labels[(i+0.5)*size+4] = ground[i]->getBlock()->getLetter();
        ground_bar_labels[(i+0.5)*size+5] = '0' + ground[i]->getWhichUnit();
      }
    }
  }
  std::cout << ground_bar << std::endl;
  if (links == true) {
    std::cout << ground_bar_labels << std::endl;
  }

  // add numbers for the columns
  std::cout << "    ";
  for (int i = 0; i <= grid_width; i++) {
    std::cout << std::left << std::setw(size) << i;
  }
  std::cout << std::endl;
}

// ==========================================================================================

void Jenga::PrintBlock(int size, bool links, std::vector<std::string> &grid,Block *b) const {
  for (int j = 0; j < b->getWidth(); j++) {
    PrintUnit(size, links, grid, b->getUnit(j));
  }    
}

// ==========================================================================================

void Jenga::PrintUnit(int size, bool links, std::vector<std::string> &grid, Unit* u) const {
  int r = u->getBlock()->getRow();
  int c = u->getColumn();
  
  for (int i = 1; i < size; i++) {
    // draw a vertical bar to the left of Unit 0
    if (u->getWhichUnit() == 0) 
      grid[r*size+i][c*size] = '|';
    // draw a vertical bar to the right of Unit width-1
    if (u->getWhichUnit() == u->getBlock()->getWidth()-1) 
      grid[r*size+i][c*size+size] = '|';
    // draw a horizontal bar above and below each unit
    grid[r*size][c*size+i] = '-';
    grid[r*size+size][c*size+i] = '-';
  }

  // label each unit with the letter for the block
  grid[(r+0.5)*size][(c+0.5)*size] = u->getBlock()->getLetter();

  // label the corners of every unit
  grid[r*size][c*size] = '+';
  grid[r*size][c*size+size] = '+';
  grid[r*size+size][c*size] = '+';
  grid[r*size+size][c*size+size] = '+';

  // label the up & down pointers between Units
  if (links) {
    if (u->getUp() != NULL) {
      grid[r*size+size-1][c*size+size/2] = u->getUp()->getBlock()->getLetter();
      grid[r*size+size-1][c*size+size/2+1] = '0' + u->getUp()->getWhichUnit();
      grid[r*size+size][c*size+size/2] = '#';
    }
    if (u->getDown() != NULL) {
      grid[r*size+1][c*size+size/2] = u->getDown()->getBlock()->getLetter();
      grid[r*size+1][c*size+size/2+1] = '0' + u->getDown()->getWhichUnit();
      grid[r*size][c*size+size/2] = '#';
    }
  }
}

// ==========================================================================================
