/*
 * Copyright 2001-2006 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Marc Flerackers, mflerackers@androme.be
 *		Stefano Ceccherini, burton666@libero.it
 */

/**	Style storage used by BTextView */


#include "InlineInput.h"
#include "StyleBuffer.h"

#include <View.h>

#include <stdio.h>


//	#pragma mark - _BStyleRunDescBuffer_


_BStyleRunDescBuffer_::_BStyleRunDescBuffer_()
	:
	_BTextViewSupportBuffer_<STEStyleRunDesc>(20)
{
}


void
_BStyleRunDescBuffer_::InsertDesc(STEStyleRunDesc* inDesc, int32 index)
{
	InsertItemsAt(1, index, inDesc);
}


void
_BStyleRunDescBuffer_::RemoveDescs(int32 index, int32 count)
{
	RemoveItemsAt(count, index);
}


int32
_BStyleRunDescBuffer_::OffsetToRun(int32 offset) const
{
	if (fItemCount <= 1)
		return 0;

	int32 minIndex = 0;
	int32 maxIndex = fItemCount;
	int32 index = 0;

	while (minIndex < maxIndex) {
		index = (minIndex + maxIndex) >> 1;
		if (offset >= fBuffer[index].offset) {
			if (index >= fItemCount - 1
				|| offset < fBuffer[index + 1].offset) {
				break;
			} else
				minIndex = index + 1;
		} else
			maxIndex = index;
	}

	return index;
}


void
_BStyleRunDescBuffer_::BumpOffset(int32 delta, int32 index)
{
	for (int32 i = index; i < fItemCount; i++)
		fBuffer[i].offset += delta;
}


//	#pragma mark - _BStyleRecordBuffer_


_BStyleRecordBuffer_::_BStyleRecordBuffer_()
	:
	_BTextViewSupportBuffer_<STEStyleRecord>()
{
}


int32
_BStyleRecordBuffer_::InsertRecord(const BFont* inFont,
	const rgb_color* inColor)
{
	int32 index = 0;

	// look for style in buffer
	if (MatchRecord(inFont, inColor, &index))
		return index;

	// style not found, add it
	font_height fh;
	inFont->GetHeight(&fh);

	// check if there's any unused space
	for (index = 0; index < fItemCount; index++) {
		if (fBuffer[index].refs < 1) {
			fBuffer[index].refs = 0;
			fBuffer[index].ascent = fh.ascent;
			fBuffer[index].descent = fh.descent + fh.leading;
			fBuffer[index].style.font = *inFont;
			fBuffer[index].style.color = *inColor;
			return index;
		}
	}

	// no unused space, expand the buffer
	const STEStyle style = { *inFont, *inColor };
	const STEStyleRecord newRecord = {
		0,
		fh.ascent,
		fh.descent + fh.leading,
		style
	};
	InsertItemsAt(1, fItemCount, &newRecord);

	return index;
}


void
_BStyleRecordBuffer_::CommitRecord(int32 index)
{
	fBuffer[index].refs++;
}


void
_BStyleRecordBuffer_::RemoveRecord(int32 index)
{
	fBuffer[index].refs--;
}


bool
_BStyleRecordBuffer_::MatchRecord(const BFont* inFont, const rgb_color* inColor,
	int32* outIndex)
{
	for (int32 i = 0; i < fItemCount; i++) {
		if (*inFont == fBuffer[i].style.font
			&& *inColor == fBuffer[i].style.color) {
			*outIndex = i;
			return true;
		}
	}

	return false;
}


//	#pragma mark - SetStyleFromMode


static void
SetStyleFromMode(uint32 mode, const BFont* fromFont, BFont* toFont,
	const rgb_color* fromColor, rgb_color* toColor)
{
	if (fromFont != NULL && toFont != NULL) {
		if ((mode & B_FONT_FAMILY_AND_STYLE) != 0)
			toFont->SetFamilyAndStyle(fromFont->FamilyAndStyle());

		if ((mode & B_FONT_FACE) != 0)
			toFont->SetFace(fromFont->Face());

		if ((mode & B_FONT_SIZE) != 0)
			toFont->SetSize(fromFont->Size());

		if ((mode & B_FONT_SHEAR) != 0)
			toFont->SetShear(fromFont->Shear());

		if ((mode & B_FONT_FALSE_BOLD_WIDTH) != 0)
			toFont->SetFalseBoldWidth(fromFont->FalseBoldWidth());
	}

	if (fromColor != NULL && toColor != NULL
		&& (mode == 0 || mode == B_FONT_ALL)) {
		*toColor = *fromColor;
	}
}


