/**************************************************************
 * 
 * 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_framework.hxx"

//_________________________________________________________________________________________________________________
//	my own includes
//_________________________________________________________________________________________________________________
#include <services/license.hxx>
#include <threadhelp/resetableguard.hxx>
#include <macros/debug.hxx>
#include <services.h>

// local header for UI implementation
#include "services/licensedlg.hxx"
#include "classes/resource.hrc"

//_________________________________________________________________________________________________________________
//	interface includes
//_________________________________________________________________________________________________________________

#include <com/sun/star/frame/XDesktop.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/util/XChangesBatch.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/lang/XComponent.hpp>


//_________________________________________________________________________________________________________________
//	includes of other projects
//_________________________________________________________________________________________________________________

#include <rtl/ustrbuf.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/ustring.hxx>
#include <rtl/string.hxx>
#include <unotools/bootstrap.hxx>
#include <osl/file.hxx>
#include <svtools/xtextedt.hxx>
#include <vcl/svapp.hxx>
#include <comphelper/processfactory.hxx>
#include <tools/date.hxx>
#include <tools/time.hxx>
#include <tools/datetime.hxx>
#include <osl/file.hxx>
#include <osl/time.h>

//_________________________________________________________________________________________________________________
//	namespace
//_________________________________________________________________________________________________________________

namespace framework{
using namespace utl;
using namespace ::osl							;
using namespace ::cppu							;
using namespace ::com::sun::star::uno			;
using namespace ::com::sun::star::beans			;
using namespace ::com::sun::star::lang			;
using namespace ::com::sun::star::util			;
using namespace ::com::sun::star::frame 		;

//_________________________________________________________________________________________________________________
//	non exported const
//_________________________________________________________________________________________________________________

// license file name
static const char *szLicensePath = "/share/readme";
#ifdef UNX
static const char *szUNXLicenseName = "/LICENSE";
static const char *szUNXLicenseExt = "";
#elif defined(WNT) || defined(OS2)
static const char *szWNTLicenseName = "/license";
static const char *szWNTLicenseExt = ".txt";
#endif

//_________________________________________________________________________________________________________________
//	non exported definitions
//_________________________________________________________________________________________________________________

//_________________________________________________________________________________________________________________
//	declarations
//_________________________________________________________________________________________________________________

//*****************************************************************************************************************
//	constructor
//*****************************************************************************************************************
License::License( const Reference< XMultiServiceFactory >& xFactory )
		//	Init baseclasses first
		//	Attention:
		//		Don't change order of initialization!
		//      ThreadHelpBase is a struct with a mutex as member. We can't use a mutex as member, while
		//		we must garant right initialization and a valid value of this! First initialize
		//		baseclasses and then members. And we need the mutex for other baseclasses !!!
        :   ThreadHelpBase  ( &Application::GetSolarMutex() )
        ,   OWeakObject     (                               )
		// Init member
        ,   m_xFactory      ( xFactory                      )
        ,   m_bTerminate    ( sal_False                     )
{
}

//*****************************************************************************************************************
//	destructor
//*****************************************************************************************************************
License::~License()
{
}

//*****************************************************************************************************************
//	XInterface, XTypeProvider, XServiceInfo
//*****************************************************************************************************************

DEFINE_XINTERFACE_4					(	License						   ,
										OWeakObject					   ,
										DIRECT_INTERFACE(XTypeProvider ),
										DIRECT_INTERFACE(XServiceInfo  ),
										DIRECT_INTERFACE(XJob	       ),
                                        DIRECT_INTERFACE(XCloseable    )
									)

DEFINE_XTYPEPROVIDER_4				(	License	,
										XTypeProvider	,
										XServiceInfo	,
										XJob            ,
                                        XCloseable
									)

DEFINE_XSERVICEINFO_MULTISERVICE	(	License,
                                        OWeakObject                 ,
										SERVICENAME_LICENSE			,
										IMPLEMENTATIONNAME_LICENSE
									)

DEFINE_INIT_SERVICE                 (   License,
                                        {
                                        }
                                    )


#if 0
IMPL_STATIC_LINK_NOINSTANCE( License, Terminate, void*, EMPTYARG )
{
    /*
    Reference< XMultiServiceFactory > xFactory = comphelper::getProcessServiceFactory();
    Reference< XDesktop > xDesktop(xFactory->createInstance(
        ::rtl::OUString::createFromAscii("com.sun.star.frame.Desktop")), UNO_QUERY);
    if (xDesktop.is())
        xDesktop->terminate();
    */
    /*
    _exit(0);
    */
	return 0;
}
#endif

