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

#include "deployment.hrc"
#include "unopkg_shared.h"
#include "dp_identifier.hxx"
#include "../../deployment/gui/dp_gui.hrc"
#include "../../app/lockfile.hxx"
#include "vcl/svapp.hxx"
#include "vcl/msgbox.hxx"
#include "rtl/bootstrap.hxx"
#include "rtl/strbuf.hxx"
#include "rtl/ustrbuf.hxx"
#include "osl/process.h"
#include "osl/file.hxx"
#include "osl/thread.hxx"
#include "tools/getprocessworkingdir.hxx"
#include "ucbhelper/contentbroker.hxx"
#include "ucbhelper/configurationkeys.hxx"
#include "unotools/processfactory.hxx"
#include "unotools/configmgr.hxx"
#include "com/sun/star/lang/XMultiServiceFactory.hpp"
#include "cppuhelper/bootstrap.hxx"
#include "comphelper/sequence.hxx"
#include <stdio.h>

using ::rtl::OUString;
using ::rtl::OString;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;

namespace unopkg {

bool getLockFilePath(OUString & out);

::rtl::OUString toString( OptionInfo const * info )
{
    OSL_ASSERT( info != 0 );
    ::rtl::OUStringBuffer buf;
    buf.appendAscii("--");
    buf.appendAscii(info->m_name);
    if (info->m_short_option != '\0')
    {
        buf.appendAscii(" (short -" );
        buf.append(info->m_short_option );
        buf.appendAscii(")");
    }
    if (info->m_has_argument)
        buf.appendAscii(" <argument>" );
    return buf.makeStringAndClear();
}

//==============================================================================
OptionInfo const * getOptionInfo(
    OptionInfo const * list,
    OUString const & opt, sal_Unicode copt )
{
    for ( ; list->m_name != 0; ++list )
    {
        OptionInfo const & option_info = *list;
        if (opt.getLength() > 0)
        {
            if (opt.equalsAsciiL(
                    option_info.m_name, option_info.m_name_length ) &&
                (copt == '\0' || copt == option_info.m_short_option))
            {
                return &option_info;
            }
        }
        else
        {
            OSL_ASSERT( copt != '\0' );
            if (copt == option_info.m_short_option)
            {
                return &option_info;
            }
        }
    }
    OSL_ENSURE( 0, ::rtl::OUStringToOString(
                    opt, osl_getThreadTextEncoding() ).getStr() );
    return 0;
}

//==============================================================================
bool isOption( OptionInfo const * option_info, sal_uInt32 * pIndex )
{
    OSL_ASSERT( option_info != 0 );
    if (osl_getCommandArgCount() <= *pIndex)
        return false;
    
    OUString arg;
    osl_getCommandArg( *pIndex, &arg.pData );
    sal_Int32 len = arg.getLength();
    
    if (len < 2 || arg[ 0 ] != '-')
        return false;
    
    if (len == 2 && arg[ 1 ] == option_info->m_short_option)
    {
        ++(*pIndex);
        dp_misc::TRACE(OUSTR(__FILE__": identified option \'")
            + OUSTR("\'") + OUString( option_info->m_short_option ) + OUSTR("\n"));
        return true;
    }
    if (arg[ 1 ] == '-' && rtl_ustr_ascii_compare(
            arg.pData->buffer + 2, option_info->m_name ) == 0)
    {
        ++(*pIndex);
        dp_misc::TRACE(OUSTR( __FILE__": identified option \'") 
            + OUString::createFromAscii(option_info->m_name) + OUSTR("\'\n"));
        return true;
    }
    return false;
}
//==============================================================================

bool isBootstrapVariable(sal_uInt32 * pIndex)
{
    OSL_ASSERT(osl_getCommandArgCount() >=  *pIndex);

    OUString arg;
    osl_getCommandArg(*pIndex, &arg.pData);
    if (arg.matchAsciiL("-env:", 5))
    {
        ++(*pIndex);
        return true;
    }
    return false;
}

//==============================================================================
bool readArgument(
    OUString * pValue, OptionInfo const * option_info, sal_uInt32 * pIndex )
{
    if (isOption( option_info, pIndex ))
    {
        if (*pIndex < osl_getCommandArgCount())
        {
            OSL_ASSERT( pValue != 0 );
            osl_getCommandArg( *pIndex, &pValue->pData );
            dp_misc::TRACE(OUSTR( __FILE__": argument value: ")
                + *pValue + OUSTR("\n"));
            ++(*pIndex);
            return true;
        }
        --(*pIndex);
    }
    return false;
}

//##############################################################################

namespace {
struct ExecutableDir : public rtl::StaticWithInit<
    const OUString, ExecutableDir> {
    const OUString operator () () {
        OUString path;
        if (osl_getExecutableFile( &path.pData ) != osl_Process_E_None) {
            throw RuntimeException(
                OUSTR("cannot locate executable directory!"),0  );
        }
        return path.copy( 0, path.lastIndexOf( '/' ) );
    }
};
struct ProcessWorkingDir : public rtl::StaticWithInit<
    const OUString, ProcessWorkingDir> {
    const OUString operator () () {
        OUString workingDir;
        tools::getProcessWorkingDir(&workingDir);
        return workingDir;
    }
};
} // anon namespace

//==============================================================================
OUString const & getExecutableDir()
{
    return ExecutableDir::get();
}

//==============================================================================
OUString const & getProcessWorkingDir()
{
    return ProcessWorkingDir::get();
}

//==============================================================================
OUString makeAbsoluteFileUrl(
    OUString const & sys_path, OUString const & base_url, bool throw_exc )
{
    // system path to file url
    OUString file_url;
    oslFileError rc = osl_getFileURLFromSystemPath( sys_path.pData, &file_url.pData );
    if ( rc != osl_File_E_None) {
        OUString tempPath;
        if ( osl_getSystemPathFromFileURL( sys_path.pData, &tempPath.pData) == osl_File_E_None )
        {
            file_url = sys_path; 
        }
        else if (throw_exc) 
        {
            throw RuntimeException(
                OUSTR("cannot get file url from system path: ") +
                sys_path, Reference< XInterface >() );
        }
    }
    
    OUString abs;
    if (osl_getAbsoluteFileURL(
            base_url.pData, file_url.pData, &abs.pData ) != osl_File_E_None)
    {
        if (throw_exc) {
            ::rtl::OUStringBuffer buf;
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                                 "making absolute file url failed: \"") );
            buf.append( base_url );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                                 "\" (base-url) and \"") );
            buf.append( file_url );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" (file-url)!") );
            throw RuntimeException(
                buf.makeStringAndClear(), Reference< XInterface >() );
        }
        return OUString();
    }
    return abs[ abs.getLength() -1 ] == '/'
        ? abs.copy( 0, abs.getLength() -1 ) : abs;
}

