/* OpenAmiga.org
 *  This file is part of the OpenAmiga "xfdmaster library project"
 *
 * For more information look at:
 *   http://openamiga.org/?function=viewproject&projectid=67
 */

/****************************************************************************
*   XFD library system for cruncher handling
*   Copyright (C) 1994-1998 by Georg Hörmann
*   Copyright (C) 1998 by Dirk Stöcker <soft@dstoecker.de>
*   Copyright (C) 2014 by Fredrik Wikström <fredrik@a500.org>
*
*   This library is free software; you can redistribute it and/or
*   modify it under the terms of the GNU Lesser General Public
*   License as published by the Free Software Foundation; either
*   version 2.1 of the License, or (at your option) any later version.
*
*   This library 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
*   Lesser General Public License for more details.
*
*   You should have received a copy of the GNU Lesser General Public
*   License along with this library; if not, write to the Free Software
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
****************************************************************************/

#include <interfaces/xfdmaster.h>
#include "../xfdmaster_vectors.h"
#include "../xfdmaster_internal.h"

/****** xfdmaster.library/xfdScanData ******************************************
*
*   NAME
*      xfdScanData -- Description
*
*   SYNOPSIS
*      UWORD xfdScanData(APTR buffer, ULONG length, ULONG * result, 
*          ULONG flags, struct xfdScanHook * scanhook);
*
*   FUNCTION
*
*   INPUTS
*       buffer - 
*       length - 
*       result - 
*       flags - 
*       scanhook - 
*
*   RESULT
*       The result ...
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*
*   SEE ALSO
*
*****************************************************************************
*
*/