//	#pragma mark - BTextView::StyleBuffer


BTextView::StyleBuffer::StyleBuffer(const BFont* inFont,
	const rgb_color* inColor)
	:
	fValidNullStyle(true)
{
	fNullStyle.font = *inFont;
	fNullStyle.color = *inColor;
}


void
BTextView::StyleBuffer::InvalidateNullStyle()
{
	fValidNullStyle = false;
}


bool
BTextView::StyleBuffer::IsValidNullStyle() const
{
	return fValidNullStyle;
}


void
BTextView::StyleBuffer::SyncNullStyle(int32 offset)
{
	if (fValidNullStyle || fStyleRunDesc.ItemCount() < 1)
		return;

	int32 index = OffsetToRun(offset);
	fNullStyle = fStyleRecord[fStyleRunDesc[index]->index]->style;

	fValidNullStyle = true;
}


void
BTextView::StyleBuffer::SetNullStyle(uint32 inMode, const BFont* inFont,
	const rgb_color* inColor, int32 offset)
{
	if (fValidNullStyle || fStyleRunDesc.ItemCount() < 1) {
		SetStyleFromMode(inMode, inFont, &fNullStyle.font, inColor,
			&fNullStyle.color);
	} else {
		int32 index = OffsetToRun(offset - 1);
		fNullStyle = fStyleRecord[fStyleRunDesc[index]->index]->style;
		SetStyleFromMode(inMode, inFont, &fNullStyle.font, inColor,
			&fNullStyle.color);
	}

	fValidNullStyle = true;
}


void
BTextView::StyleBuffer::GetNullStyle(const BFont** font,
	const rgb_color** color) const
{
	if (font != NULL)
		*font = &fNullStyle.font;

	if (color != NULL)
		*color = &fNullStyle.color;
}


STEStyleRange*
BTextView::StyleBuffer::AllocateStyleRange(const int32 numStyles) const
{
	STEStyleRange* range = (STEStyleRange*)malloc(sizeof(int32)
		+ sizeof(STEStyleRun) * numStyles);
	if (range != NULL)
		range->count = numStyles;

	return range;
}


void
BTextView::StyleBuffer::SetStyleRange(int32 fromOffset, int32 toOffset,
	int32 textLen, uint32 inMode, const BFont* inFont,
	const rgb_color* inColor)
{
	if (inFont == NULL)
		inFont = &fNullStyle.font;

	if (inColor == NULL)
		inColor = &fNullStyle.color;

	if (fromOffset == toOffset) {
		SetNullStyle(inMode, inFont, inColor, fromOffset);
		return;
	}

	if (fStyleRunDesc.ItemCount() < 1) {
		STEStyleRunDesc newDesc;
		newDesc.offset = fromOffset;
		newDesc.index = fStyleRecord.InsertRecord(inFont, inColor);
		fStyleRunDesc.InsertDesc(&newDesc, 0);
		fStyleRecord.CommitRecord(newDesc.index);
		return;
	}

	int32 offset = fromOffset;
	int32 runIndex = OffsetToRun(offset);
	int32 styleIndex = 0;
	do {
		const STEStyleRunDesc runDesc = *fStyleRunDesc[runIndex];
		int32 runEnd = textLen;
		if (runIndex < fStyleRunDesc.ItemCount() - 1)
			runEnd = fStyleRunDesc[runIndex + 1]->offset;

		STEStyle style = fStyleRecord[runDesc.index]->style;
		SetStyleFromMode(inMode, inFont, &style.font, inColor, &style.color);

		styleIndex = fStyleRecord.InsertRecord(&style.font, &style.color);

		if (runDesc.offset == offset && runIndex > 0
			&& fStyleRunDesc[runIndex - 1]->index == styleIndex) {
			RemoveStyles(runIndex);
			runIndex--;
		}

		if (styleIndex != runDesc.index) {
			if (offset > runDesc.offset) {
				STEStyleRunDesc newDesc;
				newDesc.offset = offset;
				newDesc.index = styleIndex;
				fStyleRunDesc.InsertDesc(&newDesc, runIndex + 1);
				fStyleRecord.CommitRecord(newDesc.index);
				runIndex++;
			} else {
				fStyleRunDesc[runIndex]->index = styleIndex;
				fStyleRecord.CommitRecord(styleIndex);
			}

			if (toOffset < runEnd) {
				STEStyleRunDesc newDesc;
				newDesc.offset = toOffset;
				newDesc.index = runDesc.index;
				fStyleRunDesc.InsertDesc(&newDesc, runIndex + 1);
				fStyleRecord.CommitRecord(newDesc.index);
			}
		}

		runIndex++;
		offset = runEnd;
	} while (offset < toOffset);

	if (offset == toOffset && runIndex < fStyleRunDesc.ItemCount()
		&& fStyleRunDesc[runIndex]->index == styleIndex) {
		RemoveStyles(runIndex);
	}
}


