//============================================================================
//
//   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-2017 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.
//============================================================================

#ifndef CARTRIDGE_HXX
#define CARTRIDGE_HXX

#include <fstream>
#include <sstream>

class Cartridge;
class Properties;
class CartDebugWidget;
class CartRamWidget;
class GuiObject;

#include "bspf.hxx"
#include "Device.hxx"
#include "Settings.hxx"
#include "Font.hxx"

/**
  A cartridge is a device which contains the machine code for a
  game and handles any bankswitching performed by the cartridge.
  A 'bank' is defined as a 4K block that is visible in the
  0x1000-0x2000 area (or its mirrors).

  @author  Bradford W. Mott
*/
class Cartridge : public Device
{
  public:
    /**
      Create a new cartridge object allocated on the heap.  The
      type of cartridge created depends on the properties object.

      @param image    A pointer to the ROM image
      @param size     The size of the ROM image
      @param md5      The md5sum for the given ROM image (can be updated)
      @param dtype    The detected bankswitch type of the ROM image
      @param id       Any extra info about the ROM (currently which part
                      of a multiload game is being accessed
      @param system   The osystem associated with the system
      @param settings The settings associated with the system
      @return   Pointer to the new cartridge object allocated on the heap
    */
    static unique_ptr<Cartridge>
        create(const BytePtr& image, uInt32 size,
               string& md5, string& dtype, string& id,
               const OSystem& system, Settings& settings);

    /**
      Create a new cartridge

      @param settings  A reference to the various settings (read-only)
    */
    Cartridge(const Settings& settings);
    virtual ~Cartridge() = default;

    /**
      Query some information about this cartridge.
    */
    static constexpr string& about() { return myAboutString; }

    /**
      Save the internal (patched) ROM image.

      @param out  The output file stream to save the image
    */
    bool saveROM(ofstream& out);

    /**
      Lock/unlock bankswitching capability.  The debugger will lock
      the banks before querying the cart state, otherwise reading values
      could inadvertantly cause a bankswitch to occur.
    */
    void lockBank()   { myBankLocked = true;  }
    void unlockBank() { myBankLocked = false; }
    bool bankLocked() const { return myBankLocked; }

    /**
      Get the default startup bank for a cart.  This is the bank where
      the system will look at address 0xFFFC to determine where to
      start running code.

      @return  The startup bank
    */
    uInt16 startBank();

    /**
      Answer whether the bank has changed since the last time this
      method was called.  Each cart class is able to override this
      method to deal with its specific functionality.  In those cases,
      the derived class is still responsible for calling this base
      function.

      @return  Whether the bank was changed
    */
    virtual bool bankChanged();

  public:
    //////////////////////////////////////////////////////////////////////
    // The following methods are cart-specific and will usually be
    // implemented in derived classes.  Carts which don't support
    // bankswitching (for any reason) do not have to provide an
    // implementation for bankswitch-related methods.
    //////////////////////////////////////////////////////////////////////
    /**
      Set the specified bank.  This is used only when the bankswitching
      scheme defines banks in a standard format (ie, 0 for first bank,
      1 for second, etc).  Carts which will handle their own bankswitching
      completely or non-bankswitched carts can ignore this method.
    */
    virtual bool bank(uInt16) { return false; }

    /**
      Get the current bank.  Carts which have only one bank (either real
      or virtual) always report that bank as zero.
    */
    virtual uInt16 getBank() const { return 0; }

    /**
      Query the number of 'banks' supported by the cartridge.  Note that
      this information is cart-specific, where each cart basically defines
      what a 'bank' is.

      For the normal Atari-manufactured carts, a standard bank is a 4K
      block that is directly accessible in the 4K address space.  In other
      cases where ROMs have 2K blocks in some preset area, the bankCount
      is the number of such blocks.  Finally, in some esoteric schemes,
      the number of ways that the addressing can change (multiple ROM and
      RAM slices at multiple access points) is so complicated that the
      cart will report having only one 'virtual' bank.
    */
    virtual uInt16 bankCount() const { return 1; }

    /**
      Patch the cartridge ROM.

      @param address  The ROM address to patch
      @param value    The value to place into the address
      @return    Success or failure of the patch operation
    */
    virtual bool patch(uInt16 address, uInt8 value) = 0;

    /**
      Access the internal ROM image for this cartridge.

      @param size  Set to the size of the internal ROM image data
      @return  A pointer to the internal ROM image data
    */
    virtual const uInt8* getImage(int& size) const = 0;

    /**
      Informs the cartridge about the name of the ROM file used when
      creating this cart.

      @param name  The properties file name of the ROM
    */
    virtual void setRomName(const string& name) { }

    /**
      Thumbulator only supports 16-bit ARM code.  Some Harmony/Melody drivers,
      such as BUS and CDF, feature 32-bit ARM code subroutines.  This is used
      to pass values back to the cartridge class to emulate those subroutines.
    */
    virtual uInt32 thumbCallback(uInt8 function, uInt32 value1, uInt32 value2) { return 0; }

