/*
 *  ppui/BasicTypes.h
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker 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 Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef BASICTYPES__H
#define BASICTYPES__H

typedef unsigned char	pp_uint8;
typedef signed char		pp_int8;
typedef unsigned short	pp_uint16;
typedef signed short	pp_int16;
typedef unsigned int	pp_uint32;
typedef signed int		pp_int32;

#include "ScanCodes.h"

#if defined(WIN32) || defined(_WIN32_WCE) 
	#include <windows.h>
	#include <stdio.h>
	#define VK_ALT        VK_MENU
	#define __PPUI_WINDOWS__
#endif

#if !defined(__PPUI_WINDOWS__)
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <string.h>
	#include "VirtualKeys.h"
	#include "PPSystemString_POSIX.h"
#endif

#if defined(__PPUI_WINDOWS__) && defined(UNICODE)
	#include "PPSystemString_WIN32.h"
#else
	#define PPSystemString PPString
#endif

#ifdef __GNUC__
	typedef long long		pp_int64;
#else
	typedef __int64			pp_int64;
#endif

#ifndef VK_OEM_3
	#define VK_OEM_3      0xC0
#endif

#ifndef VK_OEM_102
	#define VK_OEM_102    0xC0
#endif

// Little helper macro
#define PPSTR_PERIODS "\xef"

// ------ This has to be defined somewhere ------ 
pp_uint32 PPGetTickCount();

struct PPPoint
{
	pp_int32 x, y;

	PPPoint(pp_int32 theX, pp_int32 theY) :
		x(theX), y(theY)
	{}
	
	PPPoint() 
	{}
};

struct PPSize
{
	pp_int32 width, height;

	PPSize(pp_int32 theWidth, pp_int32 theHeight) :
		width(theWidth), height(theHeight)
	{}
	
	PPSize() 
	{}

	bool operator==(const PPSize& source) const
	{
		return (width == source.width && height == source.height);
	}
	
	bool operator!=(const PPSize& source) const
	{
		return !(width == source.width && height == source.height);
	}
	
	bool match(pp_int32 width, pp_int32 height) const
	{
		return (this->width == width && this->height == height);
	}
};

struct PPRect
{
	pp_int32 x1, y1, x2, y2;

	PPRect(pp_int32 px1, pp_int32 py1, pp_int32 px2, pp_int32 py2) :
		x1(px1), y1(py1), x2(px2), y2(py2)
	{}

	PPRect()
	{}

	pp_int32 width() const { return x2-x1; }
	pp_int32 height() const { return y2-y1; }
	
	void scale(pp_int32 scaleFactor)
	{
		x1 *= scaleFactor;
		y1 *= scaleFactor;
		x2 *= scaleFactor;
		y2 *= scaleFactor;
	}
	
	bool intersect(const PPRect& rc) const
	{
		pp_int32 left1, left2;
		pp_int32 right1, right2;
		pp_int32 top1, top2;
		pp_int32 bottom1, bottom2;
		
		left1 = this->x1;
		left2 = rc.x1;
		right1 = this->x1 + this->width();
		right2 = rc.x1 + rc.width();
		top1 = this->y1;
		top2 = rc.y1;
		bottom1 = this->y1 + this->height();
		bottom2 = rc.y1 + rc.height();
		
		if (bottom1 < top2) return false;
		if (top1 > bottom2) return false;
		
		if (right1 < left2) return false;
		if (left1 > right2) return false;
		
		return true;
	} 
	
};

struct PPColor
{
	pp_int32 r,g,b;

	PPColor(pp_int32 red, pp_int32 green, pp_int32 blue) :
		r(red), g(green), b(blue)
	{}

	PPColor() :
		r(), g(), b()
	{}
	
	void validate()
	{
		if (r > 255) r = 255;
		if (g > 255) g = 255;
		if (b > 255) b = 255;
	}

	void scale(float f) 
	{
		r = (pp_int32)((float)r*f);
		g = (pp_int32)((float)g*f);
		b = (pp_int32)((float)b*f);
		validate();
	}

	void scale(float fr, float fg, float fb) 
	{
		r = (pp_int32)((float)r*fr);
		g = (pp_int32)((float)g*fg);
		b = (pp_int32)((float)b*fb);
		validate();
	}

	void scaleFixed(pp_int32 f) 
	{
		r = (r*f)>>16;
		g = (g*f)>>16;
		b = (b*f)>>16;
		validate();
	}
	
	void interpolateFixed(const PPColor& col, pp_int32 f)
	{
		r = (f*r + col.r*(65536-f)) >> 16;
		g = (f*g + col.g*(65536-f)) >> 16;
		b = (f*b + col.b*(65536-f)) >> 16;
		validate();
	}

	PPColor invert() const
	{
		PPColor c(255-r, 255-g, 255-b);
		return c;
	}

	void set(pp_int32 red, pp_int32 green, pp_int32 blue)
	{
		r = red; g = green; b = blue;
	}
	
	void clamp()
	{
		if (r < 0) r = 0;
		if (g < 0) g = 0;
		if (b < 0) b = 0;

		validate();
	}

	void operator+=(const PPColor& source)
	{
		r+=source.r;
		g+=source.g;
		b+=source.b;
		validate();
	}
	
	bool operator==(const PPColor& source) const
	{
		return (r == source.r && g == source.g && b == source.b);
	}
	
	bool operator!=(const PPColor& source) const
	{
		return !(r == source.r && g == source.g && b == source.b);
	}
};

#ifdef WIN32
#define STRINGCOMPARE_NOCASE(left, right) _stricmp(left, right)
#else
#define STRINGCOMPARE_NOCASE(left, right) strcasecmp(left, right)
#endif

// C-String wrapper
class PPString
{
private:
	char* strBuffer;
	pp_uint32 allocatedSize;

	void reAlloc(pp_uint32 newSize)
	{
		if (newSize <= allocatedSize)
			return;

		char* newStrBuffer = new char[newSize];
		memcpy(newStrBuffer, strBuffer, allocatedSize);

		delete[] strBuffer;
		strBuffer = newStrBuffer;
		allocatedSize = newSize;
	}


public:
	// Empty string
	PPString() :
		strBuffer(new char[8]),
		allocatedSize(8)
	{
		*strBuffer = 0;		
	}

	// String from single character
	PPString(char c) :
		strBuffer(new char[8]),
		allocatedSize(8)
	{
		*strBuffer = c;
		*(strBuffer+1) = 0;
	}

	PPString(const char* str) :
		strBuffer(new char[strlen(str) + 1]),
		allocatedSize((pp_uint32)strlen(str) + 1)		
	{
		strcpy(strBuffer, str);		
	}

	PPString(const char* str, pp_uint32 length) :
		strBuffer(new char[length + 1]),
		allocatedSize(length + 1)
	{
		memcpy(strBuffer, str, length);
		strBuffer[length] = 0;
	}

	// copy c'tor
	PPString(const PPString& str) :
		strBuffer(new char[str.allocatedSize]),
		allocatedSize(str.allocatedSize)
	{
		memcpy(strBuffer, str.strBuffer, str.allocatedSize);
	}

	operator const char*() const
	{
		return strBuffer;
	}

	const char* getStrBuffer() const
	{
		return strBuffer;
	}

	// assignment operator
	PPString& operator=(const PPString& str)
	{
		if (this != &str)
		{
			if (str.allocatedSize <= allocatedSize)
			{
				memcpy(strBuffer, str.strBuffer, str.allocatedSize);
			}
			else
			{
				delete[] strBuffer;			
				strBuffer = new char[str.allocatedSize];
				memcpy(strBuffer, str.strBuffer, str.allocatedSize);
				allocatedSize = str.allocatedSize;
			}
		}
	
		return *this;
	}

	PPString& operator=(const char* str)
	{
		pp_uint32 len = (unsigned)strlen(str)+1;

		if (len <= allocatedSize)
		{
			strcpy(strBuffer, str);
		}
		else
		{
			delete[] strBuffer;
			strBuffer = new char[len];
			strcpy(strBuffer, str);
			allocatedSize = len;
		}
		
		return *this;
	}

	// comparison is necessary too
	bool operator==(const PPString& str) const
	{
		return strcmp(strBuffer, str.strBuffer) == 0;
	}

	bool operator!=(const PPString& str) const
	{
		return strcmp(strBuffer, str.strBuffer) != 0;
	}

	pp_int32 compareTo(const PPString& str) const
	{
		return strcmp(strBuffer, str.strBuffer);
	}

	pp_int32 compareToNoCase(const PPString& str) const
	{
		return STRINGCOMPARE_NOCASE(strBuffer, str.strBuffer);
	}

	bool startsWith(const PPString& str) const
	{
		if (length() < str.length())
			return false;
			
		for (pp_uint32 i = 0; i < str.length(); i++)
			if (strBuffer[i] != str.strBuffer[i])
				return false;
				
		return true;
	}
	
	~PPString()
	{
		delete[] strBuffer;
	}

	pp_uint32 length() const
	{
		return (pp_uint32)strlen(strBuffer);
	}

	char charAt(pp_uint32 index) const
	{
		if (index < length())
			return strBuffer[index];
			
		return 0;
	}

	void insertAt(pp_uint32 i, const PPString& s)
	{

		// doesn't work
		if (i > length())
			return;

		allocatedSize = length() + s.length() + 1;

		char* newStr = new char[allocatedSize];
		
		memcpy(newStr, strBuffer, i);
		memcpy(newStr + i, s.strBuffer, s.length());
		memcpy(newStr + i + s.length(), strBuffer + i, length() - i);
		newStr[length() + s.length()] = 0;

		delete[] strBuffer;

		strBuffer = newStr;
	}

	void append(const PPString& s)
	{
		insertAt(length(), s);
	}

	void deleteAt(pp_uint32 i, pp_uint32 numChars)
	{

		// not possible
		if (i > length())
			return;

		// nothing to delete
		if ((signed)length() - (signed)numChars < 0)
			return;

		// nothing to delete
		if (strBuffer[i] == 0)
			return;

		allocatedSize = length() - numChars + 1;

		char* newStr = new char[allocatedSize];
		
		memcpy(newStr, strBuffer, i);
		memcpy(newStr + i, strBuffer + i + numChars, length() - i - numChars);
		newStr[length() - numChars] = 0;

		delete[] strBuffer;

		strBuffer = newStr;
	}

	PPString subString(pp_uint32 leftIndex, pp_uint32 rightIndex) const
	{
		PPString newString;
		for (pp_uint32 i = leftIndex; i < rightIndex && i < length(); i++)
			newString.append(charAt(i));
		
		return newString;
	}

	void replace(const PPString& str)
	{
		delete[] strBuffer;
		strBuffer = new char[str.allocatedSize];
		memcpy(strBuffer, str.strBuffer, str.allocatedSize);
		allocatedSize = str.allocatedSize;
	}

	void search_replace( const char *old, const char *replace) {
		char *pos = strstr(strBuffer, old); // Find "old" in str
		if (!pos) return; // If not found, return

		size_t old_len = strlen(old);
		size_t new_len = strlen(replace);
		size_t tail_len = strlen(pos + old_len) + 1; // +1 for null terminator

		if (new_len <= old_len) {
			// Case 1: `new` is shorter or same size, overwrite and shift left if needed
			memcpy(pos, replace, new_len);
			memmove(pos + new_len, pos + old_len, tail_len);
		} else {
			// Case 2: `new` is longer, shift right to make space
			// Check if the buffer has enough space
			size_t buffer_len = strlen(strBuffer);
			if (buffer_len + new_len - old_len >= buffer_len) {
				memmove(pos + new_len, pos + old_len, tail_len);
				memcpy(pos, replace, new_len);
			} else {
				// Handle the case when the buffer is not large enough
				printf("Buffer is not large enough to hold the modified string\n");
			}
		}		
	}

	pp_int32 getIntValue() const
	{
		pp_uint32 v; 
		sscanf(strBuffer, "%d", &v);
		return v;
	}
	
	pp_int32 countLines()
	{
		pp_int32 numLines = 1;
		pp_int32 len = this->length();
		for (pp_int32 i = 0; i < len; i++)
		{
			if (strBuffer[i] == '\n' && i != len-1)
				numLines++;
		}
		
		return numLines;
	}

	pp_int32 matches(const char *str) {
		pp_int32 count = 0;
		const char *pos = strBuffer;

		while ((pos = strstr(pos, str)) != NULL) {
			count++;
			pos += strlen(str); // Move past the found occurrence
		}

		return count;
	}


	void toUpper()
	{
		for (pp_uint32 i = 0; i < length(); i++)
			if (strBuffer[i] >= 'a' && 
				strBuffer[i] <= 'z')
				strBuffer[i] -= 'a'-'A';
	}

	void toLower()
	{
		for (pp_uint32 i = 0; i < length(); i++)
			if (strBuffer[i] >= 'A' && 
				strBuffer[i] <= 'Z')
				strBuffer[i] += 'a'-'A';
	}
	

	PPString stripPath() const
	{
		char* ptr = strBuffer+strlen(strBuffer);
		
		while (ptr > strBuffer && *ptr != '/' && *ptr != '\\')
			ptr--;
			
		if (ptr != strBuffer)
			ptr++;
			
		PPString str = ptr;
		return str;
	}

	PPString stripExtension() const
	{
		char* ptr = strBuffer+strlen(strBuffer);
		
		while (ptr > strBuffer && *ptr != '.' && *ptr != '/' && *ptr != '\\')
			ptr--;
		
		if (*ptr == '/' || *ptr == '\\')
			return strBuffer;
			
		if (ptr != strBuffer)
		{	
			PPString str;
		
			delete[] str.strBuffer;
			str.allocatedSize = (pp_uint32)((ptr-strBuffer)+1);
			str.strBuffer = new char[str.allocatedSize];
			memcpy(str.strBuffer, strBuffer, (ptr-strBuffer));
			str.strBuffer[(ptr-strBuffer)] = '\0';
		
			return str;
		}
		else
		{
			return ptr;
		}
	}

	PPString getExtension() const
	{
		char* ptr = strBuffer+strlen(strBuffer);
		
		while (ptr > strBuffer && *ptr != '.' && *ptr != '/' && *ptr != '\\')
			ptr--;
		
		if (*ptr != '.')
			return "";
			
		return ptr;
	}

	pp_int32 compareExtensions(const PPString& str) const	
	{
		char* ptrSrc = strBuffer+strlen(strBuffer);
		
		while (ptrSrc > strBuffer && *ptrSrc != '.' && *ptrSrc != '/')
			ptrSrc--;
		
		bool noExt1 = false;
		
		if (*ptrSrc != '.')
			noExt1 = true;

		ptrSrc++;
		if (*ptrSrc == '\0')
			noExt1 = true;

		char* ptrDst = str.strBuffer+strlen(str.strBuffer);
		
		while (ptrDst > str.strBuffer && *ptrDst != '.' && *ptrDst != '/')
			ptrDst--;
		
		if (*ptrDst != '.')
			return noExt1 ? 0 : 1;

		ptrDst++;
		if (*ptrDst == '\0')
			return noExt1 ? 0 : 1;

		return STRINGCOMPARE_NOCASE(ptrSrc, ptrDst);
	}

	bool compareToExtension(const PPString& extension) const	
	{
		char* ptrSrc = strBuffer+strlen(strBuffer);
		
		while (ptrSrc > strBuffer && *ptrSrc != '.' && *ptrSrc != '/')
			ptrSrc--;
		
		if (*ptrSrc != '.')
			return false;

		ptrSrc++;
		if (*ptrSrc == '\0')
			return false;

		return STRINGCOMPARE_NOCASE(ptrSrc, extension.strBuffer) == 0;
	}

	void ensureTrailingCharacter(char chr)
	{
		pp_uint32 len = length();
		if (len)
		{
			char* ptr = strBuffer+(len-1);
			if (*ptr != chr)
				append(chr);
		}
	}
	
	// Delete this pointer after usage
	char* toASCIIZ() const
	{
		char* newStr = new char[length() + 1];
		strcpy(newStr, strBuffer);
		return newStr;
	}

};

struct Descriptor
{
	PPString extension;
	PPString description;
	
	Descriptor(const PPString& ext, const PPString& desc) :
		extension(ext), description(desc)
	{
	}
	
	Descriptor(const Descriptor& source) :
		extension(source.extension), description(source.description)
	{
	}
	
};


#endif
