//============================================================================
//
//   SSSS    tt          lll  lll       
//  SS  SS   tt           ll   ll        
//  SS     tttttt  eeee   ll   ll   aaaa 
//   SSSS    tt   ee  ee  ll   ll      aa
//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
//  SS  SS   tt   ee      ll   ll  aa  aa
//   SSSS     ttt  eeeee llll llll  aaaaa
//
// Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: TIA.hxx 3276 2016-02-12 14:23:40Z stephena $
//============================================================================

#ifndef TIA_HXX
#define TIA_HXX

class Console;
class Settings;
class Sound;

#include "bspf.hxx"
#include "Device.hxx"
#include "System.hxx"
#include "TIATables.hxx"

/**
  This class is a device that emulates the Television Interface Adaptor 
  found in the Atari 2600 and 7800 consoles.  The Television Interface 
  Adaptor is an integrated circuit designed to interface between an 
  eight bit microprocessor and a television video modulator. It converts 
  eight bit parallel data into serial outputs for the color, luminosity, 
  and composite sync required by a video modulator.  

  This class outputs the serial data into a frame buffer which can then
  be displayed on screen.

  @author  Bradford W. Mott
  @version $Id: TIA.hxx 3276 2016-02-12 14:23:40Z stephena $
*/
class TIA : public Device
{
  public:
    friend class TIADebug;
    friend class RiotDebug;

    /**
      Create a new TIA for the specified console

      @param console  The console the TIA is associated with
      @param sound    The sound object the TIA is associated with
      @param settings The settings object for this TIA device
    */
    TIA(Console& console, Sound& sound, Settings& settings);
    virtual ~TIA() = default;

  public:
    /**
      Reset device to its power-on state
    */
    void reset() override;

    /**
      Reset frame to current YStart/Height properties
    */
    void frameReset();

    /**
      Notification method invoked by the system right before the
      system resets its cycle counter to zero.  It may be necessary
      to override this method for devices that remember cycle counts.
    */
    void systemCyclesReset() override;

    /**
      Install TIA in the specified system.  Invoked by the system
      when the TIA is attached to it.

      @param system The system the device should install itself in
    */
    void install(System& system) override;

    /**
      Install TIA in the specified system and device.  Invoked by
      the system when the TIA is attached to it.  All devices
      which invoke this method take responsibility for chaining
      requests back to *this* device.

      @param system The system the device should install itself in
      @param device The device responsible for this address space
    */
    void install(System& system, Device& device);

    /**
      Save the current state of this device to the given Serializer.

      @param out  The Serializer object to use
      @return  False on any errors, else true
    */
    bool save(Serializer& out) const override;

    /**
      Load the current state of this device from the given Serializer.

      @param in  The Serializer object to use
      @return  False on any errors, else true
    */
    bool load(Serializer& in) override;

    /**
      The following are very similar to save() and load(), except they
      do a 'deeper' save of the display data itself.

      Normally, the internal framebuffer doesn't need to be saved to
      a state file, since the file already contains all the information
      needed to re-create it, starting from scanline 0.  In effect, when a
      state is loaded, the framebuffer is empty, and the next call to
      update() generates valid framebuffer data.

      However, state files saved from the debugger need more information,
      such as the exact state of the internal framebuffer itself *before*
      we call update(), including if the display was in partial frame mode.

      Essentially, a normal state save has 'frame resolution', whereas
      the debugger state save has 'cycle resolution', and hence needs
      more information.  The methods below save/load this extra info,
      and eliminate having to save approx. 50K to normal state files.
    */
    bool saveDisplay(Serializer& out) const;
    bool loadDisplay(Serializer& in);

    /**
      Get a descriptor for the device name (used in error checking).

      @return The name of the object
    */
    string name() const override { return "TIA"; }

    /**
      Get the byte at the specified address

      @return The byte at the specified address
    */
    uInt8 peek(uInt16 address) override;

    /**
      Change the byte at the specified address to the given value

      @param address The address where the value should be stored
      @param value The value to be stored at the address

      @return  True if the poke changed the device address space, else false
    */
    bool poke(uInt16 address, uInt8 value) override;

