// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// 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, 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.

#include <stdlib.h>
#include <memory.h>

#include "CheatSearch.h"

CheatSearchBlock cheatSearchBlocks[4];

CheatSearchData cheatSearchData = {
  0,
  cheatSearchBlocks
};

static bool cheatSearchEQ(u32 a, u32 b)
{
  return a == b;
}

static bool cheatSearchNE(u32 a, u32 b)
{
  return a != b;
}

static bool cheatSearchLT(u32 a, u32 b)
{
  return a < b;
}

static bool cheatSearchLE(u32 a, u32 b)
{
  return a <= b;
}

static bool cheatSearchGT(u32 a, u32 b)
{
  return a > b;
}

static bool cheatSearchGE(u32 a, u32 b)
{
  return a >= b;
}

static bool cheatSearchSignedEQ(s32 a, s32 b)
{
  return a == b;
}

static bool cheatSearchSignedNE(s32 a, s32 b)
{
  return a != b;
}

static bool cheatSearchSignedLT(s32 a, s32 b)
{
  return a < b;
}

static bool cheatSearchSignedLE(s32 a, s32 b)
{
  return a <= b;
}

static bool cheatSearchSignedGT(s32 a, s32 b)
{
  return a > b;
}

static bool cheatSearchSignedGE(s32 a, s32 b)
{
  return a >= b;
}

static bool (*cheatSearchFunc[])(u32,u32) = {
  cheatSearchEQ,
  cheatSearchNE,
  cheatSearchLT,
  cheatSearchLE,
  cheatSearchGT,
  cheatSearchGE
};

static bool (*cheatSearchSignedFunc[])(s32,s32) = {
  cheatSearchSignedEQ,
  cheatSearchSignedNE,
  cheatSearchSignedLT,
  cheatSearchSignedLE,
  cheatSearchSignedGT,
  cheatSearchSignedGE
};

void cheatSearchCleanup(CheatSearchData *cs)
{
  int count = cs->count;

  for(int i = 0; i < count; i++) {
    free(cs->blocks[i].saved);
    free(cs->blocks[i].bits);
  }
  cs->count = 0;
}

void cheatSearchStart(const CheatSearchData *cs)
{
  int count = cs->count;
  
  for(int i = 0; i < count; i++) {
    CheatSearchBlock *block = &cs->blocks[i];

    memset(block->bits, 0xff, block->size >> 3);
    memcpy(block->saved, block->data, block->size);
  }
}

s32 cheatSearchSignedRead(u8 *data, int off, int size)
{
  u32 res = data[off++];

  switch(size) {
  case BITS_8:
    res <<= 24;
    return ((s32)res) >> 24;
  case BITS_16:
    res |= ((u32)data[off++])<<8;
    res <<= 16;
    return ((s32)res) >> 16;
  case BITS_32:
    res |= ((u32)data[off++])<<8;
    res |= ((u32)data[off++])<<16;
    res |= ((u32)data[off++])<<24;
    return (s32)res;
  }
  return (s32)res;
}

u32 cheatSearchRead(u8 *data, int off, int size)
{
  u32 res = data[off++];
  if(size == BITS_16)
    res |= ((u32)data[off++])<<8;
  else if(size == BITS_32) {
    res |= ((u32)data[off++])<<8;
    res |= ((u32)data[off++])<<16;
    res |= ((u32)data[off++])<<24;
  }
  return res;
}

