#pragma once
#include <vector>
#include <map>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cmath>
#include <memory>
#include <random>
#include <set>
#include <cassert>

#include "basic_object.h"
class PBFNetlist;

// Define the class to handle netlist in Protocol Buffer Format
// Basic data structure
// PlcObject : a superset of attributes for different types of plc objects
// This is a superset of attributes for different types of plc objects
// A plc object can only have some or all the attributes
// Please check Circuit Training repo (https://github.com/google-research/circuit_training/blob/main/docs/NETLIST_FORMAT.md) for detailed explanation
class PlcObject {
  public:
    // constructor
    PlcObject(size_t node_id) : node_id_(node_id) {  }
    // functions
    // GetType Information
    inline bool IsHardMacro() const {
      return pb_type_ == MACRO;
    }
    
    inline bool IsSoftMacro() const {
      return pb_type_ == GRP;
    }
    
    inline bool IsSoftMacroPin() const {
      return pb_type_ == GRPPIN;
    }

    inline bool IsHardMacroPin() const {
      return pb_type_ == MACROPIN;
    }

    inline bool IsPort() const {
      return pb_type_ == PORT;
    }
    
    // Make Square
    inline void MakeSquare() {
      const float area = width_N_ * height_N_;
      width_N_ = std::sqrt(area);
      height_N_ = width_N_;
      width_ = width_N_;
      height_ = height_N_;
    }
    
    // Get real location instead of relative position
    inline const std::pair<float, float> GetPos() const {
      return std::pair<float, float>(x_, y_);
    }

    inline float GetX() const {
      return x_;
    }    

    inline float GetY() const {
      return y_;
    }

    inline const Rect GetBBox() const {
      return bbox_;
    }
    
    inline const GridRect GetGridBBox() const {
      return grid_bbox_;
    }
    
    // we need grid_width and grid_height to calculate the grid_bbox
    // We cannot set the location of MACROPIN and GRPPIN.
    // Because the locations of MACROPIN and GRPPIN are determined by MACRO and GRP.
    // When you specify the location of MACRO and GRP, the locations of coorepsonding MACROPINs and
    // GRPPINs will also be updated.
    // We only call this function after reading plc file
    void SetPos(float x, float y, float grid_width, float grid_height, int num_cols, int num_rows);
    
    // Force related functions
    inline void ResetForce() {
      f_x_ = 0.0;
      f_y_ = 0.0;
    }

    inline void AddForce(float f_x, float f_y) {
      f_x_ += f_x;
      f_y_ += f_y;
    }

    inline void NormalForce(float max_f_x, float max_f_y) {
      if (max_f_x > 0.0)
        f_x_ = f_x_ / max_f_x;
      if (max_f_y > 0.0)
        f_y_ = f_y_ / max_f_y;
    }

    inline std::pair<float, float> GetForce() const {
      return std::pair<float, float>(f_x_, f_y_);
    }

    // Flip operation
    // Flop operation can only be applied to hard macro
    // if x_flag == true, flip across x axis
    // else, flip across y axis
    void Flip(bool x_flag, float grid_width, float grid_height, int num_cols, int num_rows) {
      if (pb_type_ != MACRO)
        return;
      if (x_flag == true) { // flip around x axis
        switch (orient_)
        {
          case N:
            orient_ = FS;
            break;
          case FN:
            orient_ = S;
            break;
          case S:
            orient_ = FN;
            break;
          case FS:
            orient_ = N;
            break;
          case E:
            orient_ = FW;
            break;
          case FE:
            orient_ = W;
            break;
          case FW:
            orient_ = E;
            break;
          case W:
            orient_ = FE;
            break;
          default:
            orient_ = N;
            break;
        }
      } else { // flip around y axis
        switch (orient_)
        {
          case N:
            orient_ = FN;
            break;
          case FN:
            orient_ = N;
            break;
          case S:
            orient_ = FS;
            break;
          case FS:
            orient_ = S;
            break;
          case E:
            orient_ = FE;
            break;
          case FE:
            orient_ = E;
            break;
          case FW:
            orient_ = W;
            break;
          case W:
            orient_ = FW;
            break;
          default:
            orient_ = N;
            break;
        }
      }
      // we need update cooresponding items after orientation change
      UpdateOrientation(grid_width, grid_height, num_cols, num_rows); 
    }

