// Copyright 2014 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <memory>

#include "Common/CommonTypes.h"

// All the templated and very repetitive MMIO-related code is isolated in this
// file for easier reading. It mostly contains code related to handling methods
// (including the declaration of the public functions for creating handling
// method objects), visitors for these handling methods, and interface of the
// handler classes.
//
// This code is very genericized (aka. lots of templates) in order to handle
// u8/u16/u32 with the same code while providing type safety: it is impossible
// to mix code from these types, and the type system enforces it.

namespace Core
{
class System;
}
namespace MMIO
{
class Mapping;

// Read and write handling methods are separated for type safety. On top of
// that, some handling methods require different arguments for reads and writes
// (Complex, for example).
template <typename T>
class ReadHandlingMethod;
template <typename T>
class WriteHandlingMethod;

// Constant: use when the value read on this MMIO is always the same. This is
// only for reads.
template <typename T>
ReadHandlingMethod<T>* Constant(T value);

// Nop: use for writes that shouldn't have any effect and shouldn't log an
// error either.
template <typename T>
WriteHandlingMethod<T>* Nop();

// Direct: use when all the MMIO does is read/write the given value to/from a
// global variable, with an optional mask applied on the read/written value.
template <typename T>
ReadHandlingMethod<T>* DirectRead(const T* addr, u32 mask = 0xFFFFFFFF);
template <typename T>
WriteHandlingMethod<T>* DirectWrite(T* addr, u32 mask = 0xFFFFFFFF);

// Complex: use when no other handling method fits your needs. These allow you
// to directly provide a function that will be called when a read/write needs
// to be done.
template <typename T>
ReadHandlingMethod<T>* ComplexRead(std::function<T(Core::System&, u32)>);
template <typename T>
WriteHandlingMethod<T>* ComplexWrite(std::function<void(Core::System&, u32, T)>);

// Invalid: log an error and return -1 in case of a read. These are the default
// handlers set for all MMIO types.
template <typename T>
ReadHandlingMethod<T>* InvalidRead();
template <typename T>
WriteHandlingMethod<T>* InvalidWrite();

// {Read,Write}To{Smaller,Larger}: these functions are not themselves handling
// methods but will try to combine accesses to two handlers into one new
// handler object.
//
// This is used for example when 32 bit reads have the exact same handling as
// 16 bit. Handlers need to be registered for both 32 and 16, and it would be
// repetitive and unoptimal to require users to write the same handling code in
// both cases. Instead, an MMIO module can simply define all handlers in terms
// of 16 bit reads, then use ReadToSmaller<u32> to convert u32 reads to u16
// reads.
//
// Internally, these size conversion functions have some magic to make the
// combined handlers as fast as possible. For example, if the two underlying
// u16 handlers for a u32 reads are Direct to consecutive memory addresses,
// they can be transformed into a Direct u32 access.
//
// Warning: unlike the other handling methods, *ToSmaller are obviously not
// available for u8, and *ToLarger are not available for u32.
template <typename T>
ReadHandlingMethod<T>* ReadToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr);
template <typename T>
WriteHandlingMethod<T>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, u32 low_part_addr);
template <typename T>
ReadHandlingMethod<T>* ReadToLarger(Mapping* mmio, u32 larger_addr, u32 shift);

// Use these visitors interfaces if you need to write code that performs
// different actions based on the handling method used by a handler. Write your
// visitor implementing that interface, then use handler->VisitHandlingMethod
// to run the proper function.
template <typename T>
class ReadHandlingMethodVisitor
{
public:
  virtual void VisitConstant(T value) = 0;
  virtual void VisitDirect(const T* addr, u32 mask) = 0;
  virtual void VisitComplex(const std::function<T(Core::System&, u32)>* lambda) = 0;
};
template <typename T>
class WriteHandlingMethodVisitor
{
public:
  virtual void VisitNop() = 0;
  virtual void VisitDirect(T* addr, u32 mask) = 0;
  virtual void VisitComplex(const std::function<void(Core::System&, u32, T)>* lambda) = 0;
};

// These classes are INTERNAL. Do not use outside of the MMIO implementation
// code. Unfortunately, because we want to make Read() and Write() fast and
// inlinable, we need to provide some of the implementation of these two
// classes here and can't just use a forward declaration.
template <typename T>
class ReadHandler
{
public:
  ReadHandler();

  // Takes ownership of "method".
  ReadHandler(ReadHandlingMethod<T>* method);

  ~ReadHandler();

