/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and
 *                           Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and
 *                           Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_.
 * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com).
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */
#include <string.h>
#include "snes9x.h"
#include "srtc.h"
#include "memmap.h"

/***   The format of the rtc_data structure is:

Index Description     Range (nibble)
----- --------------  ---------------------------------------

  0   Seconds low     0-9
  1   Seconds high    0-5

  2   Minutes low     0-9
  3   Minutes high    0-5

  4   Hour low        0-9
  5   Hour high       0-2

  6   Day low         0-9
  7   Day high        0-3

  8   Month           1-C (0xC is December, 12th month)

  9   Year ones       0-9
  A   Year tens       0-9
  B   Year High       9-B  (9=19xx, A=20xx, B=21xx)

  C   Day of week     0-6  (0=Sunday, 1=Monday,...,6=Saturday)

***/

SRTC_DATA           rtc;


static int month_keys[12] = { 1, 4, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6 };


/*********************************************************************************************
 *
 * Note, if you are doing a save state for this game:
 *
 * On save:
 *
 *	Call S9xUpdateSrtcTime and save the rtc data structure.
 *
 * On load:
 *
 *	restore the rtc data structure
 *      rtc.system_timestamp = time (NULL);
 *        
 *
 *********************************************************************************************/


void S9xResetSRTC ()
{
    rtc.index = -1;
    rtc.mode = MODE_READ;
}

void S9xHardResetSRTC ()
{
    ZeroMemory (&rtc, sizeof (rtc));
    rtc.index = -1;
    rtc.mode = MODE_READ;
    rtc.count_enable = FALSE;
    rtc.needs_init = TRUE;

    // Get system timestamp
    rtc.system_timestamp = time (NULL);
}

/**********************************************************************************************/
/* S9xSRTCComputeDayOfWeek()                                                                  */
/* Return 0-6 for Sunday-Saturday                                                             */
/**********************************************************************************************/
unsigned int    S9xSRTCComputeDayOfWeek ()
{
    unsigned    year = rtc.data[10]*10 + rtc.data[9];
    unsigned    month = rtc.data[8];
    unsigned    day = rtc.data[7]*10 + rtc.data[6];
    unsigned    day_of_week;

    year += (rtc.data[11] - 9) * 100;

    // Range check the month for valid array indicies
    if ( month > 12 )
        month = 1;

    day_of_week = year + (year / 4) + month_keys[month-1] + day - 1;

    if(( year % 4 == 0 ) && ( month <= 2 ) )
        day_of_week--;

    day_of_week %= 7;

    return day_of_week;
}


/**********************************************************************************************/
/* S9xSRTCDaysInMonth()                                                                       */
/* Return the number of days in a specific month for a certain year                           */
/**********************************************************************************************/
int	S9xSRTCDaysInMmonth( int month, int year )
{
    int		mdays;

    switch ( month )
    {
	case 2:
		if ( ( year % 4 == 0 ) )    // DKJM2 only uses 199x - 22xx
			mdays = 29;
		else
			mdays = 28;
		break;

	case 4:
	case 6:
	case 9:
	case 11:
		mdays = 30;
		break;

	default:	// months 1,3,5,7,8,10,12
		mdays = 31;
		break;
    }

    return mdays;
}


#define DAYTICKS (60*60*24)
#define HOURTICKS (60*60)
#define MINUTETICKS 60


