/*
 * Copyright 2001-2015, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Marc Flerackers (mflerackers@androme.be)
 *		Axel Dörfler, axeld@pinc-software.de
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Joseph Groover <looncraz@looncraz.net>
 */

/*! BStatusBar displays a "percentage-of-completion" gauge. */
#include <StatusBar.h>

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

#include <ControlLook.h>
#include <Layout.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <Region.h>

#include <binary_compatibility/Interface.h>

enum internalFlags {
	kCustomBarColor = 1
};


BStatusBar::BStatusBar(BRect frame, const char *name, const char *label,
		const char *trailingLabel)
	:
	BView(frame, name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW),
	fLabel(label),
	fTrailingLabel(trailingLabel)
{
	_InitObject();
}


BStatusBar::BStatusBar(const char *name, const char *label,
		const char *trailingLabel)
	:
	BView(BRect(0, 0, -1, -1), name, B_FOLLOW_LEFT | B_FOLLOW_TOP,
		B_WILL_DRAW | B_SUPPORTS_LAYOUT),
	fLabel(label),
	fTrailingLabel(trailingLabel)
{
	_InitObject();
}


BStatusBar::BStatusBar(BMessage *archive)
	:
	BView(archive)
{
	_InitObject();

	archive->FindString("_label", &fLabel);
	archive->FindString("_tlabel", &fTrailingLabel);

	archive->FindString("_text", &fText);
	archive->FindString("_ttext", &fTrailingText);

	float floatValue;
	if (archive->FindFloat("_high", &floatValue) == B_OK) {
		fBarHeight = floatValue;
		fCustomBarHeight = true;
	}

	int32 color;
	if (archive->FindInt32("_bcolor", (int32 *)&color) == B_OK) {
		fBarColor = *(rgb_color *)&color;
		fInternalFlags |= kCustomBarColor;
	}

	if (archive->FindFloat("_val", &floatValue) == B_OK)
		fCurrent = floatValue;
	if (archive->FindFloat("_max", &floatValue) == B_OK)
		fMax = floatValue;
}


BStatusBar::~BStatusBar()
{
}


BArchivable *
BStatusBar::Instantiate(BMessage *archive)
{
	if (validate_instantiation(archive, "BStatusBar"))
		return new BStatusBar(archive);

	return NULL;
}


status_t
BStatusBar::Archive(BMessage *archive, bool deep) const
{
	status_t err = BView::Archive(archive, deep);
	if (err < B_OK)
		return err;

	if (fCustomBarHeight)
		err = archive->AddFloat("_high", fBarHeight);

	if (err == B_OK && fInternalFlags & kCustomBarColor)
		err = archive->AddInt32("_bcolor", (const uint32 &)fBarColor);

	if (err == B_OK && fCurrent != 0)
		err = archive->AddFloat("_val", fCurrent);
	if (err == B_OK && fMax != 100 )
		err = archive->AddFloat("_max", fMax);

	if (err == B_OK && fText.Length())
		err = archive->AddString("_text", fText);
	if (err == B_OK && fTrailingText.Length())
		err = archive->AddString("_ttext", fTrailingText);

	if (err == B_OK && fLabel.Length())
		err = archive->AddString("_label", fLabel);
	if (err == B_OK && fTrailingLabel.Length())
		err = archive->AddString ("_tlabel", fTrailingLabel);

	return err;
}


// #pragma mark -


void
BStatusBar::AttachedToWindow()
{
	// resize so that the height fits
	float width, height;
	GetPreferredSize(&width, &height);
	ResizeTo(Bounds().Width(), height);

	SetViewColor(B_TRANSPARENT_COLOR);

	AdoptParentColors();

	fTextDivider = Bounds().Width();

	if ((fInternalFlags & kCustomBarColor) == 0)
		fBarColor = ui_color(B_STATUS_BAR_COLOR);
}


void
BStatusBar::DetachedFromWindow()
{
	BView::DetachedFromWindow();
}


void
BStatusBar::AllAttached()
{
	BView::AllAttached();
}


void
BStatusBar::AllDetached()
{
	BView::AllDetached();
}


// #pragma mark -


void
BStatusBar::WindowActivated(bool state)
{
	BView::WindowActivated(state);
}


void
BStatusBar::MakeFocus(bool state)
{
	BView::MakeFocus(state);
}


// #pragma mark -