static DateTime _oslDateTimeToDateTime(const oslDateTime& aDateTime)
{
    return DateTime(
        Date(aDateTime.Day, aDateTime.Month, aDateTime.Year),
        Time(aDateTime.Hours, aDateTime.Minutes, aDateTime.Seconds));
}

static ::rtl::OUString _makeDateTimeString (const DateTime& aDateTime, sal_Bool bUTC = sal_False)
{
    ::rtl::OStringBuffer aDateTimeString;
    aDateTimeString.append((sal_Int32)aDateTime.GetYear());
    aDateTimeString.append("-");
    if (aDateTime.GetMonth()<10) aDateTimeString.append("0");
    aDateTimeString.append((sal_Int32)aDateTime.GetMonth());
    aDateTimeString.append("-");
    if (aDateTime.GetDay()<10) aDateTimeString.append("0");
    aDateTimeString.append((sal_Int32)aDateTime.GetDay());
    aDateTimeString.append("T");
    if (aDateTime.GetHour()<10) aDateTimeString.append("0");
    aDateTimeString.append((sal_Int32)aDateTime.GetHour());
    aDateTimeString.append(":");
    if (aDateTime.GetMin()<10) aDateTimeString.append("0");
    aDateTimeString.append((sal_Int32)aDateTime.GetMin());
    aDateTimeString.append(":");
    if (aDateTime.GetSec()<10) aDateTimeString.append("0");
    aDateTimeString.append((sal_Int32)aDateTime.GetSec());
    if (bUTC) aDateTimeString.append("Z");

    return OStringToOUString(aDateTimeString.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US);
}

static sal_Bool _parseDateTime(const ::rtl::OUString& aString, DateTime& aDateTime)
{
    // take apart a canonical literal xsd:dateTime string
    //CCYY-MM-DDThh:mm:ss(Z)

    ::rtl::OUString aDateTimeString = aString.trim();

    // check length
    if (aDateTimeString.getLength() < 19 || aDateTimeString.getLength() > 20)
        return sal_False;

    sal_Int32 nDateLength = 10;
    sal_Int32 nTimeLength = 8;

    ::rtl::OUString aDateTimeSep = ::rtl::OUString::createFromAscii("T");
    ::rtl::OUString aDateSep = ::rtl::OUString::createFromAscii("-");
    ::rtl::OUString aTimeSep = ::rtl::OUString::createFromAscii(":");
    ::rtl::OUString aUTCString = ::rtl::OUString::createFromAscii("Z");

    ::rtl::OUString aDateString = aDateTimeString.copy(0, nDateLength);
    ::rtl::OUString aTimeString = aDateTimeString.copy(nDateLength+1, nTimeLength);

    sal_Int32 nIndex = 0;
    sal_Int32 nYear = aDateString.getToken(0, '-', nIndex).toInt32();
    sal_Int32 nMonth = aDateString.getToken(0, '-', nIndex).toInt32();
    sal_Int32 nDay = aDateString.getToken(0, '-', nIndex).toInt32();
    nIndex = 0;
    sal_Int32 nHour = aTimeString.getToken(0, ':', nIndex).toInt32();
    sal_Int32 nMinute = aTimeString.getToken(0, ':', nIndex).toInt32();
    sal_Int32 nSecond = aTimeString.getToken(0, ':', nIndex).toInt32();

    Date tmpDate((sal_uInt16)nDay, (sal_uInt16)nMonth, (sal_uInt16)nYear);
    Time tmpTime(nHour, nMinute, nSecond);
    DateTime tmpDateTime(tmpDate, tmpTime);
    if (aString.indexOf(aUTCString) < 0)
        tmpDateTime.ConvertToUTC();

    aDateTime = tmpDateTime;
    return sal_True;
}

static ::rtl::OUString _getCurrentDateString()
{
    ::rtl::OUString aString;
    return _makeDateTimeString(DateTime());
}

