/**************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_cui.hxx"

#include <comphelper/processfactory.hxx>
#include <dialmgr.hxx>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
//#include <rtl/ustrbuf.hxx>
#include <sfx2/sfxcommands.h>
#include <sfx2/sfxdefs.hxx>
#include <sfx2/sfxuno.hxx>
#include <svtools/filter.hxx>
#include <svtools/svtools.hrc>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
#include <unotools/bootstrap.hxx>
#include <unotools/configmgr.hxx>
#include <vcl/graph.hxx>
#include <vcl/imagerepository.hxx>
#include <vcl/msgbox.hxx>
#include <vcl/svapp.hxx>
#include <vcl/tabctrl.hxx>
#include <vcl/tabdlg.hxx>
#include <vcl/tabpage.hxx>

#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
#include <com/sun/star/system/SystemShellExecute.hpp>
#include <com/sun/star/uno/Any.h>

#include "about.hxx"
#include "about.hrc"

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)

/* On Windows/OS2, all the three files have .txt extension
   and the README file name is in lowercase
   Readme files are localized and have the locale in their file name:
   README_de README_en-US
*/
#if defined(WNT) || defined(OS2)
#define FILE_EXTENSION  ".txt"
#define README_FILE     "readme"
#else
#define FILE_EXTENSION
#define README_FILE     "README"
#endif
#define LICENSE_FILE    "LICENSE" FILE_EXTENSION
#define NOTICE_FILE     "NOTICE"  FILE_EXTENSION

// Dir where the files are located
#define OOO_DIR_SHARE_README  "${OOO_BASE_DIR}/share/readme/"

using namespace com::sun::star;

namespace
{

	static void lcl_layoutFixedText( FixedText &rControl,
									 const Point& aPos,
									 Size &aSize,
									 const long nTextWidth )
	{
		aSize = rControl.GetSizePixel();
		// change the width
		aSize.Width() = nTextWidth;
		// set Position and Size, to calculate the minimum size
		// this will update the Height
		rControl.SetPosSizePixel( aPos, aSize );
		aSize = rControl.CalcMinimumSize();
		// update the size with the right Height
		rControl.SetSizePixel( aSize );
	}

	static void lcl_layoutEdit( Edit &rControl,
								const Point& aPos,
								Size &aSize,
								const long nTextWidth )
	{
		aSize = rControl.GetSizePixel();
		// change the width
		aSize.Width() = nTextWidth;
		// set Position and Size, to calculate the minimum size
		// this will update the Height
		rControl.SetPosSizePixel( aPos, aSize );
		aSize = rControl.CalcMinimumSize();
		// update the size with the right Height
		rControl.SetSizePixel( aSize );
	}

	static void lcl_readTxtFile( const rtl::OUString &rFile, rtl::OUString &sText )
	{
		rtl::OUString sFile( rFile );
		rtl::Bootstrap::expandMacros( sFile );
		osl::File aFile(sFile);
		if ( aFile.open(OpenFlag_Read) == osl::FileBase::E_None )
		{
			osl::DirectoryItem aItem;
			osl::DirectoryItem::get(sFile, aItem);

			osl::FileStatus aStatus(FileStatusMask_FileSize);
			aItem.getFileStatus(aStatus);

			sal_uInt64 nBytesRead = 0;
			sal_uInt64 nPosition = 0;
			sal_uInt32 nBytes = (sal_uInt32)aStatus.getFileSize();

			sal_Char *pBuffer = new sal_Char[nBytes];

			while ( aFile.read( pBuffer + nPosition,
								nBytes-nPosition,
								nBytesRead ) == osl::FileBase::E_None
					&& nPosition + nBytesRead < nBytes)
			{
				nPosition += nBytesRead;
			}

			OSL_ENSURE( nBytes < STRING_MAXLEN, "Text file has too much bytes!" );
			if ( nBytes > STRING_MAXLEN )
				nBytes = STRING_MAXLEN - 1;

			sText = rtl::OUString( pBuffer,
								nBytes,
								RTL_TEXTENCODING_UTF8,
								OSTRING_TO_OUSTRING_CVTFLAGS
								| RTL_TEXTTOUNICODE_FLAGS_GLOBAL_SIGNATURE);
			delete[] pBuffer;
		}
	}

	class ReadmeDialog;

	class ReadmeTabPage : public TabPage
	{
	private:
		MultiLineEdit maText;
		String        msText;

	public:
		ReadmeTabPage(Window *pParent, const String &sText);
		~ReadmeTabPage();

		void Adjust(const Size &aSz, const Size &a6Size);
	};