void
BTextView::StyleBuffer::GetStyle(int32 inOffset, BFont* outFont,
	rgb_color* outColor) const
{
	if (fStyleRunDesc.ItemCount() < 1) {
		if (outFont != NULL)
			*outFont = fNullStyle.font;

		if (outColor != NULL)
			*outColor = fNullStyle.color;

		return;
	}

	int32 runIndex = OffsetToRun(inOffset);
	int32 styleIndex = fStyleRunDesc[runIndex]->index;

	if (outFont != NULL)
		*outFont = fStyleRecord[styleIndex]->style.font;

	if (outColor != NULL)
		*outColor = fStyleRecord[styleIndex]->style.color;
}


STEStyleRange*
BTextView::StyleBuffer::GetStyleRange(int32 startOffset, int32 endOffset) const
{
	int32 startIndex = OffsetToRun(startOffset);
	int32 endIndex = OffsetToRun(endOffset);

	int32 numStyles = endIndex - startIndex + 1;
	if (numStyles < 1)
		numStyles = 1;

	STEStyleRange* result = AllocateStyleRange(numStyles);
	if (result == NULL)
		return NULL;

	STEStyleRun* run = &result->runs[0];
	for (int32 index = 0; index < numStyles; index++) {
		*run = (*this)[startIndex + index];
		run->offset -= startOffset;
		if (run->offset < 0)
			run->offset = 0;

		run++;
	}

	return result;
}


void
BTextView::StyleBuffer::RemoveStyleRange(int32 fromOffset, int32 toOffset)
{
	int32 fromIndex = fStyleRunDesc.OffsetToRun(fromOffset);
	int32 toIndex = fStyleRunDesc.OffsetToRun(toOffset) - 1;

	int32 count = toIndex - fromIndex;
	if (count > 0) {
		RemoveStyles(fromIndex + 1, count);
		toIndex = fromIndex;
	}

	fStyleRunDesc.BumpOffset(fromOffset - toOffset, fromIndex + 1);

	if (toIndex == fromIndex && toIndex < fStyleRunDesc.ItemCount() - 1) {
		STEStyleRunDesc* runDesc = fStyleRunDesc[toIndex + 1];
		runDesc->offset = fromOffset;
	}

	if (fromIndex < fStyleRunDesc.ItemCount() - 1) {
		STEStyleRunDesc* runDesc = fStyleRunDesc[fromIndex];
		if (runDesc->offset == (runDesc + 1)->offset) {
			RemoveStyles(fromIndex);
			fromIndex--;
		}
	}

	if (fromIndex >= 0 && fromIndex < fStyleRunDesc.ItemCount() - 1) {
		STEStyleRunDesc* runDesc = fStyleRunDesc[fromIndex];
		if (runDesc->index == (runDesc + 1)->index)
			RemoveStyles(fromIndex + 1);
	}
}


void
BTextView::StyleBuffer::RemoveStyles(int32 index, int32 count)
{
	for (int32 i = index; i < index + count; i++)
		fStyleRecord.RemoveRecord(fStyleRunDesc[i]->index);

	fStyleRunDesc.RemoveDescs(index, count);
}


int32
BTextView::StyleBuffer::Iterate(int32 fromOffset, int32 length,
	InlineInput* input,
	const BFont** outFont, const rgb_color** outColor,
	float* outAscent, float* outDescent, uint32*) const
{
	// TODO: Handle the InlineInput style here in some way
	int32 numRuns = fStyleRunDesc.ItemCount();
	if (length < 1 || numRuns < 1)
		return 0;

	int32 result = length;
	int32 runIndex = fStyleRunDesc.OffsetToRun(fromOffset);
	STEStyleRunDesc* run = fStyleRunDesc[runIndex];

	if (outFont != NULL)
		*outFont = &fStyleRecord[run->index]->style.font;

	if (outColor != NULL)
		*outColor = &fStyleRecord[run->index]->style.color;

	if (outAscent != NULL)
		*outAscent = fStyleRecord[run->index]->ascent;

	if (outDescent != NULL)
		*outDescent = fStyleRecord[run->index]->descent;

	if (runIndex < numRuns - 1) {
		int32 nextOffset = (run + 1)->offset - fromOffset;
		result = min_c(result, nextOffset);
	}

	return result;
}