//##############################################################################

namespace {

//------------------------------------------------------------------------------
inline void printf_space( sal_Int32 space )
{
    while (space--)
        dp_misc::writeConsole("  ");
}

//------------------------------------------------------------------------------
void printf_line(
    OUString const & name, OUString const & value, sal_Int32 level )
{
   printf_space( level );
    dp_misc::writeConsole(name + OUSTR(": ") + value + OUSTR("\n"));
}

//------------------------------------------------------------------------------
void printf_package(
    Reference<deployment::XPackage> const & xPackage,
    Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level )
{
    beans::Optional< OUString > id(
        level == 0
        ? beans::Optional< OUString >(
            true, dp_misc::getIdentifier( xPackage ) )
        : xPackage->getIdentifier() );
    if (id.IsPresent)
        printf_line( OUSTR("Identifier"), id.Value, level );
    OUString version(xPackage->getVersion());
    if (version.getLength() != 0)
        printf_line( OUSTR("Version"), version, level + 1 );
    printf_line( OUSTR("URL"), xPackage->getURL(), level + 1 );
    
    beans::Optional< beans::Ambiguous<sal_Bool> > option(
        xPackage->isRegistered( Reference<task::XAbortChannel>(), xCmdEnv ) );
    OUString value;
    if (option.IsPresent) {
        beans::Ambiguous<sal_Bool> const & reg = option.Value;
        if (reg.IsAmbiguous)
            value = OUSTR("unknown");
        else
            value = reg.Value ? OUSTR("yes") : OUSTR("no");
    }
    else
        value = OUSTR("n/a");
    printf_line( OUSTR("is registered"), value, level + 1 );
    
    const Reference<deployment::XPackageTypeInfo> xPackageType(
        xPackage->getPackageType() );
    OSL_ASSERT( xPackageType.is() );
    if (xPackageType.is()) {
        printf_line( OUSTR("Media-Type"),
                     xPackageType->getMediaType(), level + 1 );
    }
    printf_line( OUSTR("Description"), xPackage->getDescription(), level + 1 );
    if (xPackage->isBundle()) {
        Sequence< Reference<deployment::XPackage> > seq(
            xPackage->getBundle( Reference<task::XAbortChannel>(), xCmdEnv ) );
        printf_space( level + 1 );
        dp_misc::writeConsole("bundled Packages: {\n");
        ::std::vector<Reference<deployment::XPackage> >vec_bundle;
        ::comphelper::sequenceToContainer(vec_bundle, seq);
        printf_packages( vec_bundle, ::std::vector<bool>(vec_bundle.size()),
                         xCmdEnv, level + 2 );
        printf_space( level + 1 );
        dp_misc::writeConsole("}\n");
    }
}

} // anon namespace