	ReadmeTabPage::ReadmeTabPage(Window *pParent, const String &sText)
		: TabPage(pParent, CUI_RES( RID_CUI_README_TBPAGE))
		,maText( this, CUI_RES( RID_CUI_README_TBPAGE_EDIT ))
		,msText( sText )
	{
		FreeResource();

		maText.SetText(msText);
		maText.Show();
	}

	ReadmeTabPage::~ReadmeTabPage()
	{
	}

	void ReadmeTabPage::Adjust(const Size &aSz, const Size &a6Size)
	{
		long nDlgMargin  = a6Size.Width() * 2;
		long nCtrlMargin = a6Size.Height() * 2;
		maText.SetPosPixel( Point(a6Size.Width(), a6Size.Height()) );
		maText.SetSizePixel( Size(aSz.Width() - nDlgMargin, aSz.Height() - nCtrlMargin) );
	}

	class ReadmeDialog : public ModalDialog
	{
	private:
		TabControl      maTabCtrl;
		OKButton        maBtnOK;

		ReadmeTabPage  *maReadmeTabPage;
		ReadmeTabPage  *maLicenseTabPage;
		ReadmeTabPage  *maNoticeTabPage;

		DECL_LINK( ActivatePageHdl, TabControl * );
		DECL_LINK( DeactivatePageHdl, TabControl * );

	public:
		ReadmeDialog( Window* );
		~ReadmeDialog();
	};

	ReadmeDialog::ReadmeDialog( Window * pParent )
		: ModalDialog( pParent, CUI_RES( RID_CUI_README_DLG ) )
		, maTabCtrl( this, CUI_RES(RID_CUI_README_TBCTL) )
		, maBtnOK( this, CUI_RES(RID_CUI_README_OKBTN) )
		, maReadmeTabPage(0)
		, maLicenseTabPage(0)
		, maNoticeTabPage(0)
	{
		FreeResource();

		maTabCtrl.Show();

		// Notice and License are not localized
		const rtl::OUString sLicense( RTL_CONSTASCII_USTRINGPARAM( OOO_DIR_SHARE_README LICENSE_FILE ) );
		const rtl::OUString sNotice( RTL_CONSTASCII_USTRINGPARAM(  OOO_DIR_SHARE_README NOTICE_FILE ) );

		// get localized README
		rtl::OUStringBuffer aBuff;
		lang::Locale aLocale = Application::GetSettings().GetUILocale();
		aBuff.appendAscii( RTL_CONSTASCII_STRINGPARAM( OOO_DIR_SHARE_README README_FILE "_" ) );
		aBuff.append( aLocale.Language );
		if ( aLocale.Country.getLength() )
		{
			aBuff.append( sal_Unicode( '-') );
			aBuff.append( aLocale.Country );
			if ( aLocale.Variant.getLength() )
			{
				aBuff.append( sal_Unicode( '-' ) );
				aBuff.append( aLocale.Variant );
			}
		}
#if defined(WNT) || defined(OS2)
		aBuff.appendAscii( RTL_CONSTASCII_STRINGPARAM( FILE_EXTENSION ) );
#endif

		rtl::OUString sReadmeTxt, sLicenseTxt, sNoticeTxt;
		lcl_readTxtFile( aBuff.makeStringAndClear(), sReadmeTxt );
		lcl_readTxtFile( sLicense, sLicenseTxt );
		lcl_readTxtFile( sNotice, sNoticeTxt );

		maReadmeTabPage = new ReadmeTabPage( &maTabCtrl, sReadmeTxt );
		maLicenseTabPage = new ReadmeTabPage( &maTabCtrl, sLicenseTxt );
		maNoticeTabPage = new ReadmeTabPage( &maTabCtrl, sNoticeTxt );

		maTabCtrl.SetTabPage( RID_CUI_READMEPAGE, maReadmeTabPage );
		maTabCtrl.SetTabPage( RID_CUI_LICENSEPAGE, maLicenseTabPage );
		maTabCtrl.SetTabPage( RID_CUI_NOTICEPAGE, maNoticeTabPage );

		maTabCtrl.SelectTabPage( RID_CUI_READMEPAGE );

		Size aTpSz  = maReadmeTabPage->GetOutputSizePixel();
		Size a6Size = maReadmeTabPage->LogicToPixel( Size( 6, 6 ), MAP_APPFONT );

		maReadmeTabPage->Adjust( aTpSz, a6Size );
		maLicenseTabPage->Adjust( aTpSz, a6Size );
		maNoticeTabPage->Adjust( aTpSz, a6Size );

		Size aDlgSize = GetOutputSizePixel();
		Size aOkBtnSz = maBtnOK.GetSizePixel();
		Point aOKPnt( aDlgSize.Width() / 2 - aOkBtnSz.Width() / 2 , maBtnOK.GetPosPixel().Y() );
		maBtnOK.SetPosPixel( aOKPnt );
	}

