/*------------------------------------------------------------------------------
Projekt: ICP - game Othello
Authors: Michal Schorm (xschor02)
Date: FIT VUTBR 2016                                                

File content: class Field, enum FieldDirection
-------------------------------------------------------------------------------*/

#include <cstddef>    // work with NULL
#include "./Disk.cpp" // Disks are placed on the fields.

using namespace std;



//! ENUM FieldDirection - contains values for distinguish the direction, in which its neighbour is in the board. 8 possible directions
#ifndef FIELD_TYPE
#define FIELD_TYPE
enum FieldDirection { RU, RR, RD, LU, LL, LD, UU, DD };
#endif



//!  Class Field - represents a place on which disk can be placed.
/*!
Fields are inside game Board.
Fileds are either inside Board or compose border around.
Fields that are not borders must know all of their neighbours to work properly.
Fields that are borders must not contain any disks to work properly. 
*/
#ifndef FIELD_CLASS_FULL
#define FIELD_CLASS_FULL
class Field 
{
 private:
   //! Direction Right Up
   Field * neighbourRU = NULL;
   //! Direction Right
   Field * neighbourRR = NULL;
   //! Direction Right Down
   Field * neighbourRD = NULL;
   //! Direction Left Up
   Field * neighbourLU = NULL;
   //! Direction Left
   Field * neighbourLL = NULL;
   //! Direction Left down
   Field * neighbourLD = NULL;
   //! Direction Up
   Field * neighbourUU = NULL;
   //! Direction Down
   Field * neighbourDD = NULL;
   //! Pointer to Disk placed on this field
   Disk * disk = NULL;
   //! Information, if the field is border field. (true/false)
   bool border;