void printf_unaccepted_licenses(
    Reference<deployment::XPackage> const & ext)
{
        OUString id(
            dp_misc::getIdentifier(ext) );
        printf_line( OUSTR("Identifier"), id, 0 );
        printf_space(1);
        dp_misc::writeConsole(OUSTR("License not accepted\n\n"));
}

//==============================================================================
void printf_packages(
    ::std::vector< Reference<deployment::XPackage> > const & allExtensions,
    ::std::vector<bool> const & vecUnaccepted,
    Reference<XCommandEnvironment> const & xCmdEnv, sal_Int32 level )
{
    OSL_ASSERT(allExtensions.size() == vecUnaccepted.size());

    if (allExtensions.size() == 0)
    {
        printf_space( level );
        dp_misc::writeConsole("<none>\n");
    }
    else
    {
        typedef ::std::vector< Reference<deployment::XPackage> >::const_iterator I_EXT;
        int index = 0;
        for (I_EXT i = allExtensions.begin(); i != allExtensions.end(); i++, index++)
        {
            if (vecUnaccepted[index])
                printf_unaccepted_licenses(*i);
            else
                printf_package( *i, xCmdEnv, level );
            dp_misc::writeConsole(OUSTR("\n"));
        }
    }
}


//##############################################################################

namespace {

//------------------------------------------------------------------------------
Reference<XComponentContext> bootstrapStandAlone(
    DisposeGuard & disposeGuard, bool /*verbose */)
{
    Reference<XComponentContext> xContext = 
        ::cppu::defaultBootstrap_InitialComponentContext();

    // assure disposing of local component context:
    disposeGuard.reset(
        Reference<lang::XComponent>( xContext, UNO_QUERY ) );

    Reference<lang::XMultiServiceFactory> xServiceManager(
        xContext->getServiceManager(), UNO_QUERY_THROW );
    // set global process service factory used by unotools config helpers
    ::utl::setProcessServiceFactory( xServiceManager );
    
    // initialize the ucbhelper ucb,
    // because the package implementation uses it
    Sequence<Any> ucb_args( 2 );
    ucb_args[ 0 ] <<= OUSTR(UCB_CONFIGURATION_KEY1_LOCAL);
    ucb_args[ 1 ] <<= OUSTR(UCB_CONFIGURATION_KEY2_OFFICE);
    if (! ::ucbhelper::ContentBroker::initialize( xServiceManager, ucb_args ))
        throw RuntimeException( OUSTR("cannot initialize UCB!"), 0 );
    
    disposeGuard.setDeinitUCB();
    return xContext;
}

//------------------------------------------------------------------------------
Reference<XComponentContext> connectToOffice(
    Reference<XComponentContext> const & xLocalComponentContext,
    bool verbose )
{
    Sequence<OUString> args( 3 );
    args[ 0 ] = OUSTR("-nologo");
    args[ 1 ] = OUSTR("-nodefault");
    
    OUString pipeId( ::dp_misc::generateRandomPipeId() );
    ::rtl::OUStringBuffer buf;
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("-accept=pipe,name=") );
    buf.append( pipeId );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(";urp;") );
    args[ 2 ] = buf.makeStringAndClear();
    OUString appURL( getExecutableDir() + OUSTR("/soffice") );
    
    if (verbose)
    {
        dp_misc::writeConsole(
            OUSTR("Raising process: ") + 
            appURL +
            OUSTR("\nArguments: -nologo -nodefault ") +
            args[2] + 
            OUSTR("\n"));
    }
    
    ::dp_misc::raiseProcess( appURL, args );
    
    if (verbose)
        dp_misc::writeConsole("Ok.  Connecting...");
    
    OSL_ASSERT( buf.getLength() == 0 );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("uno:pipe,name=") );
    buf.append( pipeId );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                         ";urp;StarOffice.ComponentContext") );
    Reference<XComponentContext> xRet(
        ::dp_misc::resolveUnoURL(
            buf.makeStringAndClear(), xLocalComponentContext ),
        UNO_QUERY_THROW );
    if (verbose)
        dp_misc::writeConsole("Ok.\n");
    
    return xRet;
}

} // anon namespace

