#include <Bitmap.h>
#include <stdlib.h> // for labs()

// bitmap data for the LCD numbers
const unsigned char LCDMedium64x9_raw[] = {
0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0x34,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0x34,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0x34,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0x34,0xff,0x34,0x34,0x34,0xff,0xff,0x34,0x34,0x34,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};

//#include "Bitmaps.h"
#include "GfxUtils.h"
#include "TimeDisplay.h"
//#include "DrawingTidbits.h"

const int kTimeDigitSpacing = 5;
const int kTimeInterDigitSpacing = 2;
const int kTimeInterColonSpacing = 1;
const BPoint kInitialOffset(3, 3);
const float kHoffset = -2.0;
const float kVoffset = 2.0;
const BRect kTimeDigitRect(0, 0, 4, 8);
//const BRect kTimeDisplayRect(0, 0, 6*4 + kTimeDigitSpacing - 1, 8);

TimeDisplay::TimeDisplay(BRect rect, const char *name, int16 largestSegment,
		int16 smallestSegment, bool displaySign, uint32 resizeMask)
	:	BView(rect, name, resizeMask, B_WILL_DRAW),
		fLargestShown(largestSegment),
		fSmallestShown(smallestSegment),
		fDisplaySign(displaySign)
{
	fSegments = new BBitmap(BRect(0, 0, 64 - 1, 9 - 1), B_COLOR_8_BIT);
	fSegments->SetBits(LCDMedium64x9_raw, 64*9, 0, B_COLOR_8_BIT);
	SetViewColor(0, 0, 0);
	SetDoubleBuffering(B_UPDATE_INVALIDATED | B_UPDATE_RESIZED);
}

TimeDisplay::~TimeDisplay()
{
	delete fSegments;
}


void
TimeDisplay::SetValue(int32 value)
{
	if (fValue == value)
		return;
		
	SetSign(value); // invalidates leading '-' if necessary
		
	fValue = value;
	int32 conversion, previous;
	
	switch (fLargestShown) {
	case TIME_HOURS:
		conversion = 75 * 60 * 60;
		value = labs(fValue / conversion);
		SetHours(value);
		value = fValue % conversion;
		// fall through
	case TIME_MINUTES:
		if (fSmallestShown > TIME_MINUTES)
			break;
		conversion = 75 * 60;
		previous = value;
		value = labs(previous / conversion);
		SetMinutes(value);
		value = previous % conversion;
		// fall through
	case TIME_SECONDS:
		if (fSmallestShown > TIME_SECONDS)
			break;
		conversion = 75;
		previous = value;
		value = labs(previous / conversion);
		SetSeconds(value);
		value = previous % conversion;
		// fall through
	case TIME_FRAMES:
		if (fSmallestShown > TIME_FRAMES)
			break;
		SetFrames(labs(value));
	}
}

void 
TimeDisplay::SetSign(int32 value)
{
	if (fDisplaySign && ((fValue < 0) != (value < 0))) {
		BRect rect(kTimeDigitRect);
		rect.OffsetTo(kInitialOffset);
		Invalidate(rect);
	}
}

void 
TimeDisplay::SetHours(int32 newHours)
{
	if (fLargestShown < TIME_HOURS || newHours == fHour)
		return;
	
	bool doFirst = ((newHours / 10) != (fHour / 10));
	bool doSecond = ((newHours % 10) != (fHour % 10));
	fHour = newHours;
	
	int32 prevDigits = fDisplaySign ? 1 : 0;
	
	if (doFirst) {
		InvalidateDigit(prevDigits, 0);
	}
	
	prevDigits++;

	if (doSecond) {
		InvalidateDigit(prevDigits, 0);
	}
}

void 
TimeDisplay::SetMinutes(int32 newMinutes)
{
	if (fLargestShown < TIME_MINUTES || fSmallestShown > TIME_MINUTES
		|| newMinutes == fMinute)
	{
		return;
	}
	
	bool doFirst = ((newMinutes / 10) != (fMinute / 10));
	bool doSecond = ((newMinutes % 10) != (fMinute % 10));
	fMinute = newMinutes;
	
	int32 prevColons = (fLargestShown - TIME_MINUTES);
	int32 prevDigits = (prevColons * 2) + (fDisplaySign ? 1 : 0);
	
	if (doFirst) {
		InvalidateDigit(prevDigits, prevColons);
	}
	
	prevDigits++;

	if (doSecond) {
		InvalidateDigit(prevDigits, prevColons);
	}
}

void 
TimeDisplay::SetSeconds(int32 newSeconds)
{
	if (fLargestShown < TIME_SECONDS || fSmallestShown > TIME_SECONDS
		|| newSeconds == fSecond)
	{
		return;
	}
	
	bool doFirst = ((newSeconds / 10) != (fSecond / 10));
	bool doSecond = ((newSeconds % 10) != (fSecond % 10));
	fSecond = newSeconds;
	
	int32 prevColons = (fLargestShown - TIME_SECONDS);
	int32 prevDigits = (prevColons * 2) + (fDisplaySign ? 1 : 0);
	
	if (doFirst) {
		InvalidateDigit(prevDigits, prevColons);
	}
	
	prevDigits++;

	if (doSecond) {
		InvalidateDigit(prevDigits, prevColons);
	}
}

