#include <Carbon/Carbon.h>

#include "MachIPCInterface.h"
#include "ClassicProtocol.h"
#include "HandleBuffer.h"
#include "ContextData.h"
#include "CCacheData.h"
#include "ClassicSupport.h"

std::list <Handle>			CCIClassicSupport::sDiffs;
CCIUInt32				CCIClassicSupport::sFirstSeqNo = 0;
CCIUInt32				CCIClassicSupport::sNextSeqNo = 0;
bool					CCIClassicSupport::sKeepDiffs = false;

bool CCIClassicSupport::KeepDiffs ()
{
    // If we have too many diffs, assume Classic died.
    // We would have a better way to do this if Processes.h weren't in Carbon.framework
    if (sKeepDiffs && (sNextSeqNo - sFirstSeqNo >= 10000)) {
        dprintf ("Turning off diffs because Classic isn't running anymore.\n");
        sKeepDiffs = false;
        RemoveAllDiffs ();
    }
    return sKeepDiffs;
}
		
void CCIClassicSupport::SaveOneDiff (
	Handle		inDiff)
{
    if (KeepDiffs ()) {
        dprintf ("Saving CCache diff with seqno %d.\n", sNextSeqNo);
	sDiffs.push_back (inDiff);
	sNextSeqNo++;
    }
}

void CCIClassicSupport::RemoveLastDiff ()
{
        if (KeepDiffs ()) {
            if (sDiffs.begin () != sDiffs.end ()) {
                dprintf ("Removing one CCache diff at seqno %d\n", sNextSeqNo - 1);
            } else {
                dprintf ("Not removing a CCache diff because there are none.\n");
            }
        } else {
            dprintf ("Not removing a CCache diff because diffs are off.\n");
        }
            
	try {
		sDiffs.pop_back ();
		sNextSeqNo--;
	} catch (...) {
	}
}

void CCIClassicSupport::RemoveDiffsUpTo (
	CCIUInt32		inSeqNo)
{
    if (KeepDiffs ()) {
        if (sFirstSeqNo > inSeqNo) {
            dprintf ("Can't remove diffs below %d because first diff is at %d\n", inSeqNo, sFirstSeqNo);
        } else if (sNextSeqNo < inSeqNo) {
            dprintf ("Can't remove diffs below %d because last diff is at %d\n", inSeqNo, sNextSeqNo);
        } else {
            while (sFirstSeqNo < inSeqNo) {
                DisposeHandle (*(sDiffs.begin ()));
                sDiffs.pop_front ();
                sFirstSeqNo++;
            }
        }
    } else {
        dprintf ("Not removing CCache diffs because none were kept.\n");
    }
}

void CCIClassicSupport::RemoveAllDiffs ()
{
    if (KeepDiffs ()) {
        sDiffs.clear ();
        sFirstSeqNo = sNextSeqNo = 0;
    } else {
        dprintf ("Not removing all CCache diffs because none were kept.\n");
    }
}

Handle CCIClassicSupport::GetAllDiffsSince (
	CCIUInt32	inSeqNo)
{
    if (KeepDiffs ()) {
        if (inSeqNo < sFirstSeqNo) {
            dprintf ("Need to return diffs starting at %d, but the first diff is at %d.\n", inSeqNo, sFirstSeqNo);
            return NULL;
        } else if (inSeqNo > sNextSeqNo) {
            dprintf ("Need to return diffs starting at %d, but the next diff is  %d.\n", inSeqNo, sNextSeqNo);
            return NULL;
        } else {
            UInt32	count = sNextSeqNo - inSeqNo;
            
            CCIHandleBuffer		diffs;
            CCIUInt32	data = getpid ();
            diffs.Put (data);
    
            if (count != 0) {
                dprintf ("Need to return %d diffs starting at %d.\n", count, inSeqNo);
        
                CCIUInt32 seqNo = sFirstSeqNo;
                for (std::list <Handle>::const_iterator i = sDiffs.begin ();
                        i != sDiffs.end ();
                        i++, seqNo++) {
                        
                        if (seqNo >= inSeqNo) {
                                data = ccClassic_DiffCookie;
                                diffs.Put (data);
                                
                                data = seqNo;
                                diffs.Put (data);
                                
                                HLock (*i);
                                diffs.PutData (*(*i), GetHandleSize (*i));
                                HUnlock (*i);
                        }
                }
            }
            
            data = ccClassic_ResponseCookie;
            diffs.Put (data);
            
            Handle allDiffs = diffs.GetHandle ();
            diffs.ReleaseHandle ();
            
            return allDiffs;
        }
    } else {
        dprintf ("Not returning any diffs since none were kept.\n");
        return NULL;
    }
}

