/*
 * Copyright 2007-2015 Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ryan Leavengood <leavengood@gmail.com>
 *		John Scipione <jscipione@gmail.com>
 *		Joseph Groover <looncraz@looncraz.net>
 */


#include <AboutWindow.h>

#include <stdarg.h>
#include <time.h>

#include <Alert.h>
#include <AppFileInfo.h>
#include <Bitmap.h>
#include <Button.h>
#include <File.h>
#include <Font.h>
#include <GroupLayoutBuilder.h>
#include <GroupView.h>
#include <InterfaceDefs.h>
#include <LayoutBuilder.h>
#include <Message.h>
#include <MessageFilter.h>
#include <Point.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <Size.h>
#include <String.h>
#include <StringView.h>
#include <SystemCatalog.h>
#include <TextView.h>
#include <View.h>
#include <Window.h>


static const float kStripeWidth = 30.0;

using BPrivate::gSystemCatalog;


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AboutWindow"


namespace BPrivate {

class StripeView : public BView {
public:
							StripeView(BBitmap* icon);
	virtual					~StripeView();

	virtual	void			Draw(BRect updateRect);

			BBitmap*		Icon() const { return fIcon; };
			void			SetIcon(BBitmap* icon);

private:
			BBitmap*		fIcon;
};


class AboutView : public BGroupView {
public:
							AboutView(const char* name,
								const char* signature);
	virtual					~AboutView();

	virtual	void			AllAttached();

			BTextView*		InfoView() const { return fInfoView; };

			BBitmap*		Icon();
			status_t		SetIcon(BBitmap* icon);

			const char*		Name();
			status_t		SetName(const char* name);

			const char*		Version();
			status_t		SetVersion(const char* version);

private:
			const char*		_GetVersionFromSignature(const char* signature);
			BBitmap*		_GetIconFromSignature(const char* signature);

private:
			BStringView*	fNameView;
			BStringView*	fVersionView;
			BTextView*		fInfoView;
			StripeView*		fStripeView;
};


//	#pragma mark - StripeView


StripeView::StripeView(BBitmap* icon)
	:
	BView("StripeView", B_WILL_DRAW),
	fIcon(icon)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

	float width = 0.0f;
	if (icon != NULL)
		width += icon->Bounds().Width() + 24.0f;

	SetExplicitSize(BSize(width, B_SIZE_UNSET));
	SetExplicitPreferredSize(BSize(width, B_SIZE_UNLIMITED));
}


StripeView::~StripeView()
{
}


void
StripeView::Draw(BRect updateRect)
{
	if (fIcon == NULL)
		return;

	SetHighColor(ViewColor());
	FillRect(updateRect);

	BRect stripeRect = Bounds();
	stripeRect.right = kStripeWidth;
	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
	FillRect(stripeRect);

	SetDrawingMode(B_OP_ALPHA);
	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
	DrawBitmapAsync(fIcon, BPoint(15.0f, 10.0f));
}


void
StripeView::SetIcon(BBitmap* icon)
{
	if (fIcon != NULL)
		delete fIcon;

	fIcon = icon;

	float width = 0.0f;
	if (icon != NULL)
		width += icon->Bounds().Width() + 24.0f;

	SetExplicitSize(BSize(width, B_SIZE_UNSET));
	SetExplicitPreferredSize(BSize(width, B_SIZE_UNLIMITED));
};


//	#pragma mark - AboutView