void 
TimeDisplay::SetFrames(int32 newFrame)
{
	if (fSmallestShown > TIME_FRAMES || newFrame == fFrame)
		return;
	
	bool doFirst = ((newFrame / 10) != (fFrame / 10));
	bool doSecond = ((newFrame % 10) != (fFrame % 10));
	fFrame = newFrame;
	
	int32 prevColons = (fLargestShown - TIME_FRAMES);
	int32 prevDigits = (prevColons * 2) + (fDisplaySign ? 1 : 0);
	
	if (doFirst) {
		InvalidateDigit(prevDigits, prevColons);
	}
	
	prevDigits++;

	if (doSecond) {
		InvalidateDigit(prevDigits, prevColons);
	}
}

void 
TimeDisplay::InvalidateDigit(int32 prevDigits, int32 prevColons)
{
	BRect rect(kTimeDigitRect);
	rect.OffsetBy(kInitialOffset + BPoint(
		((prevDigits - prevColons) * kTimeInterDigitSpacing)
		+ (prevColons * 2 * kTimeInterColonSpacing)
		+ ((prevDigits + prevColons) * kTimeDigitRect.Width()), 0));

	rect.OffsetBy(kHoffset, kVoffset);
	Invalidate(rect);
}


void 
TimeDisplay::Draw(BRect)
{
	BRect bounds(Bounds());
	rgb_color white = create_color(255, 255, 255);
	rgb_color outline = Parent()->ViewColor();
	outline = ShiftColor(outline, 1.2);

	BeginLineArray(4);
	AddLine(BPoint(bounds.left, bounds.top), BPoint(bounds.left, bounds.bottom), outline);
	AddLine(BPoint(bounds.left, bounds.top), BPoint(bounds.right, bounds.top), outline);
	AddLine(BPoint(bounds.left, bounds.bottom), BPoint(bounds.right, bounds.bottom), white);
	AddLine(BPoint(bounds.right, bounds.bottom), BPoint(bounds.right, bounds.top), white);
	EndLineArray();
	
	bounds.InsetBy(1, 1);
	FillRect(bounds);
	float offset = 0;
	
	// draw leading '-' if necessary
	if (fDisplaySign) {
		if (fValue < 0) {
			offset = DrawDigit(offset, 0, kTimeDigitRect, fSegments);
		} else {
			offset = kTimeDigitRect.Width();
		}
		offset += kTimeInterDigitSpacing;
	}
	
	if (fLargestShown == TIME_HOURS) {
		offset = DrawDigit(offset, fHour / 10 + 1, kTimeDigitRect, fSegments);
		offset = DrawDigit(offset + kTimeInterDigitSpacing, fHour % 10 + 1,
			kTimeDigitRect, fSegments);
		// draw ':' if necessary
		if (fSmallestShown < TIME_HOURS) {
			offset = DrawDigit(offset + kTimeInterColonSpacing, 11, kTimeDigitRect, fSegments);
			offset += kTimeInterColonSpacing;
		}
	}
	
	if (fLargestShown >= TIME_MINUTES && fSmallestShown <= TIME_MINUTES) {
		offset = DrawDigit(offset, fMinute / 10 + 1, kTimeDigitRect, fSegments);
		offset = DrawDigit(offset + kTimeInterDigitSpacing, fMinute % 10 + 1,
			kTimeDigitRect, fSegments);
		// draw ':' if necessary
		if (fSmallestShown < TIME_MINUTES) {
			offset = DrawDigit(offset + kTimeInterColonSpacing, 11, kTimeDigitRect, fSegments);
			offset += kTimeInterColonSpacing;
		}
	}

	if (fLargestShown >= TIME_SECONDS && fSmallestShown <= TIME_SECONDS) {	
		offset = DrawDigit(offset, fSecond / 10 + 1, kTimeDigitRect, fSegments);
		offset = DrawDigit(offset + kTimeInterDigitSpacing, fSecond % 10 + 1,
			kTimeDigitRect, fSegments);
		// draw ':' if necessary
		if (fSmallestShown < TIME_SECONDS) {
			offset = DrawDigit(offset + kTimeInterColonSpacing, 11, kTimeDigitRect, fSegments);
			offset += kTimeInterColonSpacing;
		}
	}
	
	if (fSmallestShown == TIME_FRAMES) {
		offset = DrawDigit(offset, fFrame / 10 + 1, kTimeDigitRect, fSegments);
		offset = DrawDigit(offset + kTimeInterDigitSpacing, fFrame % 10 + 1,
			kTimeDigitRect, fSegments);
	}
}

float
TimeDisplay::DrawDigit(float offset, uint32 digitIndex, BRect digitSize,
	const BBitmap *map)
{
	BRect src(digitSize);
	
		// the '-' is first in the digit array
	src.OffsetBy(digitIndex * (digitSize.Width() + 1), 0);
	BRect dst(Bounds());
	dst.OffsetTo(offset, 0);
	dst.right = dst.left + digitSize.Width();
	dst.bottom = dst.top + digitSize.Height();
	SetHighColor(ViewColor());
	dst.OffsetBy(kInitialOffset);
	FillRect(dst);

	// take advantages of parts of the digit being transparent
	SetDrawingMode(B_OP_OVER);
	SetLowColor(B_TRANSPARENT_32_BIT); // XXX: what does this try to accomplish?  why is it here?

	dst.OffsetBy(kHoffset, kVoffset);	
	DrawBitmap(map, src, dst);
	return offset + dst.Width();
}
