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

#include "sysmailclient.hxx"
#include "sysmailmsg.hxx"

#include <com/sun/star/system/MailClientFlags.hpp>
#include <com/sun/star/system/XMailMessage.hpp>

#include <osl/diagnose.h>
#include <osl/process.h>
#include <rtl/bootstrap.hxx>
#include <osl/file.hxx>
#include <rtl/ustrbuf.hxx>

#define WIN32_LEAN_AND_MEAN
#if defined _MSC_VER
#pragma warning(push, 1)
#endif
#include <windows.h>
#include <mapi.h>
#if defined _MSC_VER
#pragma warning(pop)
#endif

#include <process.h>
#include <vector>

using css::uno::UNO_QUERY;
using css::uno::Reference;
using css::uno::Exception;
using css::uno::RuntimeException;
using css::uno::Sequence;
using css::lang::IllegalArgumentException;

using css::system::XMailClient;
using css::system::XMailMessage;
using css::system::XMailMessage;
using css::system::MailClientFlags::NO_USER_INTERFACE;
using css::system::MailClientFlags::NO_LOGON_DIALOG;

using rtl::OUString;
using rtl::OUStringBuffer;

namespace shell
{
namespace /* private */
{
    typedef std::vector<rtl::OUString> StringList_t;
    typedef StringList_t::const_iterator StringListIterator_t;

    const rtl::OUString TO = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--to"));
    const rtl::OUString CC = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--cc"));
    const rtl::OUString BCC = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--bcc"));
    const rtl::OUString FROM = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--from"));
    const rtl::OUString SUBJECT = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--subject"));
    const rtl::OUString BODY = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--body"));
    const rtl::OUString ATTACH = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--attach"));
    const rtl::OUString FLAG_MAPI_DIALOG = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--mapi-dialog"));
    const rtl::OUString FLAG_MAPI_LOGON_UI = rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("--mapi-logon-ui"));

    static OUString quoteString( const OUString& rStr )
    {
        rtl::OUStringBuffer quoted;
        quoted.append(sal_Unicode('"'));
        quoted.append(rStr);
        quoted.append(sal_Unicode('"'));

        return quoted.makeStringAndClear();
    }

    static OUString quoteAndEscape( const OUString &rStr )
    {
        OUStringBuffer aBuffer;
        aBuffer.append(sal_Unicode('"'));

        sal_Int32 nIndex = rStr.indexOf(sal_Unicode('"'));
        if ( nIndex == -1 )
            aBuffer.append( rStr );
        else
        {
            const sal_Unicode *pStart = rStr.getStr();
            const sal_Unicode *pFrom = pStart;
            const sal_Int32 nLen = rStr.getLength();
            sal_Int32 nPrev = 0;;
            do
            {
                aBuffer.append( pFrom, nIndex - nPrev );
                aBuffer.appendAscii( RTL_CONSTASCII_STRINGPARAM( "\\\"" ) );
                nIndex++;
                pFrom = pStart + nIndex;
                nPrev = nIndex;
            }
            while ( ( nIndex = rStr.indexOf( '"' , nIndex ) ) != -1  );

            aBuffer.append( pFrom, nLen - nPrev );
        }

        aBuffer.append(sal_Unicode('"'));

        return aBuffer.makeStringAndClear();
    }

    /** @internal
        look if an alternative program is configured
        which should be used as senddoc executable */
    static rtl::OUString getAlternativeSenddocUrl()
    {
        rtl::OUString altSenddocUrl;
        HKEY hkey;
        LONG lret = RegOpenKeyW(HKEY_CURRENT_USER, L"Software\\Apache OpenOffice\\SendAsEMailClient", &hkey);
        if (lret == ERROR_SUCCESS)
        {
            wchar_t buff[MAX_PATH];
            LONG sz = sizeof(buff);
            lret = RegQueryValueW(hkey, NULL, buff, &sz);
            if (lret == ERROR_SUCCESS)
            {
                osl::FileBase::getFileURLFromSystemPath(reinterpret_cast<const sal_Unicode*>(buff), altSenddocUrl);
            }
            RegCloseKey(hkey);
        }
        return altSenddocUrl;
    }

    /**
        Returns the absolute file Url of the senddoc executable.

        @returns
        the absolute file Url of the senddoc executable. In case
        of an error an empty string will be returned.
    */
    static rtl::OUString getSenddocUrl()
    {
        rtl::OUString senddocUrl = getAlternativeSenddocUrl();

        if (senddocUrl.getLength() == 0)
        {
            senddocUrl = rtl::OUString(
                RTL_CONSTASCII_USTRINGPARAM(
                    "$OOO_BASE_DIR/program/senddoc.exe"));
            rtl::Bootstrap::expandMacros(senddocUrl); //TODO: detect failure
        }
        return senddocUrl;
    }