/**********************************************************************************************/
/* S9xUpdateSrtcTime()                                                                        */
/* Advance the  S-RTC time if counting is enabled                                             */
/**********************************************************************************************/
void	S9xUpdateSrtcTime ()
{
	time_t	cur_systime;
	long    time_diff;

    // Keep track of game time by computing the number of seconds that pass on the system
    // clock and adding the same number of seconds to the S-RTC clock structure.
    // I originally tried using mktime and localtime library functions to keep track
    // of time but some of the GNU time functions fail when the year goes to 2099
    // (and maybe less) and this would have caused a bug with DKJM2 so I'm doing
    // it this way to get around that problem.

    // Note: Dai Kaijyu Monogatari II only allows dates in the range 1996-21xx.

    if (rtc.count_enable && !rtc.needs_init)
    {
        cur_systime = time (NULL);

        // This method assumes one time_t clock tick is one second
        //        which should work on PCs and GNU systems.
        //        If your tick interval is different adjust the
	//        DAYTICK, HOURTICK, and MINUTETICK defines

        time_diff = (long) (cur_systime - rtc.system_timestamp);
	rtc.system_timestamp = cur_systime;
        
        if ( time_diff > 0 )
        {
	   int		seconds;
	   int		minutes;
	   int		hours;
	   int		days;
	   int		month;
	   int		year;
	   int		temp_days;

	   int		year_hundreds;
	   int		year_tens;
	   int		year_ones;


	   if ( time_diff > DAYTICKS )
	   {
	       days = time_diff / DAYTICKS;
	       time_diff = time_diff - days * DAYTICKS;
	   }
	   else
	   {
	       days = 0;
	   }

	   if ( time_diff > HOURTICKS )
	   {
	       hours = time_diff / HOURTICKS;
	       time_diff = time_diff - hours * HOURTICKS;
	   }
	   else
	   {
	       hours = 0;
	   }

	   if ( time_diff > MINUTETICKS )
	   {
	       minutes = time_diff / MINUTETICKS;
	       time_diff = time_diff - minutes * MINUTETICKS;
	   }
	   else
	   {
	       minutes = 0;
	   }

	   if ( time_diff > 0 )
	   {
	       seconds = time_diff;
	   }
	   else
	   {
	       seconds = 0;
	   }


	   seconds += (rtc.data[1]*10 + rtc.data[0]);
           if ( seconds >= 60 )
	   {
	       seconds -= 60;
	       minutes += 1;
	   }

	   minutes += (rtc.data[3]*10 + rtc.data[2]);
           if ( minutes >= 60 )
	   {
	       minutes -= 60;
	       hours += 1;
	   }

	   hours += (rtc.data[5]*10 + rtc.data[4]);
           if ( hours >= 24 )
	   {
	       hours -= 24;
	       days += 1;
	   }

	   if ( days > 0 )
	   {
	       year =  rtc.data[10]*10 + rtc.data[9];
	       year += ( 1000 + rtc.data[11] * 100 );

	       month = rtc.data[8];
	       days += (rtc.data[7]*10 + rtc.data[6]);
	       while ( days > (temp_days = S9xSRTCDaysInMmonth( month, year )) )
               {
		    days -= temp_days;
		    month += 1;
		    if ( month > 12 )
		    {
		        year += 1;
		        month = 1;
		    }
	       }

               year_tens = year % 100;
               year_ones = year_tens % 10;
               year_tens /= 10;
               year_hundreds = (year - 1000) / 100;

	       rtc.data[6] = days % 10;
	       rtc.data[7] = days / 10;
	       rtc.data[8] = month;
	       rtc.data[9] = year_ones;
	       rtc.data[10] = year_tens;
	       rtc.data[11] = year_hundreds;
	       rtc.data[12] = S9xSRTCComputeDayOfWeek ();
	   }

	   rtc.data[0] = seconds % 10;
	   rtc.data[1] = seconds / 10;
	   rtc.data[2] = minutes % 10;
	   rtc.data[3] = minutes / 10;
	   rtc.data[4] = hours % 10;
	   rtc.data[5] = hours / 10;

	   return;
        }
    }
}


/**********************************************************************************************/
/* S9xSetSRTC()                                                                               */
/* This function sends data to the S-RTC used in Dai Kaijyu Monogatari II                     */
/**********************************************************************************************/
void S9xSetSRTC (uint8 data, uint16 Address)
{

    data &= 0x0F;	// Data is only 4-bits, mask out unused bits.

    if( data >= 0xD )
    {
        // It's an RTC command

        switch ( data )
        {
            case 0xD:
                rtc.mode = MODE_READ;
                rtc.index = -1;
                break;

            case 0xE:
                rtc.mode = MODE_COMMAND;
                break;

            default:
                // Ignore the write if it's an 0xF ???
	        // Probably should switch back to read mode -- but this
	        //  sequence never occurs in DKJM2
                break;
        }

        return;
    }

    if ( rtc.mode == MODE_LOAD_RTC )
    {
        if ( (rtc.index >= 0) || (rtc.index < MAX_RTC_INDEX) )
        {
            rtc.data[rtc.index++] = data;

            if ( rtc.index == MAX_RTC_INDEX )
            {
                // We have all the data for the RTC load

                rtc.system_timestamp = time (NULL);	// Get local system time

                // Get the day of the week
                rtc.data[rtc.index++] = S9xSRTCComputeDayOfWeek ();

                // Start RTC counting again
                rtc.count_enable = TRUE;
                rtc.needs_init = FALSE;
            }

            return;
        }
        else
        {
            // Attempting to write too much data
            // error(); // ignore??
        }
    }
    else if ( rtc.mode == MODE_COMMAND )
    {
        switch( data )
        {
            case COMMAND_CLEAR_RTC:
                // Disable RTC counter
                rtc.count_enable = FALSE;

                ZeroMemory (rtc.data, MAX_RTC_INDEX+1);
                rtc.index = -1;
                rtc.mode = MODE_COMMAND_DONE;
                break;

            case COMMAND_LOAD_RTC:
                // Disable RTC counter
                rtc.count_enable = FALSE;

                rtc.index = 0;  // Setup for writing
                rtc.mode = MODE_LOAD_RTC;
                break;

            default:
                rtc.mode = MODE_COMMAND_DONE;
                // unrecognized command - need to implement.
        }

        return;
    }
    else
    {
        if ( rtc.mode == MODE_READ )
        {
            // Attempting to write while in read mode. Ignore.
        }

        if ( rtc.mode == MODE_COMMAND_DONE )
        {
            // Maybe this isn't an error.  Maybe we should kick off
            // a new E command.  But is this valid?
        }
    }
}