	ReadmeDialog::~ReadmeDialog()
	{
		delete maReadmeTabPage;
		delete maLicenseTabPage;
		delete maNoticeTabPage;
	}
}

// -----------------------------------------------------------------------

AboutDialog::AboutDialog( Window* pParent, const ResId & rId ) :
	SfxModalDialog( pParent, rId ),
	maOKButton( this, ResId( RID_CUI_ABOUT_BTN_OK, *rId.GetResMgr() ) ),
	maReadmeButton( this, ResId( RID_CUI_ABOUT_BTN_README, *rId.GetResMgr() ) ),
	maVersionText( this, ResId( RID_CUI_ABOUT_FTXT_VERSION, *rId.GetResMgr() ) ),
	maBuildInfoEdit( this, ResId( RID_CUI_ABOUT_FTXT_BUILDDATA, *rId.GetResMgr() ) ),
	maCopyrightEdit( this, ResId( RID_CUI_ABOUT_FTXT_COPYRIGHT, *rId.GetResMgr() ) ),
	maCreditsLink( this, ResId( RID_CUI_ABOUT_FTXT_WELCOME_LINK, *rId.GetResMgr() ) )
//	maCopyrightTextStr( ResId( RID_CUI_ABOUT_STR_COPYRIGHT, *rId.GetResMgr() ) )
{
	bool bLoad = vcl::ImageRepository::loadBrandingImage(
			rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("about")),
			maAppLogo );
	OSL_ENSURE( bLoad, "Can't load about image");

	bLoad = vcl::ImageRepository::loadBrandingImage(
			rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("logo")),
			maMainLogo );
	OSL_ENSURE( bLoad, "Can't load logo image");

	const String vendor( ResId( RID_CUI_ABOUT_STR_COPYRIGHT_VENDOR, *rId.GetResMgr() ) );
	String createdRes( ResId( RID_CUI_ABOUT_STR_CREATED, *rId.GetResMgr() ) );
	if ( !vendor.EqualsAscii("Apache Software Foundation") ) {
		createdRes = String( ResId( RID_CUI_ABOUT_STR_CREATED_VENDOR, *rId.GetResMgr() ));
	}
	const String copyrightAcknowledge( ResId( RID_CUI_ABOUT_STR_ACKNOWLEDGE, *rId.GetResMgr() ) );

	rtl::OUStringBuffer sbcopyright(250);
	sbcopyright.appendAscii("Copyright ");
	sbcopyright.append((sal_Unicode)0x00a9);
	sbcopyright.appendAscii(" ");
	rtl::OUString sYear( RTL_CONSTASCII_USTRINGPARAM("2026") );
	if (vendor.EqualsAscii("Apache Software Foundation")) {
		sbcopyright.append(sYear);
		sbcopyright.appendAscii(" The Apache Software Foundation.\n\n");
	} else {
#ifdef COPYRIGHT_YEAR
		const rtl::OUString sDefYear( RTL_CONSTASCII_USTRINGPARAM( STRINGIFY( COPYRIGHT_YEAR ) ) );
		if ( sDefYear.getLength() > 0 )
		{
			sYear = sDefYear;
		}
#endif
		sbcopyright.append(sYear);
		sbcopyright.appendAscii(" ");
		sbcopyright.append(vendor);
		sbcopyright.appendAscii(".\nPortion copyright The Apache Software Foundation.\n\n");
	}
	sbcopyright.append( createdRes );
	sbcopyright.appendAscii("\n\n");
	sbcopyright.append( copyrightAcknowledge );
	maCopyrightTextStr = sbcopyright.makeStringAndClear();

	InitControls();

	// set links
	maReadmeButton.SetClickHdl( LINK( this, AboutDialog, ShowReadme_Impl ) );
	maCreditsLink.SetClickHdl( LINK( this, AboutDialog, OpenLinkHdl_Impl ) );

	FreeResource();

	SetHelpId( CMD_SID_ABOUT );
}

// -----------------------------------------------------------------------

AboutDialog::~AboutDialog()
{
}

// -----------------------------------------------------------------------

void AboutDialog::InitControls()
{
	// apply font, background et al.
	ApplyStyleSettings();

	// set strings
	maCopyrightEdit.SetText( maCopyrightTextStr );
	maBuildInfoEdit.SetText( GetBuildVersionString() );
	maCreditsLink.SetURL( maCreditsLink.GetText() );

	// determine size and position of the dialog & elements
	Size aDlgSize;
	LayoutControls( aDlgSize );

	// Change the width of the dialog
	SetOutputSizePixel( aDlgSize );
}

