/***************************************************************************
 *   Copyright (C) 2023 by Jarda Vrana                                     *
 *   jarda.vrana@gmail.com                                                 *
 *                                                                         *
 *   This program is free software;  you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation;  either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program;  if not, see <http://www.gnu.org/licenses/>.  *
 *                                                                         *
 ***************************************************************************/

#pragma once

// Class Z80RegPair allows access to two 8 bit registers as one 16 bits register pair.
// The register pair is composed by calling constuctor with references to high byte and low byte.
class Z80RegPair
{
    public:
        Z80RegPair( uint8_t &h, uint8_t &l ) : high(h), low(l) {; }
        void setLowByte( const uint8_t &byte ) { low = byte;  }
        void setHighByte( const uint8_t &byte ) { high = byte;  }
        uint8_t& getLowByte() { return low;  }
        uint8_t& getHighByte() { return high;  }
        Z80RegPair operator++( int ) { Z80RegPair temp = *this;  uint16_t r16 = *this;  r16++;  *this = r16;  return temp;  }
        Z80RegPair operator--( int ) { Z80RegPair temp = *this;  uint16_t r16 = *this;  r16--;  *this = r16;  return temp;  }
        operator uint16_t() const { return (high << 8) | low;  }
        Z80RegPair& operator=( const uint16_t& word ) { high = word >> 8;  low = word & 0xff;  return *this;  }
    
    private:
        uint8_t &high; 
        uint8_t &low; 
}; 

// Class Z80Reg16 is 16 bis register. It contains same methods as class Z80RegPair to be able to use this class by same template as class Z80RegPair.
class Z80Reg16
{
    public:
        void setLowByte( const uint8_t &byte ) { reg &= 0xff00;  reg |= byte;  }
        void setHighByte( const uint8_t &byte ) { reg &= 0x00ff;  reg |= (byte << 8);  }
        uint8_t getLowByte() { return reg & 0x00ff;  }
        uint8_t getHighByte() { return reg >> 8;  }
        Z80Reg16 operator++( int ) { Z80Reg16 temp = *this;  reg++;  return temp;  }
        Z80Reg16 operator--( int ) { Z80Reg16 temp = *this;  reg--;  return temp;  }
        operator uint16_t() const { return reg;  }
        Z80Reg16& operator=( const uint16_t& word ) { reg = word;  return *this;  }
    
    private:
        uint16_t reg; 
}; 
        
        
// Class Z80Flags is 8 bit register with flags. The methods allow various flags modification. It also remember if flags were modified or not.
class Z80Flags {
    enum { fC = 0x01, fN = 0x02, fP = 0x04, fX = 0x08, fH = 0x10, fY = 0x20, fZ = 0x40, fS = 0x80 };   // don't change flags numbers
    
    public:
        void clearChanged() { lastChanged = changed;  changed = false;  }
        bool isChanged() { return lastChanged;  }
            
