/*
 * Copyright 2006-2012 Stephan Aßmus <superstippi@gmx.de>
 * Distributed under the terms of the MIT License.
 */

#include "AlphaSlider.h"

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

#include <AppDefs.h>
#include <Bitmap.h>
#include <ControlLook.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <Window.h>

#include "ui_defines.h"

// constructor
AlphaSlider::AlphaSlider(orientation dir, BMessage* message,
	border_style border)
	: BControl("alpha slider", NULL, message,
		B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
	, fBitmap(NULL)
	, fColor(kBlack)
	, fDragging(false)
	, fOrientation(dir)
	, fBorderStyle(border)
{
	FrameResized(Bounds().Width(), Bounds().Height());

	SetViewColor(B_TRANSPARENT_COLOR);
	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));

	SetValue(255);
}

// destructor
AlphaSlider::~AlphaSlider()
{
	delete fBitmap;
}

// WindowActivated
void
AlphaSlider::WindowActivated(bool active)
{
	if (IsFocus())
		Invalidate();
}

// MakeFocus
void
AlphaSlider::MakeFocus(bool focus)
{
	if (focus != IsFocus()) {
		BControl::MakeFocus(focus);
		Invalidate();
	}
}

// MinSize
BSize
AlphaSlider::MinSize()
{
	BSize minSize;
	if (fOrientation == B_HORIZONTAL)
		minSize = BSize(255 + 4, 7 + 4);
	else
		minSize = BSize(7 + 4, 255 + 4);
	return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize);
}

// PreferredSize
BSize
AlphaSlider::PreferredSize()
{
	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
}

// MaxSize
BSize
AlphaSlider::MaxSize()
{
	BSize minSize;
	if (fOrientation == B_HORIZONTAL)
		minSize = BSize(B_SIZE_UNLIMITED, 16 + 4);
	else
		minSize = BSize(16 + 4, B_SIZE_UNLIMITED);
	return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize);
}

// MouseDown
void
AlphaSlider::MouseDown(BPoint where)
{
	if (!IsEnabled())
		return;

//	if (!IsFocus())
//		MakeFocus(true);

	fDragging = true;
	SetValue(_ValueFor(where));

	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
}

// MouseUp
void
AlphaSlider::MouseUp(BPoint where)
{
	fDragging = false;
}

// MouseMoved
void
AlphaSlider::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
	if (!IsEnabled() || !fDragging)
		return;

	SetValue(_ValueFor(where));
}

// KeyDown
void
AlphaSlider::KeyDown(const char* bytes, int32 numBytes)
{
	if (!IsEnabled() || numBytes <= 0) {
		BControl::KeyDown(bytes, numBytes);
		return;
	}

	switch (bytes[0]) {
		case B_HOME:
			SetValue(0);
			break;
		case B_END:
			SetValue(255);
			break;

		case B_LEFT_ARROW:
		case B_UP_ARROW:
			SetValue(max_c(0, Value() - 1));
			break;
		case B_RIGHT_ARROW:
		case B_DOWN_ARROW:
			SetValue(min_c(255, Value() + 1));
			break;

		default:
			BControl::KeyDown(bytes, numBytes);
			break;
	}
}

// Draw
void
AlphaSlider::Draw(BRect updateRect)
{
	BRect b = Bounds();
	bool isFocus = IsFocus() && Window()->IsActive();

	if (fBorderStyle == B_FANCY_BORDER) {
		rgb_color bg = LowColor();
		uint32 flags = 0;

		if (!IsEnabled())
			flags |= BControlLook::B_DISABLED;
		if (isFocus)
			flags |= BControlLook::B_FOCUSED;

		be_control_look->DrawTextControlBorder(this, b, updateRect, bg, flags);
	}

	DrawBitmap(fBitmap, b.LeftTop());

	// value marker
	if (fOrientation == B_HORIZONTAL) {
		float pos = floorf(b.left + Value() * b.Width() / 255.0 + 0.5);

		if (pos - 2 >= b.left) {
			SetHighColor(kWhite);
			StrokeLine(BPoint(pos - 2, b.top), BPoint(pos - 2, b.bottom));
		}
		if (pos - 1 >= b.left) {
			SetHighColor(kBlack);
			StrokeLine(BPoint(pos - 1, b.top), BPoint(pos - 1, b.bottom));
		}
		if (pos + 1 <= b.right) {
			SetHighColor(kBlack);
			StrokeLine(BPoint(pos + 1, b.top), BPoint(pos + 1, b.bottom));
		}
		if (pos + 2 <= b.right) {
			SetHighColor(kWhite);
			StrokeLine(BPoint(pos + 2, b.top), BPoint(pos + 2, b.bottom));
		}
	} else {
		float pos = floorf(b.top + Value() * b.Height() / 255.0 + 0.5);

		if (pos - 2 >= b.top) {
			SetHighColor(kWhite);
			StrokeLine(BPoint(b.left, pos - 2), BPoint(b.right, pos - 2));
		}
		if (pos - 1 >= b.top) {
			SetHighColor(kBlack);
			StrokeLine(BPoint(b.left, pos - 1), BPoint(b.right, pos - 1));
		}
		if (pos + 1 <= b.bottom) {
			SetHighColor(kBlack);
			StrokeLine(BPoint(b.left, pos + 1), BPoint(b.right, pos + 1));
		}
		if (pos + 2 <= b.bottom) {
			SetHighColor(kWhite);
			StrokeLine(BPoint(b.left, pos + 2), BPoint(b.right, pos + 2));
		}
	}
}