// -----------------------------------------------------------------------

void AboutDialog::ApplyStyleSettings()
{
	// transparent font
	Font aFont = GetFont();
	aFont.SetTransparent( sal_True );
	SetFont( aFont );

	// set for background and text the correct system color
	const StyleSettings& rSettings = GetSettings().GetStyleSettings();
	Color aWindowColor( rSettings.GetWindowColor() );
	Wallpaper aWall( aWindowColor );
	SetBackground( aWall );

	Font aNewFont( maCopyrightEdit.GetFont() );
	aNewFont.SetTransparent( sal_True );

	maVersionText.SetFont( aNewFont );
	maCopyrightEdit.SetFont( aNewFont );

	maVersionText.SetBackground(aWall);
	maCopyrightEdit.SetBackground(aWall);
	maBuildInfoEdit.SetBackground(aWall);
	maCreditsLink.SetBackground(aWall);

	Color aTextColor( rSettings.GetWindowTextColor() );
	maVersionText.SetControlForeground( aTextColor );
	maCopyrightEdit.SetControlForeground( aTextColor );
	maBuildInfoEdit.SetControlForeground( aTextColor );
	maCreditsLink.SetControlForeground();

	Size aSmaller = aNewFont.GetSize();
	aSmaller.Width() = (long) (aSmaller.Width() * 0.75);
	aSmaller.Height() = (long) (aSmaller.Height() * 0.75);
	aNewFont.SetSize( aSmaller );

	maBuildInfoEdit.SetFont( aNewFont );

	// the following is a hack to force the MultiLineEdit update its settings
	// in order to reflect the Font
	// See
	//      Window::SetControlFont
	//      MultiLineEdit::StateChanged
	//      MultiLineEdit::ImplInitSettings
	// TODO Override SetFont in MultiLineEdit and do the following,
	// otherwise SetFont has no effect at all!
	aSmaller = PixelToLogic( aSmaller, MAP_POINT );
	aNewFont.SetSize( aSmaller );
	maBuildInfoEdit.SetControlFont( aNewFont );
}

// -----------------------------------------------------------------------

void AboutDialog::LayoutControls( Size& aDlgSize )
{
	Size aMainLogoSz = maMainLogo.GetSizePixel();
	Size aAppLogoSiz = maAppLogo.GetSizePixel();

	aDlgSize = GetOutputSizePixel();
	long nCol1 = aMainLogoSz.Width();
	long nCol2 = aAppLogoSiz.Width() ? aAppLogoSiz.Width() : aDlgSize.Width();

	Size a6Size      = maVersionText.LogicToPixel( Size( 6, 6 ), MAP_APPFONT );
	long nDlgMargin  = a6Size.Width() * 2;
	long nCtrlMargin = a6Size.Height() * 2;
	long nTextWidth  = nCol2 - nDlgMargin;
	long nY          = aAppLogoSiz.Height() + a6Size.Height();

	aDlgSize.Width() = nCol1 + a6Size.Width() + nCol2;

	Point aPos( nCol1 + a6Size.Width(), nY );
	Size aSize;
	// layout fixed text control
	lcl_layoutFixedText( maVersionText, aPos, aSize, nTextWidth );
	nY += aSize.Height() + a6Size.Height();

	// Multiline edit with Build info
	aPos.Y() = nY;
	lcl_layoutEdit( maBuildInfoEdit, aPos, aSize, nTextWidth );
	nY += aSize.Height() + a6Size.Height();

	// Multiline edit with Copyright-Text
	aPos.Y() = nY;
	lcl_layoutEdit( maCopyrightEdit, aPos, aSize, nTextWidth );
	nY += aSize.Height() + a6Size.Height();

	// Hyperlink
	aPos.Y() = nY;
	lcl_layoutFixedText( maCreditsLink, aPos, aSize, nTextWidth );
	nY += aSize.Height();

	nY = std::max( nY, aMainLogoSz.Height() );
	nY += nCtrlMargin;

	// logos position
	maMainLogoPos = Point( 0, nY / 2 - aMainLogoSz.Height() / 2 );
	maAppLogoPos = Point( nCol1 + a6Size.Width(), 0 );

	// OK-Button-Position (at the bottom and centered)
	Size aOKSiz = maOKButton.GetSizePixel();
	Point aOKPnt( ( aDlgSize.Width() - aOKSiz.Width() ) - a6Size.Width(), nY );
	maOKButton.SetPosPixel( aOKPnt );

	maReadmeButton.SetPosPixel( Point(a6Size.Width(), nY) );

	aDlgSize.Height() = aOKPnt.Y() + aOKSiz.Height() + a6Size.Width();
}