/** returns the path to the lock file used by unopkg.
    @return the path. An empty string signifies an error.
*/
OUString getLockFilePath()
{   
    OUString ret;
    OUString sBootstrap(RTL_CONSTASCII_USTRINGPARAM("${$OOO_BASE_DIR/program/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}"));
    rtl::Bootstrap::expandMacros(sBootstrap);
    OUString sAbs;
    if (::osl::File::E_None ==  ::osl::File::getAbsoluteFileURL(
        sBootstrap, OUSTR(".lock"), sAbs))
    {
        if (::osl::File::E_None == 
            ::osl::File::getSystemPathFromFileURL(sAbs, sBootstrap))
        {
            ret = sBootstrap;
        }
    }

    return ret;
}
//==============================================================================
Reference<XComponentContext> getUNO( 
    DisposeGuard & disposeGuard, bool verbose, bool shared, bool bGui,
    Reference<XComponentContext> & out_localContext)
{
    // do not create any user data (for the root user) in --shared mode:
    if (shared) {
        rtl::Bootstrap::set(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("CFG_CacheUrl")),
            rtl::OUString());
    }

    // hold lock during process runtime:
    static ::desktop::Lockfile s_lockfile( false /* no IPC server */ );
    Reference<XComponentContext> xComponentContext(
        bootstrapStandAlone( disposeGuard, verbose ) );
    out_localContext = xComponentContext;
    if (::dp_misc::office_is_running()) {
        xComponentContext.set(
            connectToOffice( xComponentContext, verbose ) );
    }
    else 
    {
        if (! s_lockfile.check( 0 ))
        {
            String sMsg(ResId(RID_STR_CONCURRENTINSTANCE, *DeploymentResMgr::get()));
            //Create this string before we call DeInitVCL, because this will kill
            //the ResMgr
            String sError(ResId(RID_STR_UNOPKG_ERROR, *DeploymentResMgr::get())); 

            sMsg = sMsg + OUSTR("\n") + getLockFilePath();

            if (bGui)
            {
                //We show a message box or print to the console that there
                //is another instance already running
                if ( ! InitVCL( Reference<lang::XMultiServiceFactory>(
                                    xComponentContext->getServiceManager(),
                                    UNO_QUERY_THROW ) ))
                    throw RuntimeException( OUSTR("Cannot initialize VCL!"),
                                            NULL );
                {
                    WarningBox warn(NULL, WB_OK | WB_DEF_OK, sMsg); 
                    warn.SetText(::utl::ConfigManager::GetDirectConfigProperty(
                                     ::utl::ConfigManager::PRODUCTNAME).get<OUString>());
                    warn.SetIcon(0);
                    warn.Execute();			
                }
                DeInitVCL();
            }

            throw LockFileException(
                OUSTR("\n") + sError + sMsg + OUSTR("\n"));
        }
    }
    
    return xComponentContext;
}

