/* Mednafen - Multi-system Emulator
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// I could find no other commands than 'R', 'W', and 'S' (not sure what 'S' is for, however)

#include "../psx.h"
#include "../frontio.h"
#include "memcard.h"

class InputDevice_Memcard : public InputDevice
{
   public:

      InputDevice_Memcard();
      virtual ~InputDevice_Memcard();

      virtual void Power(void);
      virtual int StateAction(StateMem* sm, int load, int data_only, const char* section_name);

      //
      //
      //
      virtual void SetDTR(bool new_dtr);
      virtual bool GetDSR(void);
      virtual bool Clock(bool TxD, int32 &dsr_pulse_delay);

      //
      //
      virtual uint8 *GetNVData(void);
      virtual uint32 GetNVSize(void);
      virtual void ReadNV(uint8 *buffer, uint32 offset, uint32 size);
      virtual void WriteNV(const uint8 *buffer, uint32 offset, uint32 size);

      virtual uint64 GetNVDirtyCount(void);
      virtual void ResetNVDirtyCount(void);

      void Format(void);

   private:

      bool presence_new;

      uint8 card_data[1 << 17];
      uint8 rw_buffer[128];
      uint8 write_xor;

      //
      // Used to avoid saving unused memory cards' card data in save states.
      // Set to false on object initialization, set to true when data is written to card_data that differs
      // from existing data(either from loading a memory card saved to disk, or from a game writing to the memory card).
      //
      // Save and load its state to/from save states.
      //
      bool data_used;

      //
      // Do not save dirty_count in save states!
      //
      uint64 dirty_count;

      bool dtr;
      int32 command_phase;
      uint32 bitpos;
      uint8 receive_buffer;

      uint8 command;
      uint16 addr;
      uint8 calced_xor;

      uint8 transmit_buffer;
      uint32 transmit_count;
};

void InputDevice_Memcard::Format(void)
{
   memset(card_data, 0x00, sizeof(card_data));

   card_data[0x00] = 0x4D;
   card_data[0x01] = 0x43;
   card_data[0x7F] = 0x0E;

   for(unsigned int A = 0x80; A < 0x800; A += 0x80)
   {
      card_data[A + 0x00] = 0xA0;
      card_data[A + 0x08] = 0xFF;
      card_data[A + 0x09] = 0xFF;
      card_data[A + 0x7F] = 0xA0;
   }

   for(unsigned int A = 0x0800; A < 0x1200; A += 0x80)
   {
      card_data[A + 0x00] = 0xFF;
      card_data[A + 0x01] = 0xFF;
      card_data[A + 0x02] = 0xFF;
      card_data[A + 0x03] = 0xFF;
      card_data[A + 0x08] = 0xFF;
      card_data[A + 0x09] = 0xFF;
   }
}

InputDevice_Memcard::InputDevice_Memcard()
{
   Power();

   data_used = false;
   dirty_count = 0;

   // Init memcard as formatted.
   assert(sizeof(card_data) == (1 << 17));
   Format();
}

InputDevice_Memcard::~InputDevice_Memcard()
{

}

void InputDevice_Memcard::Power(void)
{
   presence_new = true;
   memset(rw_buffer, 0, sizeof(rw_buffer));
   write_xor = 0;

   dtr = 0;
   command_phase = 0;

   bitpos = 0;

   receive_buffer = 0;

   command = 0;
   addr = 0;
   calced_xor = 0;

   transmit_buffer = 0;

   transmit_count = 0;
}

int InputDevice_Memcard::StateAction(StateMem* sm, int load, int data_only, const char* section_name)
{
   // Don't save dirty_count.
   SFORMAT StateRegs[] =
   {
      SFVAR(presence_new),

      SFARRAY(rw_buffer, sizeof(rw_buffer)),
      SFVAR(write_xor),

      SFVAR(dtr),
      SFVAR(command_phase),
      SFVAR(bitpos),
      SFVAR(receive_buffer),

      SFVAR(command),
      SFVAR(addr),
      SFVAR(calced_xor),

      SFVAR(transmit_buffer),
      SFVAR(transmit_count),

      SFVAR(data_used),

      SFEND
   };

   SFORMAT CD_StateRegs[] =
   {
      SFARRAY(card_data, sizeof(card_data)),
      SFEND
   };
   int ret = 1;

   if(MDFNSS_StateAction(sm, load, data_only, StateRegs, section_name) != 0)
   {
      //printf("%s data_used=%d\n", section_name, data_used);
      if(data_used)
      {
         std::string tmp_name = std::string(section_name) + "_DT";

         ret &= MDFNSS_StateAction(sm, load, data_only, CD_StateRegs, tmp_name.c_str());
      }

      if(load)
      {
         if(data_used)
            dirty_count++;
      }
   }
   else
      ret = 0;

   return(ret);
}

void InputDevice_Memcard::SetDTR(bool new_dtr)
{
   if(!dtr && new_dtr)
   {
      command_phase = 0;
      bitpos = 0;
      transmit_count = 0;
   }
   else if(dtr && !new_dtr)
   {
      if(command_phase > 0)
         PSX_WARNING("[MCR] Communication aborted on phase %d", command_phase);
   }
   dtr = new_dtr;
}

bool InputDevice_Memcard::GetDSR(void)
{
   if(!dtr)
      return(0);

   if(!bitpos && transmit_count)
      return(1);

   return(0);
}

bool InputDevice_Memcard::Clock(bool TxD, int32 &dsr_pulse_delay)
{
   bool ret = 1;

   dsr_pulse_delay = 0;

   if(!dtr)
      return(1);

   if(transmit_count)
      ret = (transmit_buffer >> bitpos) & 1;

   receive_buffer &= ~(1 << bitpos);
   receive_buffer |= TxD << bitpos;
   bitpos = (bitpos + 1) & 0x7;

   if(!bitpos)
   {
      //if(command_phase > 0 || transmit_count)
      // printf("[MCRDATA] Received_data=0x%02x, Sent_data=0x%02x\n", receive_buffer, transmit_buffer);

      if(transmit_count)
      {
         transmit_count--;
      }

      if (command_phase >= 1024 && command_phase <= 1151)
      {
         // Transmit actual 128 bytes data
         transmit_buffer = card_data[(addr << 7) + (command_phase - 1024)];
         calced_xor ^= transmit_buffer;
         transmit_count = 1;
         command_phase++;
      }
      else if (command_phase >= 2048 && command_phase <= 2175)
      {
         calced_xor ^= receive_buffer;
         rw_buffer[command_phase - 2048] = receive_buffer;

         transmit_buffer = receive_buffer;
         transmit_count = 1;
         command_phase++;
      }
      else
         switch(command_phase)
         {
            case 0:
               if(receive_buffer != 0x81)
                  command_phase = -1;
               else
               {
                  //printf("[MCR] Device selected\n");
                  transmit_buffer = presence_new ? 0x08 : 0x00;
                  transmit_count = 1;
                  command_phase++;
               }
               break;

            case 1:
               command = receive_buffer;
               //printf("[MCR] Command received: %c\n", command);
               if(command == 'R' || command == 'W')
               {
                  command_phase++;
                  transmit_buffer = 0x5A;
                  transmit_count = 1;
               }
               else
               {
                  if(command == 'S')
                  {
                     PSX_WARNING("[MCR] Memcard S command unsupported.");
                  }

                  command_phase = -1;
                  transmit_buffer = 0;
                  transmit_count = 0;
               }
               break;

            case 2:
               transmit_buffer = 0x5D;
               transmit_count = 1;
               command_phase++;
               break;

            case 3:
               transmit_buffer = 0x00;
               transmit_count = 1;
               if(command == 'R')
                  command_phase = 1000;
               else if(command == 'W')
                  command_phase = 2000;
               break;

               //
               // Read
               //
            case 1000:
               addr = receive_buffer << 8;
               transmit_buffer = receive_buffer;
               transmit_count = 1;
               command_phase++;
               break;

            case 1001:
               addr |= receive_buffer & 0xFF;
               transmit_buffer = '\\';
               transmit_count = 1;
               command_phase++;
               break;

            case 1002:
               //printf("[MCR]   READ ADDR=0x%04x\n", addr);
               if(addr >= (sizeof(card_data) >> 7))
                  addr = 0xFFFF;

               calced_xor = 0;
               transmit_buffer = ']';
               transmit_count = 1;
               command_phase++;

               // TODO: enable this code(or something like it) when CPU instruction timing is a bit better.
               //
               //dsr_pulse_delay = 32000;
               //goto SkipDPD;
               //

               break;

            case 1003:
               transmit_buffer = addr >> 8;
               calced_xor ^= transmit_buffer;
               transmit_count = 1;
               command_phase++;
               break;

            case 1004:
               transmit_buffer = addr & 0xFF;
               calced_xor ^= transmit_buffer;

               if(addr == 0xFFFF)
               {
                  transmit_count = 1;
                  command_phase = -1;
               }
               else
               {
                  transmit_count = 1;
                  command_phase = 1024;
               }
               break;



               // XOR
            case (1024 + 128):
               transmit_buffer = calced_xor;
               transmit_count = 1;
               command_phase++;
               break;

               // End flag
            case (1024 + 129):
               transmit_buffer = 'G';
               transmit_count = 1;
               command_phase = -1;
               break;

               //
               // Write
               //
            case 2000:
               calced_xor = receive_buffer;
               addr = receive_buffer << 8;
               transmit_buffer = receive_buffer;
               transmit_count = 1;
               command_phase++;
               break;

            case 2001:
               calced_xor ^= receive_buffer;
               addr |= receive_buffer & 0xFF;
               //printf("[MCR]   WRITE ADDR=0x%04x\n", addr);
               transmit_buffer = receive_buffer;
               transmit_count = 1;
               command_phase = 2048;
               break;
            case (2048 + 128):	// XOR
               write_xor = receive_buffer;
               transmit_buffer = '\\';
               transmit_count = 1;
               command_phase++;
               break;

            case (2048 + 129):
               transmit_buffer = ']';
               transmit_count = 1;
               command_phase++;
               break;

            case (2048 + 130):	// End flag
               //printf("[MCR] Write End.  Actual_XOR=0x%02x, CW_XOR=0x%02x\n", calced_xor, write_xor);

               if(calced_xor != write_xor)
                  transmit_buffer = 'N';
               else if(addr >= (sizeof(card_data) >> 7))
                  transmit_buffer = 0xFF;
               else
               {
                  transmit_buffer = 'G';
                  presence_new = false;

                  // If the current data is different from the data to be written, increment the dirty count.
                  // memcpy()'ing over to card_data is also conditionalized here for a slight optimization.
                  if(memcmp(&card_data[addr << 7], rw_buffer, 128))
                  {
                     memcpy(&card_data[addr << 7], rw_buffer, 128);
                     dirty_count++;
                     data_used = true;
                  }
               }

               transmit_count = 1;
               command_phase = -1;
               break;

         }

      //if(command_phase != -1 || transmit_count)
      // printf("[MCR] Receive: 0x%02x, Send: 0x%02x -- %d\n", receive_buffer, transmit_buffer, command_phase);
   }

   if(!bitpos && transmit_count)
      dsr_pulse_delay = 0x100;

   //SkipDPD: ;

   return(ret);
}

uint8 *InputDevice_Memcard::GetNVData(void)
{
   return card_data;
}

uint32 InputDevice_Memcard::GetNVSize(void)
{
   return(sizeof(card_data));
}

void InputDevice_Memcard::ReadNV(uint8 *buffer, uint32 offset, uint32 size)
{
   while(size--)
   {
      *buffer = card_data[offset & (sizeof(card_data) - 1)];
      buffer++;
      offset++;
   }
}

void InputDevice_Memcard::WriteNV(const uint8 *buffer, uint32 offset, uint32 size)
{
   if(size)
   {
      dirty_count++;
   }

   while(size--)
   {
      if(card_data[offset & (sizeof(card_data) - 1)] != *buffer)
         data_used = true;

      card_data[offset & (sizeof(card_data) - 1)] = *buffer;
      buffer++;
      offset++;
   }
}

uint64 InputDevice_Memcard::GetNVDirtyCount(void)
{
   return(dirty_count);
}

void InputDevice_Memcard::ResetNVDirtyCount(void)
{
   dirty_count = 0;
}


InputDevice *Device_Memcard_Create(void)
{
   return new InputDevice_Memcard();
}

void Device_Memcard_Power(InputDevice *device)
{
   if (InputDevice_Memcard* memcard = dynamic_cast<InputDevice_Memcard*>(device))
      memcard->Power();
}

void Device_Memcard_Format(InputDevice *device)
{
   if (InputDevice_Memcard* memcard = dynamic_cast<InputDevice_Memcard*>(device))
      memcard->Format();
}