// -----------------------------------------------------------------------

const rtl::OUString AboutDialog::GetBuildId() const
{
	rtl::OUString sDefault;

	// Get buildid from version[rc|.ini]
	rtl::OUString sBuildId( utl::Bootstrap::getBuildIdData( sDefault ) );
	OSL_ENSURE( sBuildId.getLength() > 0, "No BUILDID in bootstrap file" );
	rtl::OUStringBuffer sBuildIdBuff( sBuildId );

	// Get ProductSource from version[rc|.ini]
	rtl::OUString sProductSource( utl::Bootstrap::getProductSource( sDefault ) );
	OSL_ENSURE( sProductSource.getLength() > 0, "No ProductSource in bootstrap file" );

	// the product source is something like "AOO340",
	// while the build id is something like "340m1(Build:9590)"
	// For better readability, strip the duplicate ProductMajor ("340").
	if ( sProductSource.getLength() )
	{
		sal_Int32 nMajorLength = sProductSource.getLength() - 3;
		bool bMatchingUPD =
				( sProductSource.getLength() >= 3 )
			&&	( sBuildId.getLength() >= nMajorLength )
			&&	( sProductSource.copy( 3 ) == sBuildId.copy( 0, nMajorLength ) );
		OSL_ENSURE( bMatchingUPD, "BUILDID and ProductSource do not match in their UPD" );
		if ( bMatchingUPD )
			sProductSource = sProductSource.copy( 0, sProductSource.getLength() - nMajorLength );

		// prepend the product source
		sBuildIdBuff.insert( 0, sProductSource );
	}

	return sBuildIdBuff.makeStringAndClear();
}

// -----------------------------------------------------------------------

const rtl::OUString AboutDialog::GetBuildVersionString() const
{
	rtl::OUStringBuffer aBuildString( GetBuildId() );
	rtl::OUString sRevision( utl::Bootstrap::getRevisionInfo() );

	if ( sRevision.getLength() > 0 )
	{
		aBuildString.appendAscii( RTL_CONSTASCII_STRINGPARAM( "  -  Rev. " ) );
		aBuildString.append( sRevision );
	}

#ifdef BUILD_VER_STRING
	rtl::OUString sBuildVer( RTL_CONSTASCII_USTRINGPARAM( STRINGIFY( BUILD_VER_STRING ) ) );
	if ( sBuildVer.getLength() > 0 )
	{
		aBuildString.append( sal_Unicode( '\n' ) );
		aBuildString.append( sBuildVer );
	}
#endif

	return aBuildString.makeStringAndClear();
}

// -----------------------------------------------------------------------

sal_Bool AboutDialog::Close()
{
	EndDialog( RET_OK );
	return( sal_False );
}

// -----------------------------------------------------------------------

void AboutDialog::Paint( const Rectangle& rRect )
{
	SetClipRegion( rRect );

	// workaround to ensure that the background is painted correct
	// on MacOS for example the background was gray and the image and other controls white
	SetFillColor(GetSettings().GetStyleSettings().GetWindowColor());
	SetLineColor();
	DrawRect(rRect);

	DrawImage( maMainLogoPos, maMainLogo );
	DrawImage( maAppLogoPos, maAppLogo );

	return;
}

// -----------------------------------------------------------------------

IMPL_LINK ( AboutDialog, OpenLinkHdl_Impl, svt::FixedHyperlink*, EMPTYARG )
{
	::rtl::OUString sURL( maCreditsLink.GetURL() );
	if ( sURL.getLength() > 0 )
	{
		try
		{
			uno::Reference< com::sun::star::system::XSystemShellExecute > xSystemShell(
				com::sun::star::system::SystemShellExecute::create(
					::comphelper::getProcessComponentContext() ) );
			if ( xSystemShell.is() )
				xSystemShell->execute( sURL, rtl::OUString(), com::sun::star::system::SystemShellExecuteFlags::DEFAULTS );
		}
		catch( const uno::Exception& e )
		{
			OSL_TRACE( "Caught exception: %s\n thread terminated.\n",
				rtl::OUStringToOString(e.Message, RTL_TEXTENCODING_UTF8).getStr());
		}
	}

	return 0;
}

IMPL_LINK ( AboutDialog, ShowReadme_Impl, PushButton*, EMPTYARG )
{
	ReadmeDialog aDlg( this );
	aDlg.Execute();

	return 0;
}

/* vim: set noet sw=4 ts=4: */