   //!  Private method used by other methods in this class to see if some action will affect this filed neighbours or to affect them that way. 
   /*!
   Operation determines whether calling method wants to check, if anything change or if it wants to apply changes.
   Field Direction determines direction in which (from this filed) changes will be traced / applied
   DiskColor determines color of the disk that would be put / is put on the filed
   
   The method goes through fields neighbours one by one and looks for line of disks of opposite color ended by same colored disk.  
   */
   bool diskChangeLine(DiskColor color, FieldDirection dir, bool operation) // ověřuje, jestli vložený disk ovlivní daný směr
     {
      Field * soused;
         
      if(color == white)
        {
         switch(dir)
           {
            case RU:
              soused = this->neighbourRU;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourRU; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourRU; do{ soused->turnDisk(); soused = soused->neighbourRU; } while( soused->getDiskColor() == black ); } return true; } }      
              return false;
            case RR:
              soused = this->neighbourRR;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourRR; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourRR; do{ soused->turnDisk(); soused = soused->neighbourRR; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case RD:
              soused = this->neighbourRD;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourRD; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourRD; do{ soused->turnDisk(); soused = soused->neighbourRD; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case LU:
              soused = this->neighbourLU;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourLU; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourLU; do{ soused->turnDisk(); soused = soused->neighbourLU; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case LL:
              soused = this->neighbourLL;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourLL; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourLL; do{ soused->turnDisk(); soused = soused->neighbourLL; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case LD:
              soused = this->neighbourLD;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourLD; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourLD; do{ soused->turnDisk(); soused = soused->neighbourLD; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case UU:
              soused = this->neighbourUU;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourUU; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourUU; do{ soused->turnDisk(); soused = soused->neighbourUU; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
            case DD:
              soused = this->neighbourDD;
              if(soused->getDiskColor() == black) { do{ soused = soused->neighbourDD; } while( soused->getDiskColor() == black ); if(soused->getDiskColor() == white)
                { if(operation){ soused = this->neighbourDD; do{ soused->turnDisk(); soused = soused->neighbourDD; } while( soused->getDiskColor() == black ); } return true; } }             
              return false;
           }
        }
      else if(color == black)
        {
         switch(dir)
           {
            case RU:
              soused = this->neighbourRU;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourRU; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourRU; do{ soused->turnDisk(); soused = soused->neighbourRU; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case RR:
              soused = this->neighbourRR;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourRR; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourRR; do{ soused->turnDisk(); soused = soused->neighbourRR; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case RD:
              soused = this->neighbourRD;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourRD; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourRD; do{ soused->turnDisk(); soused = soused->neighbourRD; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case LU:
              soused = this->neighbourLU;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourLU; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourLU; do{ soused->turnDisk(); soused = soused->neighbourLU; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case LL:
              soused = this->neighbourLL;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourLL; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourLL; do{ soused->turnDisk(); soused = soused->neighbourLL; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case LD:
              soused = this->neighbourLD;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourLD; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourLD; do{ soused->turnDisk(); soused = soused->neighbourLD; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case UU:
              soused = this->neighbourUU;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourUU; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourUU; do{ soused->turnDisk(); soused = soused->neighbourUU; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
            case DD:
              soused = this->neighbourDD;
              if(soused->getDiskColor() == white) { do{ soused = soused->neighbourDD; } while( soused->getDiskColor() == white ); if(soused->getDiskColor() == black)
                { if(operation){ soused = this->neighbourDD; do{ soused->turnDisk(); soused = soused->neighbourDD; } while( soused->getDiskColor() == white ); } return true; } }             
              return false;
           }
        }      
      return false;  
     } 
   
 public:
   //! Constructor only assign information whether is this field a border field
   Field(bool border)
     {
      this->border = border;
     }
   
   //! Returns color of disk on this field or "none" if this field doesn´t contain any disk   
   DiskColor getDiskColor()
     {
      if( this->disk == NULL ) return none;
      return this->disk->getColor();
     }      

   //! Call method to swap color of the disk on this field
   void turnDisk()
     {
      if( this->disk != NULL ) this->disk->turn();
     }    
   
   //! Method to set pointers to neighbour field in given direcion 
   void setNeighbour(FieldDirection direction, Field * field)
     {
      switch (direction)
        {
         case RU:    
            this->neighbourRU = field;   
            break;
         case RR:    
            this->neighbourRR = field;   
            break;
         case RD:    
            this->neighbourRD = field;   
            break;   
         case LU:    
            this->neighbourLU = field;   
            break;   
         case LL:    
            this->neighbourLL = field;   
            break;   
         case LD:    
            this->neighbourLD = field;   
            break;   
         case UU:    
            this->neighbourUU = field;   
            break;
         case DD:    
            this->neighbourDD = field;   
            break;    
        } 
     }        
   
   //! Method to get pointers to neighbour field in given direcion. Returns NULL if none neighbour is set in direction given.      
   Field * getNeighbour(FieldDirection direction)
     {
      switch (direction)
        {
         case RU:    
            return this->neighbourRU;   
            break;
         case RR:    
            return this->neighbourRR;   
            break;
         case RD:    
            return this->neighbourRD;   
            break;   
         case LU:    
            return this->neighbourLU;   
            break;   
         case LL:    
            return this->neighbourLL;   
            break;   
         case LD:    
            return this->neighbourLD;   
            break;   
         case UU:    
            return this->neighbourUU;   
            break;
         case DD:    
            return this->neighbourDD;   
            break;
         default:
            return NULL;
        } 
     }             

   //! Returns (true/false) if this filed is a border field  
   bool isBorder()
     {
      return this->border;
     }        
   
   //! Returns pointer to disk on this filed. Can return NULL.  
   Disk * getDisk()
     {
      return this->disk;
     }     
  
   //! Checks, whether a disk of given color can be put on this field. (true/false)    
   bool canPutDisk(DiskColor color)
     {
      if( this->disk != NULL ) return false;   // there is a disk already on this field
      if( this->border == true ) return false; // disks cannot be placed on border fields
      //cout << "canPutDisk(" << color  << ")\n"; // CLI debugg output
      int dir;
      for( dir=0; dir<=8; dir++){ if( this->diskChangeLine(color, (FieldDirection)dir, false) ) return true; }
      return false;  
     }   

   //! Place a disk of given color on this field. (Without any check !! method canPutDisk MUST be always called before )
   void putDisk(Disk * disk)
     {
      this->disk = disk;
      DiskColor color = disk->getColor();
      int dir;
      for( dir=0; dir<=8; dir++) this->diskChangeLine(color, (FieldDirection)dir, true);   
     }

     
};
#endif