Handle CCIClassicSupport::FabricateDiffs ()
{
    CCIUInt32	seqNo = 0;

    CCIContextData*	context = CCIContextDataInterface::GetGlobalContext ();
    std::vector <CCIObjectID>	ccaches;
    context -> GetCCacheIDs (ccaches);
    
    sort (ccaches.begin (), ccaches.end ());
    
    std::vector <CCIObjectID>::iterator	i;
    
    CCIHandleBuffer	diffs;
    
    CCIUInt32	diffType;
    
    CCIUInt32	data = getpid ();
    diffs.Put (data);

    CCIUInt32	diffCookie = ccClassic_DiffCookie;

    for (i = ccaches.begin (); i != ccaches.end (); i++) {
        diffs.Put (diffCookie);
        diffs.Put (seqNo);
        seqNo++;

        diffType = ccClassic_CCache_SkipToID;
        diffs.Put (diffType);
        diffs.Put (*i);
        
        CCICCacheDataInterface	ccache (*i);
        
        diffs.Put (diffCookie);
        diffs.Put (seqNo);
        seqNo++;

        diffType = ccClassic_Context_CreateCCache;
        diffs.Put (diffType);
        diffs.Put (context -> GetGloballyUniqueID ());
        
        CCIUInt32	version = ccache -> GetCredentialsVersion ();
        if ((version & cc_credentials_v4) != 0) {
            diffs.Put (ccache -> GetName ());
            diffs.Put (version & cc_credentials_v4);
            diffs.Put (ccache -> GetPrincipal (cc_credentials_v4));
        } else {
            diffs.Put (ccache -> GetName ());
            diffs.Put (version & cc_credentials_v5);
            diffs.Put (ccache -> GetPrincipal (cc_credentials_v5));
        }
        
        if (version == cc_credentials_v4 | cc_credentials_v5) {
            diffs.Put (diffCookie);
            diffs.Put (seqNo);
            seqNo++;

            diffType = ccClassic_CCache_SetPrincipal;
            diffs.Put (diffType);
            diffs.Put (*i);
            diffs.Put (version & cc_credentials_v5);
            diffs.Put (ccache -> GetPrincipal (cc_credentials_v5));
        }
        
        std::vector <CCIObjectID>	creds;
        ccache -> GetCredentialsIDs (creds);
        
        sort (creds.begin (), creds.end ());
        
        std::vector <CCIObjectID>::iterator j;
        
        for (j = creds.begin (); j != creds.end (); j++) {
            diffs.Put (diffCookie);
            diffs.Put (seqNo);
            seqNo++;

            CCIUInt32 diffType = ccClassic_Credentials_SkipToID;
            diffs.Put (diffType);
            diffs.Put (*j);
            
            CCICredentialsDataInterface	creds (*j);
            
            diffs.Put (diffCookie);
            diffs.Put (seqNo);
            seqNo++;

            diffType = ccClassic_CCache_StoreConvertedCredentials;
            diffs.Put (diffType);
            diffs.Put (*i);
            
            std::strstream		flatCredentials;
            flatCredentials << creds.Get () << std::ends;
            
            diffs.Put (flatCredentials);
            flatCredentials.freeze (false);	// Makes sure the buffer will be deallocated
        }
        
        diffs.Put (diffCookie);
        diffs.Put (seqNo);
        seqNo++;

        diffType = ccClassic_Credentials_SkipToID;
        diffs.Put (diffType);
        diffs.Put (CCIUniqueGlobally <CCICredentialsData>::GetNextGloballyUniqueID ());
    }

    diffs.Put (diffCookie);
    diffs.Put (seqNo);
    seqNo++;

    diffType = ccClassic_CCache_SkipToID;
    diffs.Put (diffType);
    diffs.Put (CCIUniqueGlobally <CCICCacheData>::GetNextGloballyUniqueID ());
    
    Handle diffHandle = diffs.GetHandle ();
    
    CCIUInt32	responseCookie = ccClassic_ResponseCookie;
    diffs.Put (responseCookie);
    CCIUInt32	response = ccNoError;
    diffs.Put (response);

    diffs.ReleaseHandle ();
    
    sKeepDiffs = true;
    
    return diffHandle;
}