void
BStatusBar::GetPreferredSize(float* _width, float* _height)
{
	if (_width) {
		// AttachedToWindow() might not have been called yet
		*_width = ceilf(StringWidth(fLabel.String()))
			+ ceilf(StringWidth(fTrailingLabel.String()))
			+ ceilf(StringWidth(fText.String()))
			+ ceilf(StringWidth(fTrailingText.String()))
			+ 5;
	}

	if (_height) {
		float labelHeight = 0;
		if (_HasText()) {
			font_height fontHeight;
			GetFontHeight(&fontHeight);
			labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 6;
		}

		*_height = labelHeight + BarHeight();
	}
}


BSize
BStatusBar::MinSize()
{
	float width, height;
	GetPreferredSize(&width, &height);

	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height));
}


BSize
BStatusBar::MaxSize()
{
	float width, height;
	GetPreferredSize(&width, &height);

	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
		BSize(B_SIZE_UNLIMITED, height));
}


BSize
BStatusBar::PreferredSize()
{
	float width, height;
	GetPreferredSize(&width, &height);

	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
		BSize(width, height));
}


void
BStatusBar::ResizeToPreferred()
{
	BView::ResizeToPreferred();
}


void
BStatusBar::FrameMoved(BPoint newPosition)
{
	BView::FrameMoved(newPosition);
}


void
BStatusBar::FrameResized(float newWidth, float newHeight)
{
	BView::FrameResized(newWidth, newHeight);
	Invalidate();
}


// #pragma mark -


void
BStatusBar::Draw(BRect updateRect)
{
	rgb_color backgroundColor = LowColor();

	font_height fontHeight;
	GetFontHeight(&fontHeight);
	BRect barFrame = _BarFrame(&fontHeight);
	BRect outerFrame = barFrame.InsetByCopy(-2, -2);

	BRegion background(updateRect);
	background.Exclude(outerFrame);
	FillRegion(&background, B_SOLID_LOW);

	// Draw labels/texts

	BRect rect = outerFrame;
	rect.top = 0;
	rect.bottom = outerFrame.top - 1;

	if (updateRect.Intersects(rect)) {
		// update labels
		BString leftText;
		leftText << fLabel << fText;

		BString rightText;
		rightText << fTrailingText << fTrailingLabel;

		float baseLine = ceilf(fontHeight.ascent) + 1;
		fTextDivider = rect.right;

		BFont font;
		GetFont(&font);

		if (rightText.Length()) {
			font.TruncateString(&rightText, B_TRUNCATE_BEGINNING,
				rect.Width());
			fTextDivider -= StringWidth(rightText.String());
		}

		if (leftText.Length()) {
			float width = max_c(0.0, fTextDivider - rect.left);
			font.TruncateString(&leftText, B_TRUNCATE_END, width);
		}

		rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);

		if (backgroundColor != ui_color(B_PANEL_BACKGROUND_COLOR)) {
			if (backgroundColor.Brightness() > 100)
				textColor = make_color(0, 0, 0, 255);
			else
				textColor = make_color(255, 255, 255, 255);
		}

		SetHighColor(textColor);

		if (leftText.Length())
			DrawString(leftText.String(), BPoint(rect.left, baseLine));

		if (rightText.Length())
			DrawString(rightText.String(), BPoint(fTextDivider, baseLine));
	}

	// Draw bar

	if (!updateRect.Intersects(outerFrame))
		return;

	rect = outerFrame;

	be_control_look->DrawStatusBar(this, rect, updateRect,
		backgroundColor, fBarColor, _BarPosition(barFrame));
}


void
BStatusBar::MessageReceived(BMessage *message)
{
	switch(message->what) {
		case B_UPDATE_STATUS_BAR:
		{
			float delta;
			const char *text = NULL, *trailing_text = NULL;

			message->FindFloat("delta", &delta);
			message->FindString("text", &text);
			message->FindString("trailing_text", &trailing_text);

			Update(delta, text, trailing_text);

			break;
		}

		case B_RESET_STATUS_BAR:
		{
			const char *label = NULL, *trailing_label = NULL;

			message->FindString("label", &label);
			message->FindString("trailing_label", &trailing_label);

			Reset(label, trailing_label);

			break;
		}

		case B_COLORS_UPDATED:
		{
			// Change the bar color IF we don't have an application-set color.
			if ((fInternalFlags & kCustomBarColor) == 0) {
				message->FindColor(ui_color_name(B_STATUS_BAR_COLOR),
					&fBarColor);
			}

			break;
		}

		default:
			BView::MessageReceived(message);
			break;
	}
}


void
BStatusBar::MouseDown(BPoint point)
{
	BView::MouseDown(point);
}


void
BStatusBar::MouseUp(BPoint point)
{
	BView::MouseUp(point);
}