UWORD _xfdMaster_xfdScanData(struct xfdMasterIFace *Self, APTR buffer,
	ULONG length, ULONG *result, ULONG flags, struct xfdScanHook *scanhook)
{
	DEBUGF("xfdScanData(0x%08lx, 0x%08lx, %lu, 0x%08lx, 0x%08lx, 0x%08lx)\n",
		Self, buffer, length, result, flags, scanhook);
	struct xfdMasterBase *libBase = (struct xfdMasterBase *)Self->Data.LibBase;
	struct ExecIFace *IExec = libBase->xfdm_IExec;
	struct xfdSlave *slave;
	ULONG numscans;
	ULONG *scanbuf, *scanptr;
	ULONG res;
	UWORD error;
	struct xfdScanNode *scannode;

	*result = (ULONG)NULL;

	if (buffer == NULL || length == 0) {
		return XFDERR_NOSOURCE;
	}

	/*if ((flags & XFDSDF_SCANODD) &&
		!(libBase->xfdm_ExecBase->AttnFlags & AFF_68020))
	{
		return XFDERR_BETTERCPU;
	}*/

	numscans = 0;
	slave = libBase->xfdm_FirstSlave;
	while (slave != NULL) {
		if (slave->xfds_Version <= XFDS_VERSION &&
			slave->xfds_MasterVersion <= libBase->LibNode.lib_Version &&
			(slave->xfds_PackerFlags & XFDPFF_DATA) != 0 &&
			slave->xfds_ScanData != NULL &&
			slave->xfds_VerifyData != NULL &&
			((slave->xfds_PackerFlags & XFDPFF_EXTERN) == 0 ||
			(flags & XFDSDF_USEEXTERN) != 0))
		{
			numscans++;
		}
		slave = slave->xfds_Next;
	}

	if (numscans == 0) {
		return XFDERR_OK;
	}

	scanbuf = IExec->AllocVecTags((numscans + 1) * sizeof(ULONG),
		AVT_Type, MEMF_SHARED,
		AVT_Lock, FALSE,
		AVT_ClearWithValue, 0,
		TAG_END);
	if (scanbuf == NULL) {
		return XFDERR_NOMEMORY;
	}

	scanptr = scanbuf;
	slave = libBase->xfdm_FirstSlave;
	while (slave != NULL) {
		if (slave->xfds_Version <= XFDS_VERSION &&
			slave->xfds_MasterVersion <= libBase->LibNode.lib_Version &&
			(slave->xfds_PackerFlags & XFDPFF_DATA) != 0 &&
			slave->xfds_ScanData != NULL &&
			slave->xfds_VerifyData != NULL &&
			((slave->xfds_PackerFlags & XFDPFF_EXTERN) == 0 ||
			(flags & XFDSDF_USEEXTERN) != 0))
		{
			*scanptr++ = (ULONG)slave;
		}
		slave = slave->xfds_Next;
	}

	while ((LONG)length > 0) {
		scanptr = scanbuf;
		while ((slave = (struct xfdSlave *)*scanptr++) != NULL) {
			if (IExec->IsNative(slave->xfds_ScanData)) {
				xfdScanDataFunc func = (xfdScanDataFunc)slave->xfds_ScanData;
				res = func(Self, buffer, length);
			} else {
				res = IExec->EmulateTags(slave->xfds_ScanData,
					ET_RegisterA0, buffer,
					ET_RegisterD0, length,
					ET_RegisterA6, libBase,
					TAG_END);
			}
			if (res) {
				if (IExec->IsNative(slave->xfds_VerifyData)) {
					xfdVerifyDataFunc func = (xfdVerifyDataFunc)slave->xfds_VerifyData;
					res = func(Self, buffer, length);
				} else {
					res = IExec->EmulateTags(slave->xfds_VerifyData,
						ET_RegisterA0, buffer,
						ET_RegisterD0, length,
						ET_RegisterA6, libBase,
						TAG_END);
				}
				if (res > 0 && res <= length) {
					struct xfdScanNode *lastscan;

					scannode = Self->xfdAllocObject(XFDOBJ_SCANNODE);
					if (scannode == NULL) {
						error = XFDERR_NOMEMORY;
						goto FreeList;
					}

					lastscan = (struct xfdScanNode *)*result;
					if (lastscan == NULL) {
						*result = (ULONG)scannode;
					} else {
						while (lastscan->xfdsn_Next != NULL) {
							lastscan = lastscan->xfdsn_Next;
						}
						lastscan->xfdsn_Next = scannode;
					}

					scannode->xfdsn_Save = buffer;
					scannode->xfdsn_SaveLen = res;
					scannode->xfdsn_PackerName = slave->xfds_PackerName;
					scannode->xfdsn_PackerFlags = slave->xfds_PackerFlags & ~XFDPFF_EXTERN;
					break;
				}
			}
		}
		if (slave != NULL) {
			if (!(flags & XFDSDF_SCANODD)) {
				res = (res + 1) & ~1;
			}
			buffer += res;
			length -= res;
		} else {
			buffer += 1;
			length -= 1;
			if (!(flags & XFDSDF_SCANODD)) {
				buffer += 1;
				length -= 1;
			}
			scannode = NULL;
		}
		if (scanhook != NULL) {
			scanhook->xfdsh_ToDo = ((LONG)length >= 0) ? length : 0;
			scanhook->xfdsh_ScanNode = scannode;
			if (IExec->IsNative(scanhook->xfdsh_Entry)) {
				xfdScanHookFunc func = (xfdScanHookFunc)scanhook->xfdsh_Entry;
				res = func(Self, scanhook);
			} else {
				res = IExec->EmulateTags(scanhook->xfdsh_Entry,
					ET_RegisterA0, scanhook,
					ET_RegisterA6, libBase,
					TAG_END);
			}
			if (!res) {
				error = XFDERR_HOOKBREAK;
				goto FreeBuffer;
			}
		}
	}

	error = XFDERR_OK;
	goto FreeBuffer;

FreeList:
	if ((scannode = (struct xfdScanNode *)*result) != (ULONG)NULL) {
		struct xfdScanNode *nextscan;

		*result = (ULONG)NULL;
		while (scannode != NULL) {
			nextscan = scannode->xfdsn_Next;
			Self->xfdFreeObject(scannode);
			scannode = nextscan;
		}
	}

FreeBuffer:
	IExec->FreeVec(scanbuf);

	return error;
}