  // Entry point for read handling method visitors.
  void Visit(ReadHandlingMethodVisitor<T>& visitor);

  T Read(Core::System& system, u32 addr);

  // Internal method called when changing the internal method object. Its
  // main role is to make sure the read function is updated at the same time.
  void ResetMethod(ReadHandlingMethod<T>* method);

private:
  // Initialize this handler to an invalid handler. Done lazily to avoid
  // useless initialization of thousands of unused handler objects.
  void InitializeInvalid();
  std::unique_ptr<ReadHandlingMethod<T>> m_Method;
  std::function<T(Core::System&, u32)> m_ReadFunc;
};
template <typename T>
class WriteHandler
{
public:
  WriteHandler();

  // Takes ownership of "method".
  WriteHandler(WriteHandlingMethod<T>* method);

  ~WriteHandler();

  // Entry point for write handling method visitors.
  void Visit(WriteHandlingMethodVisitor<T>& visitor);

  void Write(Core::System& system, u32 addr, T val);

  // Internal method called when changing the internal method object. Its
  // main role is to make sure the write function is updated at the same
  // time.
  void ResetMethod(WriteHandlingMethod<T>* method);

private:
  // Initialize this handler to an invalid handler. Done lazily to avoid
  // useless initialization of thousands of unused handler objects.
  void InitializeInvalid();
  std::unique_ptr<WriteHandlingMethod<T>> m_Method;
  std::function<void(Core::System&, u32, T)> m_WriteFunc;
};

// Boilerplate boilerplate boilerplate.
//
// This is used to be able to avoid putting the templates implementation in the
// header files and slow down compilation times. Instead, we declare 3
// specializations in the header file as already implemented in another
// compilation unit: u8, u16, u32.
//
// The "MaybeExtern" is there because that same macro is used for declaration
// (where MaybeExtern = "extern") and definition (MaybeExtern = "").
#define MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, T)                                        \
  MaybeExtern template ReadHandlingMethod<T>* Constant<T>(T value);                                \
  MaybeExtern template WriteHandlingMethod<T>* Nop<T>();                                           \
  MaybeExtern template ReadHandlingMethod<T>* DirectRead(const T* addr, u32 mask);                 \
  MaybeExtern template WriteHandlingMethod<T>* DirectWrite(T* addr, u32 mask);                     \
  MaybeExtern template ReadHandlingMethod<T>* ComplexRead<T>(                                      \
      std::function<T(Core::System&, u32)>);                                                       \
  MaybeExtern template WriteHandlingMethod<T>* ComplexWrite<T>(                                    \
      std::function<void(Core::System&, u32, T)>);                                                 \
  MaybeExtern template ReadHandlingMethod<T>* InvalidRead<T>();                                    \
  MaybeExtern template WriteHandlingMethod<T>* InvalidWrite<T>();                                  \
  MaybeExtern template class ReadHandler<T>;                                                       \
  MaybeExtern template class WriteHandler<T>

#define MMIO_SPECIAL_PUBLIC_SPECIALIZATIONS(MaybeExtern)                                           \
  MaybeExtern template ReadHandlingMethod<u16>* ReadToSmaller(Mapping* mmio, u32 high_part_addr,   \
                                                              u32 low_part_addr);                  \
  MaybeExtern template ReadHandlingMethod<u32>* ReadToSmaller(Mapping* mmio, u32 high_part_addr,   \
                                                              u32 low_part_addr);                  \
  MaybeExtern template WriteHandlingMethod<u16>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, \
                                                                u32 low_part_addr);                \
  MaybeExtern template WriteHandlingMethod<u32>* WriteToSmaller(Mapping* mmio, u32 high_part_addr, \
                                                                u32 low_part_addr);                \
  MaybeExtern template ReadHandlingMethod<u8>* ReadToLarger(Mapping* mmio, u32 larger_addr,        \
                                                            u32 shift);                            \
  MaybeExtern template ReadHandlingMethod<u16>* ReadToLarger(Mapping* mmio, u32 larger_addr,       \
                                                             u32 shift)

#define MMIO_PUBLIC_SPECIALIZATIONS()                                                              \
  MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u8);                                            \
  MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u16);                                           \
  MMIO_GENERIC_PUBLIC_SPECIALIZATIONS(MaybeExtern, u32);                                           \
  MMIO_SPECIAL_PUBLIC_SPECIALIZATIONS(MaybeExtern);

#define MaybeExtern extern
MMIO_PUBLIC_SPECIALIZATIONS()
#undef MaybeExtern
}  // namespace MMIO
