// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   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 of the License, 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
// -------------------------- END HEADER -------------------------------------

#include <cstring>
#include <string>
#include <sstream>
using namespace std;

#include <malloc.h>

#include "string.h"

bool strStartsWith(const std::string &needle, const std::string &haystack) {
	if(haystack.size() < needle.size()) return false;
	return strncmp(needle.c_str(), haystack.c_str(), needle.size()) == 0;
}

bool strEndsWith(const std::string &needle, const std::string &haystack) {
	if(haystack.size() < needle.size()) return false;
	return strncmp(needle.c_str(), haystack.c_str() + (haystack.size() - needle.size()), needle.size()) == 0;
}

bool isWhiteSpace(char c) {
	return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

void stringTokenize(const std::string &str, const std::string &delims, std::vector<std::string> &tokens, bool allowEmpty) {
	
	unsigned int i = 0;
	while(i < str.size()) {
		
		bool firstDelim = true;

		// Skip over delimeters.
		while(i < str.size() && strchr(delims.c_str(), str[i])) {
			i++;
			
			if(!firstDelim && allowEmpty) {
				tokens.push_back("");
			}

			firstDelim = false;
		}
		
		// Mark the start of a token.
		int tokStart = i;
		
		// Skip to the next delimeter.
		while(i < str.size() && !strchr(delims.c_str(), str[i])) {
			i++;
		}
		
		// Mark the end.
		int tokEnd = i;
		
		// Copy the token.
		if(tokStart != tokEnd) {
			string token = str.substr(tokStart, (tokEnd - tokStart));
			tokens.push_back(token);
		}
	}
}

SimpleBuffer::SimpleBuffer(const void *data, int length) {
	
	this->data = (char*)malloc(length);
	this->length = length;
	
	memcpy(this->data, data, length);
	
	readPtr = 0;
}

SimpleBuffer::SimpleBuffer(void) {
	data = NULL;
	length = 0;
	
	readPtr = 0;
}

SimpleBuffer::~SimpleBuffer(void) {
	if(data) free(data);
}

void SimpleBuffer::clear(void) {
    if(data) free(data);
    data = NULL;
}

void SimpleBuffer::addData(const void *buffer, int length) {
	
	int newLength = length + this->length;
	int oldLength = this->length;
	
	if(data) {
		data = (char*)realloc(data, newLength);
	} else {
		data = (char*)malloc(newLength);
	}
	
	char *dataWritePtr = data + oldLength;
	
	memcpy(dataWritePtr, buffer, length);
	
	this->length = newLength;
}

const char *SimpleBuffer::getData(void) const {
	return data;
}

const char *SimpleBuffer::getDataAndAdvance(void *dst, int bytes) {
	
	if(readPtr >= length) return NULL;
	
	const char *ret = data + readPtr;
	
	if(dst) {
		memcpy(dst, ret, bytes);
	}
	
	readPtr += bytes;
	
	return ret;
}

int SimpleBuffer::getLength(void) const {
	return length;
}

int SimpleBuffer::compare(const void *buffer, int length) {
    
    if(length < this->length) return 1;
    if(length > this->length) return -1;

    const char *cbuf = (const char *)buffer;

    for(int i = 0; i < length; i++) {
        if(data[i] > cbuf[i]) {
            return 1;
        }
        if(data[i] < cbuf[i]) {
            return -1;
        }
    }

    return 0;
}

std::string stringEscape(const std::string &str) {

    ostringstream outStr;

    for(unsigned int i = 0; i < str.size(); i++) {

        switch(str[i]) {

        case '"':
            outStr << "\\\"";
            break;
            
        case '\\':
            outStr << "\\\\";
            break;

        case '\n':
            outStr << "\\n";
            break;

        default:
            outStr << str[i];
            break;
        }

    }

    return outStr.str();

}

std::string stringUnescape(const std::string &str) {

    ostringstream outStr;

    for(unsigned int i = 0; i < str.size(); i++) {

        if(str[i] == '\\') {

            i++;

            switch(str[i]) {

            case '\\':
                outStr << "\\";
                break;

            case '"':
                outStr << "\"";
                break;

            case 'n':
                outStr << "\n";
                break;

            default:
                // What the heck is this?
                outStr << str[i];
                break;

            }

        } else {

            outStr << str[i];

        }
    }

    return outStr.str();
}

static inline int getNextNonWhiteSpace(const std::string &str, int start) {

    if(start >= int(str.size())) return -1;

    while(isWhiteSpace(str[start])) {
        start++;
        if(start >= int(str.size())) return -1;
    }

    return start;
}


static inline char nibbleToHex(unsigned int n) {
    
    if(n < 0xa) {
        return '0' + n;
    } else {
        return ('a' + n) - 0xa;
    }

}

static inline unsigned int hexToNibble(char h) {
    
    if(h >= '0' && h <= '9') {
        return h - '0';
    } else if(h >= 'A' && h <= 'F') {
        return h - 'A' + 0xa;
    } else if(h >= 'a' && h <= 'f') {
        return h - 'a' + 0xa;
    }

    // Derp?
    return 0;

}


void strEncodeHex(const void *buf, int length, string &str, int columns) {

    str.resize((length * 2) + ((length * 2) / columns), 0);

    int strPos = 0;
    int lineCounter = 0;
    const char *cbuf = (const char *)buf;

    for(int i = 0; i < length; i++) {
        str[strPos++] = nibbleToHex((cbuf[i] & 0xf0) >> 4);
        str[strPos++] = nibbleToHex((cbuf[i] & 0x0f));

        lineCounter += 2;

        if(lineCounter > columns) {
            str[strPos++] = '\n';
            lineCounter = 0;
        }
    }

    str.resize(strPos);

}

char *strDecodeHex(const std::string &str, int *length) {

    // Do this in two passes. First, count up the number of bytes.
    int pos = 0;

    int hexPos = 0;
    char hexDigits[2];
    int numBytes = 0;
    
    while(pos < int(str.size())) {
        pos = getNextNonWhiteSpace(str, pos);

        if(pos == -1) {
            break;
        }

        hexDigits[hexPos] = str[pos];
        hexPos++;

        if(hexPos >= 2) {
            hexPos = 0;
            numBytes++;
        }

        pos++;
    }

    // Allocate the buffer.
    *length = numBytes;
    char *retBuf = new char[numBytes];

    hexPos = 0;
    pos = 0;
    int bufPos = 0;

    // Do it again.
    while(pos < int(str.size())) {
        pos = getNextNonWhiteSpace(str, pos);

        if(pos == -1) {
            break;
        }

        hexDigits[hexPos] = str[pos];
        hexPos++;

        if(hexPos >= 2) {
            hexPos = 0;

            retBuf[bufPos++] =
                (hexToNibble(hexDigits[0]) << 4) +
                hexToNibble(hexDigits[1]);

        }

        pos++;
    }

    return retBuf;

}