    /**
      This method should be called at an interval corresponding to the 
      desired frame rate to update the TIA.  Invoking this method will update
      the graphics buffer and generate the corresponding audio samples.
    */
    void update();

    /**
      Answers the current frame buffer

      @return Pointer to the current frame buffer
    */
    uInt8* currentFrameBuffer() const
      { return myCurrentFrameBuffer.get() + myFramePointerOffset + myCurrentFrameJitter; }

    /**
      Answers the previous frame buffer

      @return Pointer to the previous frame buffer
    */
    uInt8* previousFrameBuffer() const
      { return myPreviousFrameBuffer.get() + myFramePointerOffset; }

    /**
      Answers the width and height of the frame buffer
    */
    inline uInt32 width() const  { return 160;           }
    inline uInt32 height() const { return myFrameHeight; }
    inline uInt32 ystart() const { return myFrameYStart; }

    /**
      Changes the current Height/YStart properties.
      Note that calls to these method(s) must be eventually followed by
      ::frameReset() for the changes to take effect.
    */
    void setHeight(uInt32 height) { myFrameHeight = height; }
    void setYStart(uInt32 ystart) { myFrameYStart = ystart; }

    /**
      Enables/disables auto-frame calculation.  If enabled, the TIA
      re-adjusts the framerate at regular intervals.

      @param mode  Whether to enable or disable all auto-frame calculation
    */
    void enableAutoFrame(bool mode) { myAutoFrameEnabled = mode; }

    /**
      Enables/disables color-loss for PAL modes only.

      @param mode  Whether to enable or disable PAL color-loss mode
    */
    void enableColorLoss(bool mode)
      { myColorLossEnabled = myFramerate <= 55 ? mode : false; }

    /**
      Answers whether this TIA runs at NTSC or PAL scanrates,
      based on how many frames of out the total count are PAL frames.
    */
    bool isPAL() const
      { return float(myPALFrameCounter) / myFrameCounter >= (25.0/60.0); }

    /**
      Answers the current color clock we've gotten to on this scanline.

      @return The current color clock
    */
    uInt32 clocksThisLine() const
      { return ((mySystem->cycles() * 3) - myClockWhenFrameStarted) % 228; }

    /**
      Answers the total number of scanlines the TIA generated in producing
      the current frame buffer. For partial frames, this will be the
      current scanline.

      @return The total number of scanlines generated
    */
    uInt32 scanlines() const
      { return ((mySystem->cycles() * 3) - myClockWhenFrameStarted) / 228; }

    /**
      Answers whether the TIA is currently in 'partial frame' mode
      (we're in between a call of startFrame and endFrame).

      @return If we're in partial frame mode
    */
    bool partialFrame() const { return myPartialFrameFlag; }

    /**
      Answers the first scanline at which drawing occured in the last frame.

      @return The starting scanline
    */
    uInt32 startScanline() const { return myStartScanline; }

    /**
      Answers the current position of the virtual 'electron beam' used to
      draw the TIA image.  If not in partial frame mode, the position is
      defined to be in the lower right corner (@ width/height of the screen).
      Note that the coordinates are with respect to currentFrameBuffer(),
      taking any YStart values into account.

      @return The x/y coordinates of the scanline electron beam, and whether
              it is in the visible/viewable area of the screen
    */
    bool scanlinePos(uInt16& x, uInt16& y) const;

    /**
      Enables/disable/toggle the specified (or all) TIA bit(s).  Note that
      disabling a graphical object also disables its collisions.

      @param mode  1/0 indicates on/off, and values greater than 1 mean
                   flip the bit from its current state

      @return  Whether the bit was enabled or disabled
    */
    bool toggleBit(TIABit b, uInt8 mode = 2);
    bool toggleBits();

    /**
      Enables/disable/toggle the specified (or all) TIA bit collision(s).

      @param mode  1/0 indicates on/off, and values greater than 1 mean
                   flip the collision from its current state

      @return  Whether the collision was enabled or disabled
    */
    bool toggleCollision(TIABit b, uInt8 mode = 2);
    bool toggleCollisions();

    /**
      Toggle the display of HMOVE blanks.

      @return  Whether the HMOVE blanking was enabled or disabled
    */
    bool toggleHMOVEBlank();