//Determines if a folder does not contains a folder.
//Return false may also mean that the status could not be determined
//because some error occurred.
bool hasNoFolder(OUString const & folderUrl)
{
    bool ret = false;
    OUString url = folderUrl;
    ::rtl::Bootstrap::expandMacros(url);
    ::osl::Directory dir(url);
    osl::File::RC rc = dir.open();
    if (rc == osl::File::E_None)
    {
        bool bFolderExist = false;
        osl::DirectoryItem i;
        osl::File::RC rcNext = osl::File::E_None;
        while ( (rcNext = dir.getNextItem(i)) == osl::File::E_None)
        {
            osl::FileStatus stat(FileStatusMask_Type);
            if (i.getFileStatus(stat) == osl::File::E_None)
            {
                if (stat.getFileType() == osl::FileStatus::Directory)
                {
                    bFolderExist = true;
                    break;
                }
            }
            else
            {
                dp_misc::writeConsole(
                    OUSTR("unopkg: Error while investigating ") + url + OUSTR("\n"));
                break;
            }
            i = osl::DirectoryItem();
        }
                
        if (rcNext == osl::File::E_NOENT ||
            rcNext == osl::File::E_None)
        {
            if (!bFolderExist)
                ret = true;
        }
        else
        {
            dp_misc::writeConsole(
                OUSTR("unopkg: Error while investigating ") + url + OUSTR("\n"));
        }
        
        dir.close();
    }
    else
    {
        dp_misc::writeConsole(
            OUSTR("unopkg: Error while investigating ") + url + OUSTR("\n"));
    }
    return ret;
}

void removeFolder(OUString const & folderUrl)
{
    OUString url = folderUrl;
    ::rtl::Bootstrap::expandMacros(url);
    ::osl::Directory dir(url);
    ::osl::File::RC rc = dir.open();
    if (rc == osl::File::E_None)
    {
        ::osl::DirectoryItem i;
        ::osl::File::RC rcNext = ::osl::File::E_None;
        while ( (rcNext = dir.getNextItem(i)) == ::osl::File::E_None)
        {
            ::osl::FileStatus stat(FileStatusMask_Type | FileStatusMask_FileURL);
            if (i.getFileStatus(stat) == ::osl::File::E_None)
            {
                ::osl::FileStatus::Type t = stat.getFileType();
                if (t == ::osl::FileStatus::Directory)
                {
                    //remove folder
                    removeFolder(stat.getFileURL());
                }
                else if (t == ::osl::FileStatus::Regular)
                {
                    //remove file
                    ::osl::File::remove(stat.getFileURL());
                }
                else
                {
                    OSL_ASSERT(0);
                }
            }
            else
            {
                dp_misc::writeConsole(
                    OUSTR("unopkg: Error while investigating ") + url + OUSTR("\n"));
                break;
            }
            i = ::osl::DirectoryItem();
        }
        dir.close();
        ::osl::Directory::remove(url);
    }
    else if (rc != osl::File::E_NOENT)
    {
        dp_misc::writeConsole(
            OUSTR("unopkg: Error while removing ") + url + OUSTR("\n"));
    }    
}

}