// FrameResized
void
AlphaSlider::FrameResized(float width, float height)
{
	BRect r = _BitmapRect();
	_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
	_UpdateColors();
	Invalidate();

}

// SetValue
void
AlphaSlider::SetValue(int32 value)
{
	if (value == Value())
		return;

	Invoke(Message());
	BControl::SetValue(value);
}

// SetEnabled
void
AlphaSlider::SetEnabled(bool enabled)
{
	if (enabled == IsEnabled())
		return;

	BControl::SetEnabled(enabled);

	_UpdateColors();
	Invalidate();
}

// #pragma mark -

// SetColor
void
AlphaSlider::SetColor(const rgb_color& color)
{
	if ((uint32&)fColor == (uint32&)color)
		return;

	fColor = color;

	_UpdateColors();
	Invalidate();
}

// #pragma mark -

// blend_colors
inline void
blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3)
{
	if (alpha > 0) {
		if (alpha == 255) {
			d[0] = c1;
			d[1] = c2;
			d[2] = c3;
		} else {
			d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8);
			d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8);
			d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8);
		}
	}
}

// _UpdateColors
void
AlphaSlider::_UpdateColors()
{
	if (fBitmap == NULL || !fBitmap->IsValid())
		return;

	// fill in top row with alpha gradient
	uint8* topRow = (uint8*)fBitmap->Bits();
	uint32 width = fBitmap->Bounds().IntegerWidth() + 1;

	uint8* p = topRow;
	rgb_color color = fColor;
	if (!IsEnabled()) {
		// blend low color and color to give disabled look
		rgb_color bg = LowColor();
		color.red = (uint8)(((uint32)bg.red + color.red) / 2);
		color.green = (uint8)(((uint32)bg.green + color.green) / 2);
		color.blue = (uint8)(((uint32)bg.blue + color.blue) / 2);
	}
	for (uint32 x = 0; x < width; x++) {
		p[0] = color.blue;
		p[1] = color.green;
		p[2] = color.red;
		p[3] = x * 255 / width;
		p += 4;
	}
	// copy top row to rest of bitmap
	uint32 height = fBitmap->Bounds().IntegerHeight() + 1;
	uint32 bpr = fBitmap->BytesPerRow();
	uint8* dstRow = topRow + bpr;
	for (uint32 i = 1; i < height; i++) {
		memcpy(dstRow, topRow, bpr);
		dstRow += bpr;
	}
	// post process bitmap to underlay it with a pattern to visualize alpha
	uint8* row = topRow;
	for (uint32 i = 0; i < height; i++) {
		uint8* p = row;
		for (uint32 x = 0; x < width; x++) {
			uint8 alpha = p[3];
			if (alpha < 255) {
				p[3] = 255;
				alpha = 255 - alpha;
				if (x % 8 >= 4) {
					if (i % 8 >= 4) {
						blend_colors(p, alpha,
									 kAlphaLow.blue,
									 kAlphaLow.green,
									 kAlphaLow.red);
					} else {
						blend_colors(p, alpha,
									 kAlphaHigh.blue,
									 kAlphaHigh.green,
									 kAlphaHigh.red);
					}
				} else {
					if (i % 8 >= 4) {
						blend_colors(p, alpha,
									 kAlphaHigh.blue,
									 kAlphaHigh.green,
									 kAlphaHigh.red);
					} else {
						blend_colors(p, alpha,
									 kAlphaLow.blue,
									 kAlphaLow.green,
									 kAlphaLow.red);
					}
				}
			}
			p += 4;
		}
		row += bpr;
	}
}

// _AllocBitmap
void
AlphaSlider::_AllocBitmap(int32 width, int32 height)
{
	if (width < 2 || height < 2)
		return;

	delete fBitmap;
	fBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
}

// _BitmapRect
BRect
AlphaSlider::_BitmapRect() const
{
	BRect r = Bounds();
	if (fBorderStyle == B_FANCY_BORDER)
		r.InsetBy(2, 2);
	return r;
}

// _ValueFor
int32
AlphaSlider::_ValueFor(BPoint where) const
{
	BRect r = _BitmapRect();

	int32 value;
	if (fOrientation == B_HORIZONTAL)
		value = (int32)(255 * (where.x - r.left) / r.Width() + 0.5);
	else
		value = (int32)(255 * (where.y - r.top) / r.Height() + 0.5);

	value = max_c(0, value);
	value = min_c(255, value);

	return value;
}