AboutView::AboutView(const char* appName, const char* signature)
	:
	BGroupView("AboutView", B_VERTICAL)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fNameView = new BStringView("name", appName);
	BFont font;
	fNameView->GetFont(&font);
	font.SetFace(B_BOLD_FACE);
	font.SetSize(font.Size() * 2.0);
	fNameView->SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE
		| B_FONT_FLAGS);
	fNameView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

	fVersionView = new BStringView("version",
		_GetVersionFromSignature(signature));
	fVersionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

	rgb_color documentColor = ui_color(B_DOCUMENT_TEXT_COLOR);
	fInfoView = new BTextView("info", NULL, &documentColor, B_WILL_DRAW);
	fInfoView->SetExplicitMinSize(BSize(210.0, 160.0));
	fInfoView->MakeEditable(false);
	fInfoView->SetWordWrap(true);
	fInfoView->SetInsets(5.0, 5.0, 5.0, 5.0);
	fInfoView->SetStylable(true);

	BScrollView* infoViewScroller = new BScrollView(
		"infoViewScroller", fInfoView, B_WILL_DRAW | B_FRAME_EVENTS,
		false, true, B_PLAIN_BORDER);

	fStripeView = new StripeView(_GetIconFromSignature(signature));

	const char* ok = B_TRANSLATE_MARK("OK");
	BButton* closeButton = new BButton("ok",
		gSystemCatalog.GetString(ok, "AboutWindow"),
		new BMessage(B_QUIT_REQUESTED));

	GroupLayout()->SetSpacing(0);
	BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
		.Add(fStripeView)
		.AddGroup(B_VERTICAL)
			.SetInsets(0, B_USE_DEFAULT_SPACING,
				B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING)
			.AddGroup(B_VERTICAL, 0)
				.Add(fNameView)
				.Add(fVersionView)
				.AddStrut(B_USE_SMALL_SPACING)
				.Add(infoViewScroller)
				.End()
			.AddGroup(B_HORIZONTAL, 0)
				.AddGlue()
				.Add(closeButton)
				.End()
			.End()
		.View()->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}


AboutView::~AboutView()
{
}


void
AboutView::AllAttached()
{
	fNameView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fInfoView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
	fVersionView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}


//	#pragma mark - AboutView private methods


const char*
AboutView::_GetVersionFromSignature(const char* signature)
{
	if (signature == NULL)
		return NULL;

	entry_ref ref;
	if (be_roster->FindApp(signature, &ref) != B_OK)
		return NULL;

	BFile file(&ref, B_READ_ONLY);
	BAppFileInfo appMime(&file);
	if (appMime.InitCheck() != B_OK)
		return NULL;

	version_info versionInfo;
	if (appMime.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) == B_OK) {
		if (versionInfo.major == 0 && versionInfo.middle == 0
			&& versionInfo.minor == 0) {
			return NULL;
		}

		const char* version = B_TRANSLATE_MARK("Version");
		version = gSystemCatalog.GetString(version, "AboutWindow");
		BString appVersion(version);
		appVersion << " " << versionInfo.major << "." << versionInfo.middle;
		if (versionInfo.minor > 0)
			appVersion << "." << versionInfo.minor;

		// Add the version variety
		const char* variety = NULL;
		switch (versionInfo.variety) {
			case B_DEVELOPMENT_VERSION:
				variety = B_TRANSLATE_MARK("development");
				break;
			case B_ALPHA_VERSION:
				variety = B_TRANSLATE_MARK("alpha");
				break;
			case B_BETA_VERSION:
				variety = B_TRANSLATE_MARK("beta");
				break;
			case B_GAMMA_VERSION:
				variety = B_TRANSLATE_MARK("gamma");
				break;
			case B_GOLDEN_MASTER_VERSION:
				variety = B_TRANSLATE_MARK("gold master");
				break;
		}

		if (variety != NULL) {
			variety = gSystemCatalog.GetString(variety, "AboutWindow");
			appVersion << "-" << variety;
		}

		return appVersion.String();
	}

	return NULL;
}


BBitmap*
AboutView::_GetIconFromSignature(const char* signature)
{
	if (signature == NULL)
		return NULL;

	entry_ref ref;
	if (be_roster->FindApp(signature, &ref) != B_OK)
		return NULL;

	BFile file(&ref, B_READ_ONLY);
	BAppFileInfo appMime(&file);
	if (appMime.InitCheck() != B_OK)
		return NULL;

	BBitmap* icon = new BBitmap(BRect(0.0, 0.0, 63.0, 63.0), B_RGBA32);
	if (appMime.GetIcon(icon, (icon_size)64) == B_OK)
		return icon;

	delete icon;
	return NULL;
}


//	#pragma mark - AboutView public methods