    /**
        Execute Senddoc.exe which a MAPI wrapper.
        @param rCommandArgs
                [in] the arguments to be passed to Senddoc.exe
        @returns
                <TRUE/> on success.
    */
    static bool executeSenddoc(const StringList_t& rCommandArgs)
    {
        rtl::OUString senddocUrl = getSenddocUrl();
        if (senddocUrl.getLength() == 0)
            return false;

        oslProcess proc;
        oslProcessError err = osl_Process_E_Unknown;

        /* for efficiency reasons we are using a 'bad' cast here
        as a vector or rtl::OUStrings is nothing else than
        an array of pointers to rtl_uString's */
        err = osl_executeProcess(
            senddocUrl.pData,
            (rtl_uString**)&rCommandArgs[0],
            rCommandArgs.size(),
            osl_Process_WAIT | osl_Process_DETACHED,
            NULL,
            NULL,
            NULL,
            0,
            &proc);

        if (err != osl_Process_E_None)
            return false;

        oslProcessInfo procInfo;
        procInfo.Size = sizeof(oslProcessInfo);
        osl_getProcessInfo(proc, osl_Process_EXITCODE, &procInfo);
        osl_freeProcessHandle(proc);
        return (procInfo.Code == SUCCESS_SUCCESS);
    }
} // namespace private


Reference<XMailMessage> SAL_CALL WinSysMailClient::createMailMessage()
    throw (RuntimeException)
{
    return Reference<XMailMessage>( new WinSysMailMsg() );
}

/**
    Assemble a command line for SendDoc.exe out of the members
    of the supplied XMailMessage.

    @param xMailMessage
    [in] the mail message.

    @param aFlags
    [in] different flags to be used with the system mail service.

    @param rCommandArgs
    [in|out] a buffer for the command line arguments. The buffer
    is assumed to be empty.

    @throws com::sun::star::lang::IllegalArgumentException
    if an invalid file URL has been detected in the attachment list.
*/
void WinSysMailClient::assembleCommandLine(
    const Reference<XMailMessage>& xMailMessage,
    sal_Int32 aFlag,
    StringList_t& rCommandArgs)
{
    OSL_ENSURE(rCommandArgs.size() == 0, "Provided command argument buffer not empty");

    rtl::OUString to = xMailMessage->getRecipient();
    if (to.getLength() > 0)
    {
        rCommandArgs.push_back(TO);
        rCommandArgs.push_back(to);
    }

    Sequence<rtl::OUString> ccRecipients = xMailMessage->getCcRecipient();
    for (int i = 0; i < ccRecipients.getLength(); i++)
    {
        rCommandArgs.push_back(CC);
        rCommandArgs.push_back(ccRecipients[i]);
    }

    Sequence<rtl::OUString> bccRecipients = xMailMessage->getBccRecipient();
    for (int i = 0; i < bccRecipients.getLength(); i++)
    {
        rCommandArgs.push_back(BCC);
        rCommandArgs.push_back(bccRecipients[i]);
    }

    rtl::OUString from = xMailMessage->getOriginator();
    if (from.getLength() > 0)
    {
        rCommandArgs.push_back(FROM);
        rCommandArgs.push_back(from);
    }

    Sequence<rtl::OUString> attachments = xMailMessage->getAttachement();
    for (int i = 0; i < attachments.getLength(); i++)
    {
        rtl::OUString sysPath;
        osl::FileBase::RC err = osl::FileBase::getSystemPathFromFileURL(attachments[i], sysPath);
        if (err != osl::FileBase::E_None)
            throw IllegalArgumentException(
                rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Invalid attachment file URL")),
                static_cast<XMailClient*>(this),
                1);

        rCommandArgs.push_back(ATTACH);
        rCommandArgs.push_back( quoteString( sysPath ) );
    }

    rtl::OUString body = xMailMessage->getBody();
    if (body.getLength()>0)
    {
        rCommandArgs.push_back(BODY);
        rCommandArgs.push_back( quoteAndEscape( body ) );
    }

    rtl::OUString subject = xMailMessage->getSubject();
    if (subject.getLength() > 0)
    {
        rCommandArgs.push_back(SUBJECT);
        rCommandArgs.push_back( quoteAndEscape( subject ) );
    }

    if (!(aFlag & NO_USER_INTERFACE))
        rCommandArgs.push_back(FLAG_MAPI_DIALOG);

    if (!(aFlag & NO_LOGON_DIALOG))
        rCommandArgs.push_back(FLAG_MAPI_LOGON_UI);
}

void SAL_CALL WinSysMailClient::sendMailMessage(
    const Reference<XMailMessage>& xMailMessage,
    sal_Int32 aFlag)
    throw (IllegalArgumentException, Exception, RuntimeException)
{
    validateParameter(xMailMessage, aFlag);

    StringList_t senddocParams;
    assembleCommandLine(xMailMessage, aFlag, senddocParams);

    if (!executeSenddoc(senddocParams))
        throw Exception(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Send email failed")),
            static_cast<XMailClient*>(this));
}

void WinSysMailClient::validateParameter(
    const Reference<XMailMessage>& xMailMessage,
    sal_Int32 aFlag )
{
    if (!xMailMessage.is())
        throw IllegalArgumentException(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Empty mail message reference")),
            static_cast<XMailClient*>(this),
            1);

    // #93077#
    OSL_ENSURE(!(aFlag & NO_LOGON_DIALOG), "Flag NO_LOGON_DIALOG has currently no effect");

    // check the flags, the allowed range is 0 - (2^n - 1)
    if (aFlag < 0 || aFlag > 3)
        throw IllegalArgumentException(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("Invalid flag value")),
            static_cast<XMailClient*>(this),
            2);

    // check if a recipient is specified of the flags NO_USER_INTERFACE is specified
    if ((aFlag & NO_USER_INTERFACE) && !xMailMessage->getRecipient().getLength())
        throw IllegalArgumentException(
            rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("No recipient specified")),
            static_cast<XMailClient*>(this),
            1);
}

}