/**
 ** Supermodel
 ** A Sega Model 3 Arcade Emulator.
 ** Copyright 2011 Bart Trzynadlowski, Nik Henson 
 **
 ** This file is part of Supermodel.
 **
 ** Supermodel 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.
 **
 ** Supermodel 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 Supermodel.  If not, see <http://www.gnu.org/licenses/>.
 **/
 
/*
 * DriveBoard.h
 *
 * Header for the CDriveBoard (force feedback emulation) class.
 */

#ifndef INCLUDED_DRIVEBOARD_H
#define INCLUDED_DRIVEBOARD_H

#include "Util/NewConfig.h"

/*
 * CDriveBoard
 */
class CDriveBoard : public IBus
{
public:
  /*
   * IsAttached(void):
   *
   * Returns:
   *    True if the drive board is "attached" and should be emulated,
   *    otherwise false.
   */
  bool IsAttached(void);

  /*
   * IsSimulated(void):
   *
   * Returns:
   *    True if the drive board is being simulated rather than actually
   *    emulated, otherwise false.
   */
  bool IsSimulated(void);

  /*
   * GetDIPSwitches(dip1, dip2):
   *
   * Reads the two sets of DIP switches on the drive board.
   *
   * Parameters:
   *    dip1  Reference of variable to store DIP switch 1 to.
   *    dip2  DIP switch 2.
   */
  void GetDIPSwitches(UINT8 &dip1, UINT8 &dip2);

  /*
   * SetDIPSwitches(dip1, dip2):
   *
   * Sets the DIP switches.
   *
   * Parameters:
   *    dip1  DIP switch 1 value.
   *    dip2  DIP switch 2 value.
   */
  void SetDIPSwitches(UINT8 dip1, UINT8 dip2);

  /*
   * GetSteeringStrength(void):
   *
   * Returns:
   *    Strength of the steering based on drive board DIP switches (1-8).
   */
  unsigned GetSteeringStrength(void);

  /*
   * SetSteeringStrength(steeringStrength):
   *
   * Sets the steering strength (modifies the DIP switch setting).
   *
   * Parameters:
   *    steeringStrength  A value ranging from 1 to 8.
   */
  void SetSteeringStrength(unsigned steeringStrength);

  /*
   * Get7SegDisplays(seg1Digit1, seg1Digit2, seg2Digit1, seg2Digit2):
   *
   * Reads the 7-segment displays.
   *
   * Parameters:
   *    seg1Digit1  Reference of variable to store digit 1 of the first 7-
   *          segment display to.
   *    seg1Digit2  First display, second digit.
   *    seg2Digit1  Second display, first digit.
   *    seg2Digit2  Second display, second digit.
   */
  void Get7SegDisplays(UINT8 &seg1Digit, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2);

  /*
   * GetZ80(void):
   *
   * Returns:
   *    The Z80 object.
   */
  CZ80 *GetZ80(void);

  /*
   * SaveState(SaveState):
   *
   * Saves the drive board state.
   *
   * Parameters:
   *    SaveState Block file to save state information to.
   */
  void SaveState(CBlockFile *SaveState);

  /*
   * LoadState(SaveState):
   *
   * Restores the drive board state.
   *
   * Parameters:
   *    SaveState Block file to load save state information from.
   */
  void LoadState(CBlockFile *SaveState);

  /*
   * Init(romPtr):
   *
   * Initializes (and "attaches") the drive board. This should be called
   * before other members.
   *
   * Parameters:
   *    romPtr    Pointer to the drive board ROM (Z80 program). If this
   *          is NULL, then the drive board will not be emulated.
   *
   * Returns:
   *    FAIL if the drive board could not be initialized (prints own error
   *    message), otherwise OKAY. If the drive board is not attached
   *    because no ROM was passed to it, no error is generated and the
   *    drive board is silently disabled (detached).
   */
  bool Init(const UINT8 *romPtr);

  /*
   * AttachInputs(InputsPtr, gameInputFlags):
   *
   * Attaches inputs to the drive board (for access to the steering wheel 
   * position).
   *
   * Parameters:
   *    inputs      Pointer to the input object.
   *    gameInputFlags  The current game's input flags.
   */
  void AttachInputs(CInputs *inputs, unsigned gameInputFlags);

  void AttachOutputs(COutputs *outputs);

  /*
   * Reset(void):
   *
   * Resets the drive board.
   */
  void Reset(void);

  /*
   * Read():
   *
   * Reads data from the drive board.
   *
   * Returns:
   *    Data read.
   */
  UINT8 Read(void);