void cheatSearch(const CheatSearchData *cs, int compare, int size, 
                 bool isSigned)
{
  if(compare < 0 || compare > SEARCH_GE)
    return;
  int inc = 1;
  if(size == BITS_16)
    inc = 2;
  else if(size == BITS_32)
    inc = 4;

  if(isSigned) {
    bool (*func)(s32,s32) = cheatSearchSignedFunc[compare];

    for(int i = 0; i < cs->count; i++) {
      CheatSearchBlock *block = &cs->blocks[i];
      int size2 = block->size;
      u8 *bits = block->bits;
      u8 *data = block->data;
      u8 *saved = block->saved;
      
      for(int j = 0; j < size2; j += inc) {
	if(IS_BIT_SET(bits, j)) {
	  s32 a = cheatSearchSignedRead(data, j, size);
	  s32 b = cheatSearchSignedRead(saved,j, size);

	  if(!func(a, b)) {
	    CLEAR_BIT(bits, j);
	    if(size == BITS_16)
	      CLEAR_BIT(bits, j+1);
	    if(size == BITS_32) {
	      CLEAR_BIT(bits, j+2);
	      CLEAR_BIT(bits, j+3);
	    }
	  }
	}
      }
    }
  } else {
    bool (*func)(u32,u32) = cheatSearchFunc[compare];

    for(int i = 0; i < cs->count; i++) {
      CheatSearchBlock *block = &cs->blocks[i];
      int size2 = block->size;
      u8 *bits = block->bits;
      u8 *data = block->data;
      u8 *saved = block->saved;
      
      for(int j = 0; j < size2; j += inc) {
	if(IS_BIT_SET(bits, j)) {
	  u32 a = cheatSearchRead(data, j, size);
	  u32 b = cheatSearchRead(saved,j, size);

	  if(!func(a, b)) {
	    CLEAR_BIT(bits, j);
	    if(size == BITS_16)
	      CLEAR_BIT(bits, j+1);
	    if(size == BITS_32) {
	      CLEAR_BIT(bits, j+2);
	      CLEAR_BIT(bits, j+3);
	    }
	  }
	}
      }
    }
  }
}

void cheatSearchValue(const CheatSearchData *cs, int compare, int size, 
		      bool isSigned, u32 value)
{
  if(compare < 0 || compare > SEARCH_GE)
    return;
  int inc = 1;
  if(size == BITS_16)
    inc = 2;
  else if(size == BITS_32)
    inc = 4;

  if(isSigned) {
    bool (*func)(s32,s32) = cheatSearchSignedFunc[compare];

    for(int i = 0; i < cs->count; i++) {
      CheatSearchBlock *block = &cs->blocks[i];
      int size2 = block->size;
      u8 *bits = block->bits;
      u8 *data = block->data;
      
      for(int j = 0; j < size2; j += inc) {
	if(IS_BIT_SET(bits, j)) {
	  s32 a = cheatSearchSignedRead(data, j, size);
	  s32 b = (s32)value;

	  if(!func(a, b)) {
	    CLEAR_BIT(bits, j);
	    if(size == BITS_16)
	      CLEAR_BIT(bits, j+1);
	    if(size == BITS_32) {
	      CLEAR_BIT(bits, j+2);
	      CLEAR_BIT(bits, j+3);
	    }
	  }
	}
      }
    }
  } else {
    bool (*func)(u32,u32) = cheatSearchFunc[compare];

    for(int i = 0; i < cs->count; i++) {
      CheatSearchBlock *block = &cs->blocks[i];
      int size2 = block->size;
      u8 *bits = block->bits;
      u8 *data = block->data;
      
      for(int j = 0; j < size2; j += inc) {
	if(IS_BIT_SET(bits, j)) {
	  u32 a = cheatSearchRead(data, j, size);

	  if(!func(a, value)) {
	    CLEAR_BIT(bits, j);
	    if(size == BITS_16)
	      CLEAR_BIT(bits, j+1);
	    if(size == BITS_32) {
	      CLEAR_BIT(bits, j+2);
	      CLEAR_BIT(bits, j+3);
	    }
	  }
	}
      }
    }
  }
}

int cheatSearchGetCount(const CheatSearchData *cs, int size) 
{
  int res = 0;
  int inc = 1;
  if(size == BITS_16)
    inc = 2;
  else if(size == BITS_32)
    inc = 4;

  for(int i = 0; i < cs->count; i++) {
    CheatSearchBlock *block = &cs->blocks[i];
    
    int size2 = block->size;
    u8 *bits = block->bits;
    for(int j = 0; j < size2; j += inc) {
      if(IS_BIT_SET(bits, j))
	res++;
    }
  }
  return res;
}

void cheatSearchUpdateValues(const CheatSearchData *cs)
{
  for(int i = 0; i < cs->count; i++) {
    CheatSearchBlock *block = &cs->blocks[i];

    memcpy(block->saved, block->data, block->size);
  }
}