    /**
      Get debugger widget responsible for accessing the inner workings
      of the cart.  This will need to be overridden and implemented by
      each specific cart type, since the bankswitching/inner workings
      of each cart type can be very different from each other.
    */
    virtual CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont,
        const GUI::Font& nfont, int x, int y, int w, int h) { return nullptr; }

    // Info about the various bankswitch schemes, useful for displaying
    // in GUI dropdown boxes, etc
    struct BankswitchType {
      const char* type;
      const char* desc;
    };
    enum { ourNumBSTypes = 50 };
    static BankswitchType ourBSList[ourNumBSTypes];

  protected:
    /**
      Indicate that an illegal read from a write port has occurred.

      @param address  The address of the illegal read
    */
    void triggerReadFromWritePort(uInt16 address);

    /**
      Create an array that holds code-access information for every byte
      of the ROM (indicated by 'size').  Note that this is only used by
      the debugger, and is unavailable otherwise.

      @param size  The size of the code-access array to create
    */
    void createCodeAccessBase(uInt32 size);

    /**
      Fill the given RAM array with (possibly random) data.

      @param arr  Pointer to the RAM array
      @param size The size of the RAM array
      @param val  If provided, the value to store in the RAM array
    */
    void initializeRAM(uInt8* arr, uInt32 size, uInt8 val = 0) const;

  private:
    /**
      Get an image pointer and size for a ROM that is part of a larger,
      multi-ROM image.

      @param image    A pointer to the ROM image
      @param size     The size of the ROM image
      @param numroms  The number of ROMs in the multicart
      @param md5      The md5sum for the specific cart in the ROM image
      @param id       The ID for the specific cart in the ROM image
      @param settings The settings associated with the system
      @return   The bankswitch type for the specific cart in the ROM image
    */
    static string createFromMultiCart(const uInt8*& image, uInt32& size,
        uInt32 numroms, string& md5, string& id, Settings& settings);

    /**
      Try to auto-detect the bankswitching type of the cartridge

      @param image  A pointer to the ROM image
      @param size   The size of the ROM image

      @return The "best guess" for the cartridge type
    */
    static string autodetectType(const uInt8* image, uInt32 size);

    /**
      Search the image for the specified byte signature

      @param image      A pointer to the ROM image
      @param imagesize  The size of the ROM image
      @param signature  The byte sequence to search for
      @param sigsize    The number of bytes in the signature
      @param minhits    The minimum number of times a signature is to be found

      @return  True if the signature was found at least 'minhits' time, else false
    */
    static bool searchForBytes(const uInt8* image, uInt32 imagesize,
                               const uInt8* signature, uInt32 sigsize,
                               uInt32 minhits);

    /**
      Returns true if the image is probably a SuperChip (128 bytes RAM)
      Note: should be called only on ROMs with size multiple of 4K
    */
    static bool isProbablySC(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 4K SuperChip (128 bytes RAM)
    */
    static bool isProbably4KSC(const uInt8* image, uInt32 size);

    /**
      Returns true if the image probably contains ARM code in the first 1K
    */
    static bool isProbablyARM(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 0840 bankswitching cartridge
    */
    static bool isProbably0840(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 3E bankswitching cartridge
    */
    static bool isProbably3E(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 3E+ bankswitching cartridge
    */
    static bool isProbably3EPlus(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 3F bankswitching cartridge
    */
    static bool isProbably3F(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a 4A50 bankswitching cartridge
    */
    static bool isProbably4A50(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a BF/BFSC bankswitching cartridge
    */
    static bool isProbablyBF(const uInt8* image, uInt32 size, const char*& type);

    /**
      Returns true if the image is probably a BUS bankswitching cartridge
    */
    static bool isProbablyBUS(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a CDF bankswitching cartridge
    */
    static bool isProbablyCDF(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a CTY bankswitching cartridge
    */
    static bool isProbablyCTY(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a CV bankswitching cartridge
    */
    static bool isProbablyCV(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a CV+ bankswitching cartridge
    */
    static bool isProbablyCVPlus(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a DASH bankswitching cartridge
    */
    static bool isProbablyDASH(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a DF/DFSC bankswitching cartridge
    */
    static bool isProbablyDF(const uInt8* image, uInt32 size, const char*& type);

    /**
      Returns true if the image is probably a DPC+ bankswitching cartridge
    */
    static bool isProbablyDPCplus(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a E0 bankswitching cartridge
    */
    static bool isProbablyE0(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a E7 bankswitching cartridge
    */
    static bool isProbablyE7(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably an EF/EFSC bankswitching cartridge
    */
    static bool isProbablyEF(const uInt8* image, uInt32 size, const char*& type);

    /**
      Returns true if the image is probably an F6 bankswitching cartridge
    */
    static bool isProbablyF6(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably an FA2 bankswitching cartridge
    */
    static bool isProbablyFA2(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably an FE bankswitching cartridge
    */
    static bool isProbablyFE(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a MDM bankswitching cartridge
    */
    static bool isProbablyMDM(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a SB bankswitching cartridge
    */
    static bool isProbablySB(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably a UA bankswitching cartridge
    */
    static bool isProbablyUA(const uInt8* image, uInt32 size);

    /**
      Returns true if the image is probably an X07 bankswitching cartridge
    */
    static bool isProbablyX07(const uInt8* image, uInt32 size);

  protected:
    // Settings class for the application
    const Settings& mySettings;

    // The startup bank to use (where to look for the reset vector address)
    uInt16 myStartBank;

    // Indicates if the bank has changed somehow (a bankswitch has occurred)
    bool myBankChanged;

    // The array containing information about every byte of ROM indicating
    // whether it is used as code.
    BytePtr myCodeAccessBase;

  private:
    // If myBankLocked is true, ignore attempts at bankswitching. This is used
    // by the debugger, when disassembling/dumping ROM.
    bool myBankLocked;

    // Contains info about this cartridge in string format
    static string myAboutString;

    // Following constructors and assignment operators not supported
    Cartridge() = delete;
    Cartridge(const Cartridge&) = delete;
    Cartridge(Cartridge&&) = delete;
    Cartridge& operator=(const Cartridge&) = delete;
    Cartridge& operator=(Cartridge&&) = delete;
};

#endif
