/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  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 2 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, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */


#ifndef DOSBOX_INOUT_H
#define DOSBOX_INOUT_H

#include <stdio.h>
#include <stdint.h>

using io_port_t = uint16_t; // DOS only supports 16-bit port addresses

#define IO_MAX (64*1024+3)

#define IO_MB	0x1
#define IO_MW	0x2
#define IO_MD	0x4
#define IO_MA	(IO_MB | IO_MW | IO_MD )

class IO_CalloutObject;

typedef Bitu IO_ReadHandler(Bitu port,Bitu iolen);
typedef void IO_WriteHandler(Bitu port,Bitu val,Bitu iolen);

typedef IO_ReadHandler* (IO_ReadCalloutHandler)(IO_CalloutObject &co,Bitu port,Bitu iolen);
typedef IO_WriteHandler* (IO_WriteCalloutHandler)(IO_CalloutObject &co,Bitu port,Bitu iolen);

extern IO_WriteHandler * io_writehandlers[3][IO_MAX];
extern IO_ReadHandler * io_readhandlers[3][IO_MAX];

void IO_RegisterReadHandler(Bitu port,IO_ReadHandler * handler,Bitu mask,Bitu range=1);
void IO_RegisterWriteHandler(Bitu port,IO_WriteHandler * handler,Bitu mask,Bitu range=1);

void IO_FreeReadHandler(Bitu port,Bitu mask,Bitu range=1);
void IO_FreeWriteHandler(Bitu port,Bitu mask,Bitu range=1);

void IO_InvalidateCachedHandler(Bitu port,Bitu range=1);

void IO_WriteB(Bitu port,uint8_t val);
void IO_WriteW(Bitu port,uint16_t val);
void IO_WriteD(Bitu port,uint32_t val);

uint8_t IO_ReadB(Bitu port);
uint16_t IO_ReadW(Bitu port);
uint32_t IO_ReadD(Bitu port);

static const Bitu IOMASK_ISA_10BIT = 0x3FFU; /* ISA 10-bit decode */
static const Bitu IOMASK_ISA_12BIT = 0xFFFU; /* ISA 12-bit decode */
static const Bitu IOMASK_FULL = 0xFFFFU; /* full 16-bit decode */

/* WARNING: Will only produce a correct result if 'x' is a nonzero power of two.
 * For use with IOMASK_Combine.
 *
 * A device with 16 I/O ports would produce a range mask of:
 *
 *       ~(16 - 1) = ~15 = ~0xF = 0xFFFFFFF0
 *
 *       or
 *
 *       ~(0x10 - 1) = ~0xF = 0xFFFFFFF0
 *
 */
static inline constexpr Bitu IOMASK_Range(const Bitu x) {
    return ~((Bitu)x - (Bitu)1U);
}

/* combine range mask with IOMASK value.
 *
 * Example: Sound Blaster 10-bit decode with 16 I/O ports:
 *
 *     IOMASK_Combine(IOMASK_ISA_10BIT,IOMASK_Range(16));
 *
 */
static inline constexpr Bitu IOMASK_Combine(const Bitu a,const Bitu b) {
    return a & b;
}

/* Classes to manage the IO objects created by the various devices.
 * The io objects will remove itself on destruction.*/
class IO_Base{
protected:
	bool installed = false;
	Bitu m_port, m_mask/*IO_MB, etc.*/, m_range/*number of ports*/;
public:
	IO_Base() : m_port(0), m_mask(0), m_range(0) {};
};
/* NTS: To explain the Install() method, the caller not only provides the IOMASK_.. value, but ANDs
 *      the least significant bits to define the range of I/O ports to respond to. An ISA Sound Blaster
 *      for example would set portmask = (IOMASK_ISA_10BIT & (~0xF)) in order to respond to 220h-22Fh,
 *      240h-24Fh, etc. At I/O callout time, the callout object is tested
 *      if (cpu_ioport & io_mask) == (m_port & io_mask)
 *
 *      This does not prevent emulation of devices that start on non-aligned ports or strange port ranges,
 *      because the callout handler is free to decline the I/O request, leading the callout process to
 *      move on to the next device or mark the I/O port as empty. */
class IO_CalloutObject: private IO_Base {
public:
    IO_CalloutObject() : IO_Base() {};
    void InvalidateCachedHandlers(void);
	void Install(Bitu port,Bitu portmask/*IOMASK_ISA_10BIT, etc.*/,IO_ReadCalloutHandler *r_handler,IO_WriteCalloutHandler *w_handler);
	void Uninstall();
public:
    uint16_t io_mask = 0xFFFF;
    uint16_t range_mask = 0;
    uint16_t alias_mask = 0xFFFF;
    unsigned int getcounter = 0;
    IO_ReadCalloutHandler *m_r_handler = NULL;
    IO_WriteCalloutHandler *m_w_handler = NULL;
    bool alloc = false;
public:
    inline bool MatchPort(const uint16_t p) {
        /* (p & io_mask) == (m_port & io_mask) but this also works.
         * apparently modern x86 processors are faster at addition/subtraction than bitmasking.
         * for this to work, m_port must be a multiple of the I/O range. For example, if the I/O
         * range is 16 ports, then m_port must be a multiple of 16. */
        return ((p - m_port) & io_mask) == 0;
    }
    inline bool isInstalled(void) {
        return installed;
    }
};
class IO_ReadHandleObject: private IO_Base {
public:
    IO_ReadHandleObject() : IO_Base() {};
	void Install(Bitu port,IO_ReadHandler * handler,Bitu mask,Bitu range=1);
	void Uninstall();
	~IO_ReadHandleObject();
};
class IO_WriteHandleObject: private IO_Base{
public:
    IO_WriteHandleObject() : IO_Base() {};
	void Install(Bitu port,IO_WriteHandler * handler,Bitu mask,Bitu range=1);
	void Uninstall();
	~IO_WriteHandleObject();
};

static INLINE void IO_Write(Bitu port,uint8_t val) {
	IO_WriteB(port,val);
}
static INLINE uint8_t IO_Read(Bitu port){
	return IO_ReadB(port);
}

enum IO_Type_t {
    IO_TYPE_NONE=0,
    IO_TYPE_MIN=1,
    IO_TYPE_ISA=1,
    IO_TYPE_PCI,
    IO_TYPE_MB,

    IO_TYPE_MAX
};

void IO_InitCallouts(void);

typedef uint32_t IO_Callout_t;

static inline constexpr uint32_t IO_Callout_t_comb(const enum IO_Type_t t,const uint32_t idx) {
    return ((uint32_t)t << (uint32_t)28) + idx;
}

static inline constexpr enum IO_Type_t IO_Callout_t_type(const IO_Callout_t t) {
    return (enum IO_Type_t)(t >> 28);
}

static inline constexpr uint32_t IO_Callout_t_index(const IO_Callout_t t) {
    return t & (((uint32_t)1 << (uint32_t)28) - (uint32_t)1);
}

static const IO_Callout_t IO_Callout_t_none = (IO_Callout_t)0;

IO_Callout_t IO_AllocateCallout(IO_Type_t t);
void IO_FreeCallout(IO_Callout_t c);
IO_CalloutObject *IO_GetCallout(IO_Callout_t c);
void IO_PutCallout(IO_CalloutObject *obj);

#endif