    /**
      Enables/disable/toggle 'fixed debug colors' mode.

      @param mode  1/0 indicates on/off, otherwise flip from
                   its current state

      @return  Whether the mode was enabled or disabled
    */
    bool toggleFixedColors(uInt8 mode = 2);

    /**
      Enable/disable/query state of 'undriven/floating TIA pins'.

      @param mode  1/0 indicates on/off, otherwise return the current state

      @return  Whether the mode was enabled or disabled
    */
    bool driveUnusedPinsRandom(uInt8 mode = 2);

    /**
      Enables/disable/toggle 'scanline jittering' mode, and set the
      recovery 'factor'.

      @param mode  1/0 indicates on/off, otherwise flip from
                   its current state

      @return  Whether the mode was enabled or disabled
    */
    bool toggleJitter(uInt8 mode = 2);
    void setJitterRecoveryFactor(Int32 f) { myJitterRecoveryFactor = f; }

#ifdef DEBUGGER_SUPPORT
    /**
      This method should be called to update the TIA with a new scanline.
    */
    void updateScanline();

    /**
      This method should be called to update the TIA with a new partial
      scanline by stepping one CPU instruction.
    */
    void updateScanlineByStep();

    /**
      This method should be called to update the TIA with a new partial
      scanline by tracing to target address.
    */
    void updateScanlineByTrace(int target);
#endif

  private:
    /**
      Enables/disables all TIABit bits.  Note that disabling a graphical
      object also disables its collisions.

      @param mode  Whether to enable or disable all bits
    */
    void enableBits(bool mode);

    /**
      Enables/disables all TIABit collisions.

      @param mode  Whether to enable or disable all collisions
    */
    void enableCollisions(bool mode);

    // Reset all instance variables to the initial state
    void initialize();

    // Update the current frame buffer to the specified color clock
    void updateFrame(Int32 clock);

    // Waste cycles until the current scanline is finished
    void waitHorizontalSync();

    // Reset horizontal sync counter
    void waitHorizontalRSync();

    // Clear both internal TIA buffers to black (palette color 0)
    void clearBuffers();

    // Set up bookkeeping for the next frame
    void startFrame();

    // Update bookkeeping at end of frame
    void endFrame();

    // Convert resistance from ports to dumped value
    uInt8 dumpedInputPort(int resistance);

    // Write the specified value to the HMOVE registers at the given clock
    void pokeHMP0(uInt8 value, Int32 clock);
    void pokeHMP1(uInt8 value, Int32 clock);
    void pokeHMM0(uInt8 value, Int32 clock);
    void pokeHMM1(uInt8 value, Int32 clock);
    void pokeHMBL(uInt8 value, Int32 clock);

    // Apply motion to registers when HMOVE is currently active
    void applyActiveHMOVEMotion(int hpos, Int16& pos, Int32 motionClock);

    // Apply motion to registers when HMOVE was previously active
    void applyPreviousHMOVEMotion(int hpos, Int16& pos, uInt8 motion);

  private:
    // Console the TIA is associated with
    Console& myConsole;

    // Sound object the TIA is associated with
    Sound& mySound;

    // Settings object the TIA is associated with
    Settings& mySettings;

    // Pointer to the current frame buffer
    unique_ptr<uInt8[]> myCurrentFrameBuffer;

    // Pointer to the previous frame buffer
    unique_ptr<uInt8[]> myPreviousFrameBuffer;

    // Pointer to the next pixel that will be drawn in the current frame buffer
    uInt8* myFramePointer;

    // Indicates offset used by the exported frame buffer
    // (the exported frame buffer is a vertical 'sliding window' of the actual buffer)
    uInt32 myFramePointerOffset;

    // Indicates the number of 'colour clocks' offset from the base
    // frame buffer pointer
    // (this is used when loading state files with a 'partial' frame)
    uInt32 myFramePointerClocks;

    // Indicated what scanline the frame should start being drawn at
    uInt32 myFrameYStart;

    // Indicates the height of the frame in scanlines
    uInt32 myFrameHeight;