// execution of license check...
css::uno::Any SAL_CALL License::execute(const css::uno::Sequence< css::beans::NamedValue >& )
    throw( css::lang::IllegalArgumentException, css::uno::Exception)
{
    // return value
    Any aRet; aRet <<= sal_False;

    try
    {
        ::rtl::OUString aBaseInstallPath;
        Bootstrap::PathStatus aBaseLocateResult =
            Bootstrap::locateBaseInstallation(aBaseInstallPath);
        if (aBaseLocateResult != Bootstrap::PATH_EXISTS)
        {
            // base install noit found
            // prepare termination
            // m_bTerminate = sal_True;
            // Application::PostUserEvent( STATIC_LINK( 0, License, Terminate ) );
            aRet <<= sal_False;
            return aRet;
        }
        // determine the filename of the license to show
	    ::rtl::OUString  aLangString;
        ::com::sun::star::lang::Locale aLocale;
        ::rtl::OString aMgrName = ::rtl::OString("fwe");
        AllSettings aSettings(Application::GetSettings());
        aLocale = aSettings.GetUILocale();
        ResMgr* pResMgr = ResMgr::SearchCreateResMgr( aMgrName.getStr(), aLocale);

        aLangString = aLocale.Language;
        if ( aLocale.Country.getLength() != 0 )
        {
            aLangString += ::rtl::OUString::createFromAscii("-");
            aLangString += aLocale.Country;
	        if ( aLocale.Variant.getLength() != 0 )
            {
	            aLangString += ::rtl::OUString::createFromAscii("-");
	            aLangString += aLocale.Variant;
            }
        }
#if defined(WNT) || defined(OS2)
        ::rtl::OUString aLicensePath =
            aBaseInstallPath + ::rtl::OUString::createFromAscii(szLicensePath)
            + ::rtl::OUString::createFromAscii(szWNTLicenseName)
            + ::rtl::OUString::createFromAscii("_")
            + aLangString
            + ::rtl::OUString::createFromAscii(szWNTLicenseExt);
#else
        ::rtl::OUString aLicensePath =
            aBaseInstallPath + ::rtl::OUString::createFromAscii(szLicensePath)
            + ::rtl::OUString::createFromAscii(szUNXLicenseName)
            + ::rtl::OUString::createFromAscii("_")
            + aLangString
            + ::rtl::OUString::createFromAscii(szUNXLicenseExt);
#endif
        // check if we need to show the license at all
        // open org.openoffice.Setup/Office/ooLicenseAcceptDate
        ::rtl::OUString sConfigSrvc = SERVICENAME_CFGPROVIDER;
        ::rtl::OUString sAccessSrvc = ::rtl::OUString::createFromAscii("com.sun.star.configuration.ConfigurationUpdateAccess");
        ::rtl::OUString sReadSrvc   = SERVICENAME_CFGREADACCESS;

        // get configuration provider
        Reference< XMultiServiceFactory > theConfigProvider = Reference< XMultiServiceFactory >(
        m_xFactory->createInstance(sConfigSrvc), UNO_QUERY_THROW);
        Sequence< Any > theArgs(1);
        NamedValue v;
        v.Name = ::rtl::OUString::createFromAscii("NodePath");
        v.Value <<= ::rtl::OUString::createFromAscii("org.openoffice.Setup/Office");
        theArgs[0] <<= v;
        Reference< XPropertySet > pset = Reference< XPropertySet >(
            theConfigProvider->createInstanceWithArguments(sAccessSrvc, theArgs), UNO_QUERY_THROW);

        // if we find a date there, compare it to baseinstall license date
        ::rtl::OUString aAcceptDate;
        if (pset->getPropertyValue(::rtl::OUString::createFromAscii("ooLicenseAcceptDate")) >>= aAcceptDate)
        {
            // get LicenseFileDate from base install
            ::rtl::OUString aLicenseURL = aLicensePath;
            /*
            if (FileBase::getFileURLFromSystemPath(aLicensePath, aLicenseURL) != FileBase::E_None)
                return makeAny(sal_False);
                */
            DirectoryItem aDirItem;
            if (DirectoryItem::get(aLicenseURL, aDirItem) != FileBase::E_None)
                return makeAny(sal_False);
            FileStatus aStatus(FileStatusMask_All);
            if (aDirItem.getFileStatus(aStatus) != FileBase::E_None)
                return makeAny(sal_False);
            TimeValue aTimeVal = aStatus.getModifyTime();
            oslDateTime aDateTimeVal;
            if (!osl_getDateTimeFromTimeValue(&aTimeVal, &aDateTimeVal))
                return makeAny(sal_False);

            // compare dates
            DateTime aLicenseDateTime = _oslDateTimeToDateTime(aDateTimeVal);
            DateTime aAcceptDateTime;
            if (!_parseDateTime(aAcceptDate, aAcceptDateTime))
                return makeAny(sal_False);

            if ( aAcceptDateTime > aLicenseDateTime )
                return makeAny(sal_True);
        }
        // prepare to show
        // display license dialog
        LicenseDialog* pDialog = new LicenseDialog(aLicensePath, pResMgr);
        sal_Bool bAgreed = (pDialog->Execute() == 1);
        delete pDialog;

        if (bAgreed) {

            // write org.openoffice.Setup/ooLicenseAcceptDate
            aAcceptDate = _getCurrentDateString();
            pset->setPropertyValue(::rtl::OUString::createFromAscii("ooLicenseAcceptDate"), makeAny(aAcceptDate));
            Reference< XChangesBatch >(pset, UNO_QUERY_THROW)->commitChanges();

            // enable quickstarter
            sal_Bool bQuickstart( sal_True );
            sal_Bool bAutostart( sal_True );
            Sequence< Any > aSeq( 2 );
            aSeq[0] <<= bQuickstart;
            aSeq[1] <<= bAutostart;

            Reference < XInitialization > xQuickstart( ::comphelper::getProcessServiceFactory()->createInstance(
                ::rtl::OUString::createFromAscii( "com.sun.star.office.Quickstart" )),UNO_QUERY );
            if ( xQuickstart.is() )
                xQuickstart->initialize( aSeq );

            aRet <<= sal_True;
        }
        else
        {
            // license was not accepted
            // prepare termination
            // m_bTerminate = sal_True;
            // Application::PostUserEvent( STATIC_LINK( 0, License, Terminate ) );
            aRet <<= sal_False;
        }
    }
    catch (RuntimeException&)
    {
        // license could not be verified
        aRet <<= sal_False;
    }
    return aRet;
}