void
BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
{
	BView::MouseMoved(point, transit, message);
}


// #pragma mark -


void
BStatusBar::SetBarColor(rgb_color color)
{
	fInternalFlags |= kCustomBarColor;
	fBarColor = color;

	Invalidate();
}


void
BStatusBar::SetBarHeight(float barHeight)
{
	float oldHeight = BarHeight();

	fCustomBarHeight = true;
	fBarHeight = barHeight;

	if (barHeight == oldHeight)
		return;

	// resize so that the height fits
	if ((Flags() & B_SUPPORTS_LAYOUT) != 0)
		InvalidateLayout();
	else {
		float width, height;
		GetPreferredSize(&width, &height);
		ResizeTo(Bounds().Width(), height);
	}
}


void
BStatusBar::SetText(const char* string)
{
	_SetTextData(fText, string, fLabel, false);
}


void
BStatusBar::SetTrailingText(const char* string)
{
	_SetTextData(fTrailingText, string, fTrailingLabel, true);
}


void
BStatusBar::SetMaxValue(float max)
{
	// R5 and/or Zeta's SetMaxValue does not trigger an invalidate here.
	// this is probably not ideal behavior, but it does break apps in some cases
	// as observed with SpaceMonitor.
	// TODO: revisit this when we break binary compatibility
	fMax = max;
}


void
BStatusBar::Update(float delta, const char* text, const char* trailingText)
{
	// If any of these are NULL, the existing text remains (BeBook)
	if (text == NULL)
		text = fText.String();
	if (trailingText == NULL)
		trailingText = fTrailingText.String();
	BStatusBar::SetTo(fCurrent + delta, text, trailingText);
}


void
BStatusBar::Reset(const char *label, const char *trailingLabel)
{
	// Reset replaces the label and trailing label with copies of the
	// strings passed as arguments. If either argument is NULL, the
	// label or trailing label will be deleted and erased.
	fLabel = label ? label : "";
	fTrailingLabel = trailingLabel ? trailingLabel : "";

	// Reset deletes and erases any text or trailing text
	fText = "";
	fTrailingText = "";

	fCurrent = 0;
	fMax = 100;

	Invalidate();
}


void
BStatusBar::SetTo(float value, const char* text, const char* trailingText)
{
	SetText(text);
	SetTrailingText(trailingText);

	if (value > fMax)
		value = fMax;
	else if (value < 0)
		value = 0;
	if (value == fCurrent)
		return;

	BRect barFrame = _BarFrame();
	float oldPosition = _BarPosition(barFrame);

	fCurrent = value;

	float newPosition = _BarPosition(barFrame);
	if (oldPosition == newPosition)
		return;

	// update only the part of the status bar with actual changes
	BRect update = barFrame;
	if (oldPosition < newPosition) {
		update.left = floorf(max_c(oldPosition - 1, update.left));
		update.right = ceilf(newPosition);
	} else {
		update.left = floorf(max_c(newPosition - 1, update.left));
		update.right = ceilf(oldPosition);
	}

	// TODO: Ask the BControlLook in the first place about dirty rect.
	update.InsetBy(-1, -1);

	Invalidate(update);
}


float
BStatusBar::CurrentValue() const
{
	return fCurrent;
}


float
BStatusBar::MaxValue() const
{
	return fMax;
}


rgb_color
BStatusBar::BarColor() const
{
	return fBarColor;
}


float
BStatusBar::BarHeight() const
{
	if (!fCustomBarHeight && fBarHeight == -1) {
		// the default bar height is as height as the label
		font_height fontHeight;
		GetFontHeight(&fontHeight);
		const_cast<BStatusBar *>(this)->fBarHeight = fontHeight.ascent
			+ fontHeight.descent + 5;
	}

	return ceilf(fBarHeight);
}


const char *
BStatusBar::Text() const
{
	return fText.String();
}


const char *
BStatusBar::TrailingText() const
{
	return fTrailingText.String();
}


const char *
BStatusBar::Label() const
{
	return fLabel.String();
}


const char *
BStatusBar::TrailingLabel() const
{
	return fTrailingLabel.String();
}


// #pragma mark -


BHandler *
BStatusBar::ResolveSpecifier(BMessage* message, int32 index,
	BMessage* specifier, int32 what, const char *property)
{
	return BView::ResolveSpecifier(message, index, specifier, what, property);
}


status_t
BStatusBar::GetSupportedSuites(BMessage* data)
{
	return BView::GetSupportedSuites(data);
}