    // Indicates offset in color clocks when display should stop
    uInt32 myStopDisplayOffset;

    // Indicates color clocks when the current frame began
    Int32 myClockWhenFrameStarted;

    // Indicates color clocks when frame should begin to be drawn
    Int32 myClockStartDisplay;

    // Indicates color clocks when frame should stop being drawn
    Int32 myClockStopDisplay;

    // Indicates color clocks when the frame was last updated
    Int32 myClockAtLastUpdate;

    // Indicates how many color clocks remain until the end of 
    // current scanline.  This value is valid during the 
    // displayed portion of the frame.
    Int32 myClocksToEndOfScanLine;

    // Indicates the total number of scanlines generated by the last frame
    uInt32 myScanlineCountForLastFrame;

    // Indicates the maximum number of scanlines to be generated for a frame
    uInt32 myMaximumNumberOfScanlines;

    // Indicates potentially the first scanline at which drawing occurs
    uInt32 myStartScanline;

    // Color clock when VSYNC ending causes a new frame to be started
    Int32 myVSYNCFinishClock; 

    uInt8 myVSYNC;        // Holds the VSYNC register value
    uInt8 myVBLANK;       // Holds the VBLANK register value

    uInt8 myNUSIZ0;       // Number and size of player 0 and missle 0
    uInt8 myNUSIZ1;       // Number and size of player 1 and missle 1

    uInt8 myPlayfieldPriorityAndScore;
    uInt8 myPriorityEncoder[2][256];
    uInt8 myColor[8];
    uInt8 myFixedColor[8];
    uInt8* myColorPtr;

    uInt8 myCTRLPF;       // Playfield control register

    bool myREFP0;         // Indicates if player 0 is being reflected
    bool myREFP1;         // Indicates if player 1 is being reflected

    uInt32 myPF;          // Playfield graphics (19-12:PF2 11-4:PF1 3-0:PF0)

    uInt8 myGRP0;         // Player 0 graphics register
    uInt8 myGRP1;         // Player 1 graphics register
    
    uInt8 myDGRP0;        // Player 0 delayed graphics register
    uInt8 myDGRP1;        // Player 1 delayed graphics register

    bool myENAM0;         // Indicates if missle 0 is enabled
    bool myENAM1;         // Indicates if missle 1 is enabled

    bool myENABL;         // Indicates if the ball is enabled
    bool myDENABL;        // Indicates if the vertically delayed ball is enabled

    uInt8 myHMP0;         // Player 0 horizontal motion register
    uInt8 myHMP1;         // Player 1 horizontal motion register
    uInt8 myHMM0;         // Missle 0 horizontal motion register
    uInt8 myHMM1;         // Missle 1 horizontal motion register
    uInt8 myHMBL;         // Ball horizontal motion register

    bool myVDELP0;        // Indicates if player 0 is being vertically delayed
    bool myVDELP1;        // Indicates if player 1 is being vertically delayed
    bool myVDELBL;        // Indicates if the ball is being vertically delayed

    bool myRESMP0;        // Indicates if missle 0 is reset to player 0
    bool myRESMP1;        // Indicates if missle 1 is reset to player 1

    uInt16 myCollision;     // Collision register

    // Determines whether specified collisions are enabled or disabled
    // The lower 16 bits are and'ed with the collision register to mask out
    // any collisions we don't want to be processed
    // The upper 16 bits are used to store which objects is currently
    // enabled or disabled
    // This is necessary since there are 15 collision combinations which
    // are controlled by 6 objects
    uInt32 myCollisionEnabledMask;

    // Note that these position registers contain the color clock 
    // on which the object's serial output should begin (0 to 159)
    Int16 myPOSP0;        // Player 0 position register
    Int16 myPOSP1;        // Player 1 position register
    Int16 myPOSM0;        // Missle 0 position register
    Int16 myPOSM1;        // Missle 1 position register
    Int16 myPOSBL;        // Ball position register

    // The color clocks elapsed so far for each of the graphical objects,
    // as denoted by 'MOTCK' line described in A. Towers TIA Hardware Notes
    Int32 myMotionClockP0;
    Int32 myMotionClockP1;
    Int32 myMotionClockM0;
    Int32 myMotionClockM1;
    Int32 myMotionClockBL;