void SAL_CALL License::close(sal_Bool /*bDeliverOwnership*/) throw (css::util::CloseVetoException)
{
    if (!m_bTerminate)
        throw CloseVetoException();
}
void SAL_CALL License::addCloseListener(const css::uno::Reference< css::util::XCloseListener >&)
    throw (css::uno::RuntimeException)
{
}
void SAL_CALL License::removeCloseListener(const css::uno::Reference< css::util::XCloseListener >&)
    throw (css::uno::RuntimeException)
{
}


//************************************************************************
//   License Dialog
//************************************************************************

LicenseDialog::LicenseDialog(const ::rtl::OUString & aLicensePath, ResMgr *pResMgr) :
    ModalDialog(NULL, ResId(DLG_LICENSE, *pResMgr)),
    aLicenseML(this, ResId(ML_LICENSE, *pResMgr)),
    aInfo1FT(this, ResId(FT_INFO1, *pResMgr)),
    aInfo2FT(this, ResId(FT_INFO2, *pResMgr)),
    aInfo3FT(this, ResId(FT_INFO3, *pResMgr)),
    aInfo2_1FT(this, ResId(FT_INFO2_1, *pResMgr)),
    aInfo3_1FT(this, ResId(FT_INFO3_1, *pResMgr)),
    aFixedLine(this, ResId(FL_DIVIDE, *pResMgr)),
    aPBPageDown(this, ResId(PB_PAGEDOWN, *pResMgr)),
    aPBDecline( this, ResId(PB_DECLINE, *pResMgr) ),
    aPBAccept( this, ResId(PB_ACCEPT, *pResMgr) ),
    aArrow(this, ResId(IMG_ARROW, *pResMgr)),
    aStrAccept( ResId(LICENSE_ACCEPT, *pResMgr) ),
    aStrNotAccept( ResId(LICENSE_NOTACCEPT, *pResMgr) ),
    bEndReached(sal_False)
{
    FreeResource();

    aLicenseML.SetEndReachedHdl( LINK(this, LicenseDialog, EndReachedHdl) );
    aLicenseML.SetScrolledHdl( LINK(this, LicenseDialog, ScrolledHdl) );

    aPBPageDown.SetClickHdl( LINK(this, LicenseDialog, PageDownHdl) );
    aPBDecline.SetClickHdl( LINK(this, LicenseDialog, DeclineBtnHdl) );
    aPBAccept.SetClickHdl( LINK(this, LicenseDialog, AcceptBtnHdl) );

    // We want a automatic repeating page down button
    WinBits aStyle = aPBPageDown.GetStyle();
    aStyle |= WB_REPEAT;
    aPBPageDown.SetStyle( aStyle );

    String aText = aInfo2FT.GetText();
    aText.SearchAndReplaceAll( UniString::CreateFromAscii("%PAGEDOWN"), aPBPageDown.GetText() );
    aInfo2FT.SetText( aText );

    aPBDecline.SetText( aStrNotAccept );
    aPBAccept.SetText( aStrAccept );

    aPBAccept.Disable();

    // load license text
    File aLicenseFile(aLicensePath);
    if ( aLicenseFile.open(OpenFlag_Read) == FileBase::E_None)
    {
        DirectoryItem d;
        DirectoryItem::get(aLicensePath, d);
        FileStatus fs(FileStatusMask_FileSize);
        d.getFileStatus(fs);
        sal_uInt64 nBytesRead = 0;
        sal_uInt64 nPosition = 0;
        sal_uInt32 nBytes = (sal_uInt32)fs.getFileSize();
        sal_Char *pBuffer = new sal_Char[nBytes];
        // FileBase RC r = FileBase::E_None;
        while (aLicenseFile.read(pBuffer+nPosition, nBytes-nPosition, nBytesRead) == FileBase::E_None
            && nPosition + nBytesRead < nBytes)
        {
            nPosition += nBytesRead;
        }
        ::rtl::OUString aLicenseString(pBuffer, nBytes, RTL_TEXTENCODING_UTF8,
                OSTRING_TO_OUSTRING_CVTFLAGS | RTL_TEXTTOUNICODE_FLAGS_GLOBAL_SIGNATURE);
        delete[] pBuffer;
        aLicenseML.SetText(aLicenseString);
    }

}