        bool getS() { return flags & fS;  }
        bool getZ() { return flags & fZ;  }
        bool getH() { return flags & fH;  }
        bool getP() { return flags & fP;  }
        bool getN() { return flags & fN;  }
        bool getC() { return flags & fC;  }
        Z80Flags& setH() { flags |= fH;  changed = true;  return *this;  }
        Z80Flags& setN() { flags |= fN;  changed = true;  return *this;  }
        Z80Flags& setC() { flags |= fC;  changed = true;  return *this;  }
        Z80Flags& resetY() { flags &= ~fY;  changed = true;  return *this;  }
        Z80Flags& resetH() { flags &= ~fH;  changed = true;  return *this;  }
        Z80Flags& resetX() { flags &= ~fX;  changed = true;  return *this;  }
        Z80Flags& resetN() { flags &= ~fN;  changed = true;  return *this;  }
        Z80Flags& resetC() { flags &= ~fC;  changed = true;  return *this;  }
        Z80Flags& resetHN() { flags &= ~( fH | fN );  changed = true;  return *this;  }
        Z80Flags& negC() { flags ^= fC;  changed = true;  return *this;  }
        Z80Flags& copyYX(const uint8_t &r) { flags &= ~( fY | fX );  flags |= r & ( fY | fX );  changed = true;  return *this;  }
        Z80Flags& copyNegCtoH() { flags &= ~fH;  flags |= (~flags << 4) & fH;  changed = true;  return *this;  }
        Z80Flags& andZ(const bool &z) { if (!z) flags &= ~fZ;  changed = true;  return *this;  }
        Z80Flags& orYX(const uint8_t &r) { flags |= r & ( fY | fX );  changed = true;  return *this;  }
        Z80Flags& storeP(const bool &p) { flags &= ~fP;  flags |= p << 2 ;  changed = true;  return *this;  }
        Z80Flags& storeC(const bool &c) { flags &= ~fC;  flags |= c ;  changed = true;  return *this;  }
        Z80Flags& storeHC(const bool &hc) { flags &= ~( fH | fC);  flags |= hc | (hc << 4) ;  changed = true;  return *this;  }
        Z80Flags& copySzeroZ(const uint8_t &r) { flags &= ~( fS | fZ );  flags |= r & fS; 
                                                 if ( r == 0x00 ) flags |= fZ; 
                                                 changed = true;  return *this;  }
        Z80Flags& copySzeroZP(const uint8_t &r) { flags &= ~( fS | fZ | fP );  flags |= r & fS; 
                                                  if ( r == 0x00 ) flags |= fZ | fP; 
                                                  changed = true;  return *this;  }
        Z80Flags& copySYXzeroZ(const uint8_t &r) { flags &= ~( fS | fZ | fY | fX );  flags |= r & ( fS | fY | fX ); 
                                                   if ( r == 0x00 ) flags |= fZ; 
                                                   changed = true;  return *this;  }
        Z80Flags& copySYXzeroZparityP(const uint8_t &r) { flags &= ~( fS | fZ | fY | fX | fP );  flags |= r & ( fS | fY | fX ); 
                                                          if ( r == 0x00 ) flags |= fZ; 
                                                          if ( parity(r) == false ) flags |= fP; 
                                                          changed = true;  return *this;  }
        Z80Flags& overflowHP(const uint8_t &r) { flags &= ~( fH | fP ); 
                                                 if ( (r & 0x08) != 0x00 ) flags |= fH; 
                                                 if ( (r & 0xC0) == 0x80 || (r & 0xC0) == 0x40 ) flags |= fP; 
                                                 changed = true;  return *this;  }
        Z80Flags& overflowHC(const uint8_t &r) { flags &= ~( fH | fC ); 
                                                 if ( (r & 0x08) != 0x00 ) flags |= fH; 
                                                 if ( (r & 0x80) != 0x00 ) flags |= fC; 
                                                 changed = true;  return *this;  }
        Z80Flags& overflowHPC(const uint8_t &r) { flags &= ~( fH | fP | fC ); 
                                                  if ( (r & 0x08) != 0x00 ) flags |= fH; 
                                                  if ( (r & 0xC0) == 0x80 || (r & 0xC0) == 0x40 ) flags |= fP; 
                                                  if ( (r & 0x80) != 0x00 ) flags |= fC; 
                                                  changed = true;  return *this;  }
        Z80Flags& parityP(const uint8_t &r) { flags &= ~fP;  if ( parity(r) == false ) flags |= fP;  changed = true;  return *this;  }
        Z80Flags& copyBit1YX(const uint8_t &r) { flags &= ~( fY | fX );  flags |= r & fX;  if ( (r & 0x02) != 0x00 ) flags |= fY;  changed = true;  return *this;  }
        Z80Flags& copyBit7N(const uint8_t &r) { flags &= ~fN;  if ( (r & 0x80) != 0x00 ) flags |= fN;  changed = true;  return *this;  }
        Z80Flags& inotxxPH(const uint8_t &r1, const uint8_t &r2) {
            if ((flags & fC) == 0) flags ^= ( (parity(r2 & 0x07) == true) << 2 ) & fP; 
            else {
                flags &= ~fH; 
                if ((r1 & 0x80) != 0) {
                    if ((r2 & 0x0f) == 0x00) flags |= fH; 
                    flags ^= ( (parity((r2 - 1) & 0x07) == true) << 2) & fP; 
                } else {
                    if ((r2 & 0x0f) == 0x0f) flags |= fH; 
                    flags ^= ( (parity((r2 + 1) & 0x07) == true) << 2 ) & fP; 
                }
            }
            changed = true; 
            return *this; 
        }
        operator uint8_t&() { return flags;  }
        uint8_t* operator&() { return &flags;  }
        uint8_t& operator=( const uint16_t& f ) { flags = f;  return flags;  }
    
    private:
        uint8_t flags; 
        bool changed = false; 
        bool lastChanged = false; 
            
        // Calculation parity of bits in reg. Result is true for odd number of bits and false for even number of bits.
        bool parity( uint8_t reg ) { reg ^= (reg >> 1);  reg ^= (reg >> 2);  reg ^= (reg >> 4);  return (reg & 0x01);  }
}; 