BBitmap*
AboutView::Icon()
{
	if (fStripeView == NULL)
		return NULL;

	return fStripeView->Icon();
}


status_t
AboutView::SetIcon(BBitmap* icon)
{
	if (fStripeView == NULL)
		return B_NO_INIT;

	fStripeView->SetIcon(icon);

	return B_OK;
}


const char*
AboutView::Name()
{
	return fNameView->Text();
}


status_t
AboutView::SetName(const char* name)
{
	fNameView->SetText(name);

	return B_OK;
}


const char*
AboutView::Version()
{
	return fVersionView->Text();
}


status_t
AboutView::SetVersion(const char* version)
{
	fVersionView->SetText(version);

	return B_OK;
}

} // namespace BPrivate


//	#pragma mark - BAboutWindow


BAboutWindow::BAboutWindow(const char* appName, const char* signature)
	:
	BWindow(BRect(0.0, 0.0, 400.0, 200.0), appName, B_MODAL_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE
			| B_AUTO_UPDATE_SIZE_LIMITS | B_CLOSE_ON_ESCAPE)
{
	SetLayout(new BGroupLayout(B_VERTICAL));

	const char* about = B_TRANSLATE_MARK("About %app%");
	about = gSystemCatalog.GetString(about, "AboutWindow");

	BString title(about);
	title.ReplaceFirst("%app%", appName);
	SetTitle(title.String());

	fAboutView = new BPrivate::AboutView(appName, signature);
	AddChild(fAboutView);

	MoveTo(AboutPosition(Frame().Width(), Frame().Height()));
}


BAboutWindow::~BAboutWindow()
{
	fAboutView->RemoveSelf();
	delete fAboutView;
	fAboutView = NULL;
}


//	#pragma mark - BAboutWindow virtual methods


void
BAboutWindow::Show()
{
	if (IsHidden()) {
		// move to current workspace
		SetWorkspaces(B_CURRENT_WORKSPACE);
	}

	BWindow::Show();
}


//	#pragma mark - BAboutWindow public methods


BPoint
BAboutWindow::AboutPosition(float width, float height)
{
	BPoint result(100, 100);

	BWindow* window =
		dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL)));

	BScreen screen(window);
	BRect screenFrame(0, 0, 640, 480);
	if (screen.IsValid())
		screenFrame = screen.Frame();

	// Horizontally, we're smack in the middle
	result.x = screenFrame.left + (screenFrame.Width() / 2.0) - (width / 2.0);

	// This is probably sooo wrong, but it looks right on 1024 x 768
	result.y = screenFrame.top + (screenFrame.Height() / 4.0)
		- ceil(height / 3.0);

	return result;
}


void
BAboutWindow::AddDescription(const char* description)
{
	if (description == NULL)
		return;

	AddText(description);
}


void
BAboutWindow::AddCopyright(int32 firstCopyrightYear,
	const char* copyrightHolder, const char** extraCopyrights)
{
	BString copyright(B_UTF8_COPYRIGHT " %years% %holder%");

	// Get current year
	time_t tp;
	time(&tp);
	char currentYear[5];
	strftime(currentYear, 5, "%Y", localtime(&tp));
	BString copyrightYears;
	copyrightYears << firstCopyrightYear;
	if (copyrightYears != currentYear)
		copyrightYears << "-" << currentYear;

	BString text("");
	if (fAboutView->InfoView()->TextLength() > 0)
		text << "\n\n";

	text << copyright;

	// Fill out the copyright year placeholder
	text.ReplaceAll("%years%", copyrightYears.String());

	// Fill in the copyright holder placeholder
	text.ReplaceAll("%holder%", copyrightHolder);

	// Add extra copyright strings
	if (extraCopyrights != NULL) {
		// Add optional extra copyright information
		for (int32 i = 0; extraCopyrights[i]; i++)
			text << "\n" << B_UTF8_COPYRIGHT << " " << extraCopyrights[i];
	}

	const char* allRightsReserved = B_TRANSLATE_MARK("All Rights Reserved.");
	allRightsReserved = gSystemCatalog.GetString(allRightsReserved,
		"AboutWindow");
	text << "\n    " << allRightsReserved;

	fAboutView->InfoView()->Insert(text.String());
}