    // string representation
    friend std::ostream& operator <<(std::ostream &out, PlcObject& object); // for protocol buffer netlist
    std::string SimpleStr() const; // for plc file    
  private:
    // inherient attributes
    std::string name_ = "";
    size_t node_id_ = 0;  
    float weight_ = 1.0;
    PBTYPE pb_type_ = MACRO;  // hard macro
    // we define xx_N_ becuase the Portocol buffer netlist required these values to generate the netlist file
    float width_N_ = 0.0;
    float height_N_ = 0.0;
    float x_offset_N_ = 0.0;
    float y_offset_N_ = 0.0;
    std::string macro_name_ = ""; // the correponding macro names
    std::shared_ptr<PlcObject> macro_ptr_ = nullptr; 
    std::vector<std::shared_ptr<PlcObject> > macro_pins_; // pins connnected to this macro
    std::vector<size_t> nets_; // nets (id) connecte to this node (src and sink)
    std::vector<std::string> inputs_; // sinks driven by this node
    std::string side_ = "LEFT"; // attribute for IOPORT (LEFT, RIGHT, TOP, BOTTOM)
    // real information  (can be updated)
    float x_ = 0.0; // center of the object
    float y_ = 0.0; // center of the object
    float height_ = 0.0;
    float width_ = 0.0; 
    float x_offset_ = 0.0;
    float y_offset_ = 0.0;
    ORIENTATION orient_ = N; // orientation
    Rect bbox_;
    GridRect grid_bbox_; // gridding box
    // the forces applied to this node
    float f_x_ = 0.0;
    float f_y_ = 0.0;   
    // utility function
    void UpdateBBox(float grid_width, float grid_height, int num_cols, int num_rows);
    // update the orientation
    void UpdateOrientation(float grid_width, float grid_height, int num_cols, int num_rows) {
      // update the hard macro information first
      if (orient_ == E || orient_ == FE || orient_ == W || orient_ == FW) {
        width_ = height_N_;
        height_ = width_N_;
      } else {
        width_ = width_N_;
        height_ = height_N_;
      }
      UpdateBBox(grid_width, grid_height, num_cols, num_rows);
      // update the cooresponding bboxes of pins
      for (auto& pin : macro_pins_) {
        switch (orient_)
        {
          case N:
            pin->x_offset_ = pin->x_offset_N_;
            pin->y_offset_ = pin->y_offset_N_;
            break;
          case FN:
            pin->x_offset_ = -1 * pin->x_offset_N_;
            pin->y_offset_ = pin->y_offset_N_;
            break;
          case S:
            pin->x_offset_ = -1 * pin->x_offset_N_;
            pin->y_offset_ = -1 * pin->y_offset_N_;
            break;
          case FS:
            pin->x_offset_ = pin->x_offset_N_;
            pin->y_offset_ = -1 * pin->y_offset_N_;
            break;
          case E:
            pin->x_offset_ = pin->y_offset_N_;
            pin->y_offset_ = -1 * pin->x_offset_N_;
            break;
          case FE:
            pin->x_offset_ = -1 * pin->y_offset_N_;
            pin->y_offset_ = -1 * pin->x_offset_N_;
            break;
          case FW:
            pin->x_offset_ = -1 * pin->y_offset_N_;
            pin->y_offset_ = pin->x_offset_N_;
            break;
          case W:
            pin->x_offset_ = pin->y_offset_N_;
            pin->y_offset_ = pin->x_offset_N_;
            break;
          default:
            break;
        }
        pin->UpdateBBox(grid_width, grid_height, num_cols, num_rows);
      }
    }

    // utility function
    friend class PBFNetlist;
    friend class Net;
};


// Utility functions
std::string PrintPlaceholder(std::string key, std::string value);
std::string PrintPlaceholder(std::string key, float value);