    // Indicates 'start' signal for each of the graphical objects as
    // described in A. Towers TIA Hardware Notes
    Int32 myStartP0;
    Int32 myStartP1;
    Int32 myStartM0;
    Int32 myStartM1;

    // Index into the player mask arrays indicating whether display
    // of the first copy should be suppressed
    uInt8 mySuppressP0;
    uInt8 mySuppressP1;

    // Latches for 'more motion required' as described in A. Towers TIA
    // Hardware Notes
    bool myHMP0mmr;
    bool myHMP1mmr;
    bool myHMM0mmr;
    bool myHMM1mmr;
    bool myHMBLmmr;

    // Graphics for Player 0 that should be displayed.  This will be
    // reflected if the player is being reflected.
    uInt8 myCurrentGRP0;

    // Graphics for Player 1 that should be displayed.  This will be
    // reflected if the player is being reflected.
    uInt8 myCurrentGRP1;

    // It's VERY important that the BL, M0, M1, P0 and P1 current
    // mask pointers are always on a uInt32 boundary.  Otherwise,
    // the TIA code will fail on a good number of CPUs.
    const uInt8* myP0Mask;
    const uInt8* myM0Mask;
    const uInt8* myM1Mask;
    const uInt8* myP1Mask;
    const uInt8* myBLMask;
    const uInt32* myPFMask;

    // Audio values; only used by TIADebug
    uInt8 myAUDV0, myAUDV1, myAUDC0, myAUDC1, myAUDF0, myAUDF1;

    // Indicates when the dump for paddles was last set
    Int32 myDumpDisabledCycle;

    // Indicates if the dump is current enabled for the paddles
    bool myDumpEnabled;

    // Latches for INPT4 and INPT5
    uInt8 myINPT4, myINPT5;

    // Indicates if HMOVE blanks are currently or previously enabled,
    // and at which horizontal position the HMOVE was initiated
    Int32 myCurrentHMOVEPos;
    Int32 myPreviousHMOVEPos;
    bool myHMOVEBlankEnabled;
    bool myAllowHMOVEBlanks;

    // Indicates if unused TIA pins are randomly driven high or low
    // Otherwise, they take on the value previously on the databus
    bool myTIAPinsDriven;

    // Bitmap of the objects that should be considered while drawing
    uInt8 myEnabledObjects;

    // Determines whether specified bits (from TIABit) are enabled or disabled
    // This is and'ed with the enabled objects each scanline to mask out any
    // objects we don't want to be processed
    uInt8 myDisabledObjects;

    // Indicates if color loss should be enabled or disabled.  Color loss
    // occurs on PAL (and maybe SECAM) systems when the previous frame
    // contains an odd number of scanlines.
    bool myColorLossEnabled;

    // Indicates whether we're done with the current frame. poke() clears this
    // when VSYNC is strobed or the max scanlines/frame limit is hit.
    bool myPartialFrameFlag;

    // Automatic framerate correction based on number of scanlines
    bool myAutoFrameEnabled;

    // Number of total frames displayed by this TIA
    uInt32 myFrameCounter;

    // Number of PAL frames displayed by this TIA
    uInt32 myPALFrameCounter;

    // The framerate currently in use by the Console
    float myFramerate;

    // Whether TIA bits/collisions are currently enabled/disabled
    bool myBitsEnabled, myCollisionsEnabled;
  
    // Whether to enable jitter emulation
    bool myJitterEnabled;

    // Derived from the difference between the scanline counts of the
    // current and prior frames.  If non-zero the next frame should jitter.
    Int32 myNextFrameJitter;
  
    // Jitter amount for the current frame
    Int32 myCurrentFrameJitter;

    // Large jitter values will take multiple frames to recover from
    Int32 myJitterRecovery, myJitterRecoveryFactor;
  
  private:
    // Following constructors and assignment operators not supported
    TIA() = delete;
    TIA(const TIA&) = delete;
    TIA(TIA&&) = delete;
    TIA& operator=(const TIA&) = delete;
    TIA& operator=(TIA&&) = delete;
};

#endif