int32
BTextView::StyleBuffer::OffsetToRun(int32 offset) const
{
	return fStyleRunDesc.OffsetToRun(offset);
}


void
BTextView::StyleBuffer::BumpOffset(int32 delta, int32 index)
{
	fStyleRunDesc.BumpOffset(delta, index);
}


STEStyleRun
BTextView::StyleBuffer::operator[](int32 index) const
{
	STEStyleRun run;

	if (fStyleRunDesc.ItemCount() < 1) {
		run.offset = 0;
		run.style = fNullStyle;
	} else {
		STEStyleRunDesc* runDesc = fStyleRunDesc[index];
		run.offset = runDesc->offset;
		run.style = fStyleRecord[runDesc->index]->style;
	}

	return run;
}


// TODO: Horrible name, but can't think of a better one
// ? CompareStyles ?
// ? FilterStyles ?
static void
FixupMode(const STEStyle &firstStyle, const STEStyle &otherStyle, uint32 &mode,
	bool &sameColor)
{
	if ((mode & B_FONT_FAMILY_AND_STYLE) != 0) {
		if (firstStyle.font != otherStyle.font)
			mode &= ~B_FONT_FAMILY_AND_STYLE;
	}
	if ((mode & B_FONT_SIZE) != 0) {
		if (firstStyle.font.Size() != otherStyle.font.Size())
			mode &= ~B_FONT_SIZE;
	}
	if ((mode & B_FONT_SHEAR) != 0) {
		if (firstStyle.font.Shear() != otherStyle.font.Shear())
			mode &= ~B_FONT_SHEAR;
	}
	if ((mode & B_FONT_FALSE_BOLD_WIDTH) != 0) {
		if (firstStyle.font.FalseBoldWidth()
				!= otherStyle.font.FalseBoldWidth()) {
			mode &= ~B_FONT_FALSE_BOLD_WIDTH;
		}
	}
	if (firstStyle.color != otherStyle.color)
		sameColor = false;

	// TODO: Finish this: handle B_FONT_FACE, B_FONT_FLAGS, etc.
	// if needed
}


void
BTextView::StyleBuffer::ContinuousGetStyle(BFont *outFont, uint32* ioMode,
	rgb_color* outColor, bool* sameColor, int32 fromOffset,
	int32 toOffset) const
{
	uint32 mode = B_FONT_ALL;

	if (fStyleRunDesc.ItemCount() < 1) {
		if (ioMode)
			*ioMode = mode;

		if (outFont != NULL)
			*outFont = fNullStyle.font;

		if (outColor != NULL)
			*outColor = fNullStyle.color;

		if (sameColor != NULL)
			*sameColor = true;

		return;
	}

	int32 fromIndex = OffsetToRun(fromOffset);
	int32 toIndex = OffsetToRun(toOffset - 1);

	if (fromIndex == toIndex) {
		int32 styleIndex = fStyleRunDesc[fromIndex]->index;
		const STEStyle* style = &fStyleRecord[styleIndex]->style;

		if (ioMode != NULL)
			*ioMode = mode;

		if (outFont != NULL)
			*outFont = style->font;

		if (outColor != NULL)
			*outColor = style->color;

		if (sameColor != NULL)
			*sameColor = true;
	} else {
		bool oneColor = true;
		int32 styleIndex = fStyleRunDesc[toIndex]->index;
		STEStyle theStyle = fStyleRecord[styleIndex]->style;

		for (int32 i = fromIndex; i < toIndex; i++) {
			styleIndex = fStyleRunDesc[i]->index;
			FixupMode(fStyleRecord[styleIndex]->style, theStyle, mode,
				oneColor);
		}

		if (ioMode != NULL)
			*ioMode = mode;

		if (outFont != NULL)
			*outFont = theStyle.font;

		if (outColor != NULL)
			*outColor = theStyle.color;

		if (sameColor != NULL)
			*sameColor = oneColor;
	}
}