/**********************************************************************************************/
/* S9xGetSRTC()                                                                               */
/* This function retrieves data from the S-RTC                                                */
/**********************************************************************************************/
uint8 S9xGetSRTC (uint16 Address)
{
    if ( rtc.mode == MODE_READ )
    {
        if ( rtc.index < 0 )
        {
            S9xUpdateSrtcTime ();	// Only update it if the game reads it
            rtc.index++;
            return ( 0x0f );        // Send start marker.
        }
        else if (rtc.index > MAX_RTC_INDEX)
        {
            rtc.index = -1;         // Setup for next set of reads
            return ( 0x0f );        // Data done marker.
        }
        else
        {
            // Feed out the data
            return rtc.data[rtc.index++];
        }
     }
     else
     {
         return 0x0;
     }
}

void S9xSRTCPreSaveState ()
{
    if (Settings.SRTC)
    {
	S9xUpdateSrtcTime ();

	int s = Memory.SRAMSize ?
		(1 << (Memory.SRAMSize + 3)) * 128 : 0;
	if (s > 0x20000)
	    s = 0x20000;

	SRAM [s + 0] = rtc.needs_init;
	SRAM [s + 1] = rtc.count_enable;
	memmove (&SRAM [s + 2], rtc.data, MAX_RTC_INDEX + 1);
	SRAM [s + 3 + MAX_RTC_INDEX] = rtc.index;
	SRAM [s + 4 + MAX_RTC_INDEX] = rtc.mode;

#ifdef LSB_FIRST
	memmove (&SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8);
#else
	SRAM [s + 5  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >>  0);
	SRAM [s + 6  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >>  8);
	SRAM [s + 7  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 16);
	SRAM [s + 8  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 24);
	SRAM [s + 9  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 32);
	SRAM [s + 10 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 40);
	SRAM [s + 11 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 48);
	SRAM [s + 12 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 56);
#endif
    }
}

void S9xSRTCPostLoadState ()
{
    if (Settings.SRTC)
    {
	int s = Memory.SRAMSize ?
		(1 << (Memory.SRAMSize + 3)) * 128 : 0;
	if (s > 0x20000)
	    s = 0x20000;

	rtc.needs_init = SRAM [s + 0];
	rtc.count_enable = SRAM [s + 1];
	memmove (rtc.data, &SRAM [s + 2], MAX_RTC_INDEX + 1);
	rtc.index = SRAM [s + 3 + MAX_RTC_INDEX];
	rtc.mode = SRAM [s + 4 + MAX_RTC_INDEX];

#ifdef LSB_FIRST
	memmove (&rtc.system_timestamp, &SRAM [s + 5 + MAX_RTC_INDEX], 8);
#else
	rtc.system_timestamp |= (SRAM [s +  5 + MAX_RTC_INDEX] <<  0);
	rtc.system_timestamp |= (SRAM [s +  6 + MAX_RTC_INDEX] <<  8);
	rtc.system_timestamp |= (SRAM [s +  7 + MAX_RTC_INDEX] << 16);
	rtc.system_timestamp |= (SRAM [s +  8 + MAX_RTC_INDEX] << 24);
	rtc.system_timestamp |= (SRAM [s +  9 + MAX_RTC_INDEX] << 32);
	rtc.system_timestamp |= (SRAM [s + 10 + MAX_RTC_INDEX] << 40);
	rtc.system_timestamp |= (SRAM [s + 11 + MAX_RTC_INDEX] << 48);
	rtc.system_timestamp |= (SRAM [s + 12 + MAX_RTC_INDEX] << 56);
#endif
	S9xUpdateSrtcTime ();
    }
}