LicenseDialog::~LicenseDialog()
{
}

IMPL_LINK( LicenseDialog, PageDownHdl, PushButton *, EMPTYARG )
{
    aLicenseML.ScrollDown( SCROLL_PAGEDOWN );
    return 0;
}

IMPL_LINK( LicenseDialog, EndReachedHdl, LicenseView *, EMPTYARG )
{
    bEndReached = sal_True;

    EnableControls();

    return 0;
}

IMPL_LINK( LicenseDialog, ScrolledHdl, LicenseView *, EMPTYARG )
{
    EnableControls();

    return 0;
}

IMPL_LINK( LicenseDialog, DeclineBtnHdl, PushButton *, EMPTYARG )
{
    EndDialog(0);
    return 0;
}
IMPL_LINK( LicenseDialog, AcceptBtnHdl, PushButton *, EMPTYARG )
{
    EndDialog(1);
    return 0;
}


void LicenseDialog::EnableControls()
{
    if( !bEndReached &&
        ( aLicenseML.IsEndReached() || !aLicenseML.GetText().Len() ) )
        bEndReached = sal_True;

    if ( bEndReached )
    {
        Point aPos( aInfo1FT.GetPosPixel().X(),
                aInfo3_1FT.GetPosPixel().Y() );
        aArrow.SetPosPixel( aPos );
        aPBAccept.Enable();
    }
    else
    {
        Point aPos( aInfo1FT.GetPosPixel().X(),
                aInfo2_1FT.GetPosPixel().Y() );
        aArrow.SetPosPixel( aPos );
        aPBAccept.Disable();
    }

    if ( aLicenseML.IsEndReached() )
        aPBPageDown.Disable();
    else
        aPBPageDown.Enable();

}


LicenseView::LicenseView( Window* pParent, const ResId& rResId )
    : MultiLineEdit( pParent, rResId )
{
    SetLeftMargin( 5 );

    mbEndReached = IsEndReached();

	StartListening( *GetTextEngine() );
}

LicenseView::~LicenseView()
{
    maEndReachedHdl = Link();
    maScrolledHdl   = Link();

    EndListeningAll();
}

void LicenseView::ScrollDown( ScrollType eScroll )
{
    ScrollBar*  pScroll = GetVScrollBar();

    if ( pScroll )
        pScroll->DoScrollAction( eScroll );
}

sal_Bool LicenseView::IsEndReached() const
{
    sal_Bool bEndReached;

    ExtTextView*    pView = GetTextView();
    ExtTextEngine*  pEdit = GetTextEngine();
    sal_uLong           nHeight = pEdit->GetTextHeight();
    Size            aOutSize = pView->GetWindow()->GetOutputSizePixel();
    Point           aBottom( 0, aOutSize.Height() );

    if ( (sal_uLong) pView->GetDocPos( aBottom ).Y() >= nHeight - 1 )
        bEndReached = sal_True;
    else
        bEndReached = sal_False;

    return bEndReached;
}

void LicenseView::Notify( SfxBroadcaster&, const SfxHint& rHint )
{
    if ( rHint.IsA( TYPE(TextHint) ) )
    {
        sal_Bool    bLastVal = EndReached();
        sal_uLong   nId = ((const TextHint&)rHint).GetId();

        if ( nId == TEXT_HINT_PARAINSERTED )
        {
            if ( bLastVal )
                mbEndReached = IsEndReached();
        }
        else if ( nId == TEXT_HINT_VIEWSCROLLED )
        {
            if ( ! mbEndReached )
                mbEndReached = IsEndReached();
            maScrolledHdl.Call( this );
        }

        if ( EndReached() && !bLastVal )
        {
            maEndReachedHdl.Call( this );
        }
    }
}

}		//	namespace framework