  /*
   * Write(data):
   *
   * Writes data to the drive board.
   *
   * Parameters:
   *    data  Data to send.
   */
  void Write(UINT8 data);

  /*
   * RunFrame(void):
   *
   * Emulates a single frame's worth of time on the drive board.
   */
  void RunFrame(void);

  /*
   * CDriveBoard(config):
   * ~CDriveBoard():
   *
   * Constructor and destructor. Memory is freed by destructor.
   *
   * Paramters:
   *    config  Run-time configuration. The reference should be held because
   *            this changes at run-time.
   */
  CDriveBoard(const Util::Config::Node &config);
  ~CDriveBoard(void);

  /*
   * Read8(addr):
   * IORead8(portNum):
   *
   * Methods for reading from Z80's memory and IO space. Required by CBus.
   *
   * Parameters:
   *    addr    Address in memory (0-0xFFFF).
   *    portNum   Port address (0-255).
   *
   * Returns:
   *    A byte of data from the address or port.
   */
  UINT8 Read8(UINT32 addr);
  UINT8 IORead8(UINT32 portNum);
  
  /*
   * Write8(addr, data):
   * IORead8(portNum, data):
   *
   * Methods for writing to Z80's memory and IO space. Required by CBus.
   *
   * Parameters:
   *    addr    Address in memory (0-0xFFFF).
   *    portNum   Port address (0-255).
   *    data    Byte to write.
   */
  void Write8(UINT32 addr, UINT8 data);
  void IOWrite8(UINT32 portNum, UINT8 data);
  
private:
  const Util::Config::Node &m_config;
  bool m_attached;    // True if drive board is attached
  bool m_tmpDisabled;     // True if temporarily disabled by loading an incompatible save state
  bool m_simulated;       // True if drive board should be simulated rather than emulated

  UINT8 m_dip1;       // Value of DIP switch 1
  UINT8 m_dip2;         // Value of DIP switch 2

  const UINT8* m_rom;     // 32k ROM 
  UINT8* m_ram;           // 8k RAM

  CZ80 m_z80;             // Z80 CPU @ 4MHz

  CInputs *m_inputs;      
  unsigned m_inputFlags;

  COutputs *m_outputs;
  
  // Emulation state
  bool m_initialized;     // True if drive board has finished initialization
  bool m_allowInterrupts; // True if drive board has enabled NMI interrupts

  UINT8 m_seg1Digit1;   // Current value of left digit on 7-segment display 1
  UINT8 m_seg1Digit2;   // Current value of right digit on 7-segment display 1
  UINT8 m_seg2Digit1;   // Current value of left digit on 7-segment display 2
  UINT8 m_seg2Digit2;     // Current value of right digit on 7-segment display 2

  UINT8 m_dataSent;       // Last command sent by main board
  UINT8 m_dataReceived;   // Data to send back to main board

  UINT16 m_adcPortRead;   // ADC port currently reading from
  UINT8 m_adcPortBit;     // Bit number currently reading on ADC port

  UINT8 m_port42Out;      // Last value sent to Z80 I/O port 42 (encoder motor data)
  UINT8 m_port46Out;      // Last value sent to Z80 I/O port 46 (encoder motor control)

  UINT8 m_prev42Out;      // Previous value sent to Z80 I/O port 42
  UINT8 m_prev46Out;      // Previous value sent to Z80 I/O port 46

  UINT8 m_uncenterVal1;   // First part of pending uncenter command
  UINT8 m_uncenterVal2;   // Second part of pending uncenter command

  // Simulation state
  UINT8 m_initState;
  UINT8 m_statusFlags;
  UINT8 m_boardMode;
  UINT8 m_readMode;
  UINT8 m_wheelCenter;
  UINT8 m_cockpitCenter;
  UINT8 m_echoVal;

  // Feedback state
  INT8 m_lastConstForce;  // Last constant force command sent
  UINT8 m_lastSelfCenter; // Last self center command sent
  UINT8 m_lastFriction;   // Last friction command sent
  UINT8 m_lastVibrate;    // Last vibrate command sent

  UINT8 SimulateRead(void);

  void SimulateWrite(UINT8 data);

  void SimulateFrame(void);

  void EmulateFrame(void);

  void ProcessEncoderCmd(void);

  void SendStopAll(void);

  void SendConstantForce(INT8 val);

  void SendSelfCenter(UINT8 val);

  void SendFriction(UINT8 val);

  void SendVibrate(UINT8 val);
};

#endif  // INCLUDED_DRIVEBOARD_H