void
BAboutWindow::AddAuthors(const char** authors)
{
	if (authors == NULL)
		return;

	const char* writtenBy = B_TRANSLATE_MARK("Written by:");
	writtenBy = gSystemCatalog.GetString(writtenBy, "AboutWindow");

	AddText(writtenBy, authors);
}


void
BAboutWindow::AddSpecialThanks(const char** thanks)
{
	if (thanks == NULL)
		return;

	const char* specialThanks = B_TRANSLATE_MARK("Special Thanks:");
	specialThanks = gSystemCatalog.GetString(specialThanks, "AboutWindow");

	AddText(specialThanks, thanks);
}


void
BAboutWindow::AddVersionHistory(const char** history)
{
	if (history == NULL)
		return;

	const char* versionHistory = B_TRANSLATE_MARK("Version history:");
	versionHistory = gSystemCatalog.GetString(versionHistory, "AboutWindow");

	AddText(versionHistory, history);
}


void
BAboutWindow::AddExtraInfo(const char* extraInfo)
{
	if (extraInfo == NULL)
		return;

	const char* appExtraInfo = B_TRANSLATE_MARK(extraInfo);
	appExtraInfo = gSystemCatalog.GetString(extraInfo, "AboutWindow");

	BString extra("");
	if (fAboutView->InfoView()->TextLength() > 0)
		extra << "\n\n";

	extra << appExtraInfo;

	fAboutView->InfoView()->Insert(extra.String());
}


void
BAboutWindow::AddText(const char* header, const char** contents)
{
	BTextView* infoView = fAboutView->InfoView();
	int32 textLength = infoView->TextLength();
	BString text("");

	if (textLength > 0) {
		text << "\n\n";
		textLength += 2;
	}

	const char* indent = "";
	if (header != NULL) {
		indent = "    ";
		text << header;
	}

	if (contents != NULL) {
		text << "\n";
		for (int32 i = 0; contents[i]; i++)
			text << indent << contents[i] << "\n";
	}

	infoView->Insert(text.String());

	if (contents != NULL && header != NULL) {
		infoView->SetFontAndColor(textLength, textLength + strlen(header),
			be_bold_font);
	}
}


BBitmap*
BAboutWindow::Icon()
{
	return fAboutView->Icon();
}


void
BAboutWindow::SetIcon(BBitmap* icon)
{
	fAboutView->SetIcon(icon);
}


const char*
BAboutWindow::Name()
{
	return fAboutView->Name();
}


void
BAboutWindow::SetName(const char* name)
{
	fAboutView->SetName(name);
}


const char*
BAboutWindow::Version()
{
	return fAboutView->Version();
}


void
BAboutWindow::SetVersion(const char* version)
{
	fAboutView->SetVersion(version);
}


// FBC padding

void BAboutWindow::_ReservedAboutWindow20() {}
void BAboutWindow::_ReservedAboutWindow19() {}
void BAboutWindow::_ReservedAboutWindow18() {}
void BAboutWindow::_ReservedAboutWindow17() {}
void BAboutWindow::_ReservedAboutWindow16() {}
void BAboutWindow::_ReservedAboutWindow15() {}
void BAboutWindow::_ReservedAboutWindow14() {}
void BAboutWindow::_ReservedAboutWindow13() {}
void BAboutWindow::_ReservedAboutWindow12() {}
void BAboutWindow::_ReservedAboutWindow11() {}
void BAboutWindow::_ReservedAboutWindow10() {}
void BAboutWindow::_ReservedAboutWindow9() {}
void BAboutWindow::_ReservedAboutWindow8() {}
void BAboutWindow::_ReservedAboutWindow7() {}
void BAboutWindow::_ReservedAboutWindow6() {}
void BAboutWindow::_ReservedAboutWindow5() {}
void BAboutWindow::_ReservedAboutWindow4() {}
void BAboutWindow::_ReservedAboutWindow3() {}
void BAboutWindow::_ReservedAboutWindow2() {}
void BAboutWindow::_ReservedAboutWindow1() {}
