/****************************************************************************
(c) 2006 Melanie Thielker

This file is part of libdjconsole.

libdjconsole 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.

libdjconsole 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
libdjconsole; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 
****************************************************************************/

#include "delta.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

class DeltaSpec
{
public:
	int byte;
	unsigned char mask;
	int code;
};

DeltaEvent::DeltaEvent()
{
	Code=0;
	Value=0;
}

DeltaEvent::DeltaEvent(const DeltaEvent& in)
{
	Code=in.Code;
	Value=in.Value;
}

DeltaEvent::DeltaEvent(int c, int v)
{
	Code=c;
	Value=v;
}

DeltaEvent::~DeltaEvent()
{
}

DeltaEvent& DeltaEvent::operator= (const DeltaEvent& in)
{
	Code=in.Code;
	Value=in.Value;

	return *this;
}

int DeltaEvent::code()
{
	return Code;
}

int DeltaEvent::value()
{
	return Value;
}

Delta::Delta()
{
	Buffer=0;
	Length=0;

	Events.reserve(100000);
	pthread_mutex_init(&EventLock, 0);
}

Delta::~Delta()
{
	pthread_mutex_destroy(&EventLock);

	if(Buffer != 0)
		delete[] Buffer;
}

bool Delta::load(const char *file)
{
	FILE *fp;

	fp=fopen(file, "r");
	if(!fp)
		return false;
	
	while(Specs.size())
	{
		delete Specs[0];
		Specs.erase(Specs.begin());
	}

	char buf[1024];

	while(fgets(buf, 1024, fp))
	{
		int byte, mask, code;

		if(sscanf(buf, "%d %d %d", &byte, &mask, &code) == 3)
		{
			DeltaSpec *s=new DeltaSpec;
			s->byte=byte;
			s->mask=(unsigned char)(mask & 0xff);
			s->code=code;

			Specs.push_back(s);
			if(Buffer)
			{
				if(byte < Length)
					Events.push_back(DeltaEvent(code, Buffer[byte] & mask));
			}
		}
	}
	fclose(fp);

	return true;
}

void Delta::inject(int code, int value)
{
	pthread_mutex_lock(&EventLock);
	Events.push_back(DeltaEvent(code, value));
	pthread_mutex_unlock(&EventLock);
}

void Delta::set(const unsigned char *buffer, int len)
{
	if(Length != 0 && Length != len)
		return;
	
	vector<DeltaSpec *>::iterator it;

	if(Length == 0)
	{
		Buffer=new unsigned char[len];
		Length=len;
		memcpy(Buffer, buffer, Length);

		for(it=Specs.begin();it != Specs.end();++it)
		{
			DeltaSpec *s=(*it);
			if(s->byte < 0 || s->byte >= Length)
				continue;

			pthread_mutex_lock(&EventLock);
			Events.push_back(DeltaEvent(s->code, Buffer[s->byte] & s->mask));
			pthread_mutex_unlock(&EventLock);
		}

		return;
	}

	for(it=Specs.begin();it != Specs.end();++it)
	{
		DeltaSpec *s=(*it);
		if(s->byte < 0 || s->byte >= Length)
			continue;

		if((Buffer[s->byte] & s->mask) != (buffer[s->byte] & s->mask))
		{
			pthread_mutex_lock(&EventLock);
			Events.push_back(DeltaEvent(s->code, buffer[s->byte] & s->mask));
			pthread_mutex_unlock(&EventLock);
		}
	}

	memcpy(Buffer, buffer, Length);
}

DeltaEvent Delta::event()
{
	pthread_mutex_lock(&EventLock);
	if(Events.size() == 0)
	{
		pthread_mutex_unlock(&EventLock);
		return DeltaEvent(0, 0);
	}
	
	DeltaEvent ev=Events[Events.size()-1];;
	Events.pop_back();
	pthread_mutex_unlock(&EventLock);
	return ev;
}

const unsigned char *Delta::rawbits()
{
	return Buffer;
}

int Delta::size()
{
	return Length;
}