status_t
BStatusBar::Perform(perform_code code, void* _data)
{
	switch (code) {
		case PERFORM_CODE_MIN_SIZE:
			((perform_data_min_size*)_data)->return_value
				= BStatusBar::MinSize();
			return B_OK;
		case PERFORM_CODE_MAX_SIZE:
			((perform_data_max_size*)_data)->return_value
				= BStatusBar::MaxSize();
			return B_OK;
		case PERFORM_CODE_PREFERRED_SIZE:
			((perform_data_preferred_size*)_data)->return_value
				= BStatusBar::PreferredSize();
			return B_OK;
		case PERFORM_CODE_LAYOUT_ALIGNMENT:
			((perform_data_layout_alignment*)_data)->return_value
				= BStatusBar::LayoutAlignment();
			return B_OK;
		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
			((perform_data_has_height_for_width*)_data)->return_value
				= BStatusBar::HasHeightForWidth();
			return B_OK;
		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
		{
			perform_data_get_height_for_width* data
				= (perform_data_get_height_for_width*)_data;
			BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max,
				&data->preferred);
			return B_OK;
		}
		case PERFORM_CODE_SET_LAYOUT:
		{
			perform_data_set_layout* data = (perform_data_set_layout*)_data;
			BStatusBar::SetLayout(data->layout);
			return B_OK;
		}
		case PERFORM_CODE_LAYOUT_INVALIDATED:
		{
			perform_data_layout_invalidated* data
				= (perform_data_layout_invalidated*)_data;
			BStatusBar::LayoutInvalidated(data->descendants);
			return B_OK;
		}
		case PERFORM_CODE_DO_LAYOUT:
		{
			BStatusBar::DoLayout();
			return B_OK;
		}
	}

	return BView::Perform(code, _data);
}


// #pragma mark -


extern "C" void
_ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value,
	const char* text, const char* trailingText)
{
	self->BStatusBar::SetTo(value, text, trailingText);
}


void BStatusBar::_ReservedStatusBar2() {}
void BStatusBar::_ReservedStatusBar3() {}
void BStatusBar::_ReservedStatusBar4() {}


BStatusBar &
BStatusBar::operator=(const BStatusBar& other)
{
	return *this;
}


// #pragma mark -


void
BStatusBar::_InitObject()
{
	fMax = 100.0;
	fCurrent = 0.0;

	fBarHeight = -1.0;
	fTextDivider = Bounds().Width();

	fCustomBarHeight = false;
	fInternalFlags = 0;

	SetFlags(Flags() | B_FRAME_EVENTS);
}


void
BStatusBar::_SetTextData(BString& text, const char* source,
	const BString& combineWith, bool rightAligned)
{
	if (source == NULL)
		source = "";

	// If there were no changes, we don't have to do anything
	if (text == source)
		return;

	bool oldHasText = _HasText();
	text = source;

	BString newString;
	if (rightAligned)
		newString << text << combineWith;
	else
		newString << combineWith << text;

	if (oldHasText != _HasText())
		InvalidateLayout();

	font_height fontHeight;
	GetFontHeight(&fontHeight);

//	Invalidate(BRect(position, 0, position + invalidateWidth,
//		ceilf(fontHeight.ascent) + ceilf(fontHeight.descent)));
// TODO: redrawing the entire area takes care of the edge case
// where the left side string changes because of truncation and
// part of it needs to be redrawn as well.
	Invalidate(BRect(0, 0, Bounds().right,
		ceilf(fontHeight.ascent) + ceilf(fontHeight.descent)));
}


/*!
	Returns the inner bar frame without the surrounding bevel.
*/
BRect
BStatusBar::_BarFrame(const font_height* fontHeight) const
{
	float top = 2;
	if (_HasText()) {
		if (fontHeight == NULL) {
			font_height height;
			GetFontHeight(&height);
			top = ceilf(height.ascent + height.descent) + 6;
		} else
			top = ceilf(fontHeight->ascent + fontHeight->descent) + 6;
	}

	return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4);
}


float
BStatusBar::_BarPosition(const BRect& barFrame) const
{
	if (fCurrent == 0)
		return barFrame.left - 1;

	return roundf(barFrame.left - 1
		+ (fCurrent * (barFrame.Width() + 3) / fMax));
}


bool
BStatusBar::_HasText() const
{
	// Force BeOS behavior where the size of the BStatusBar always included
	// room for labels, even when there weren't any.
	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
		return true;
	return fLabel.Length() > 0 || fTrailingLabel.Length() > 0
		|| fTrailingText.Length() > 0 || fText.Length() > 0;
}
