/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

#ifdef ENABLE_CUPS
#include <cups/cups.h>
#include <cups/ppd.h>

#else // !ENABLE_CUPS
typedef void ppd_file_t;
typedef void cups_dest_t;
typedef void cups_option_t;
#endif

#include <unistd.h>

#include "cupsmgr.hxx"

#include "osl/thread.h"
#include "osl/diagnose.h"
#include "osl/conditn.hxx"

#include "rtl/ustrbuf.hxx"

#include <algorithm>
#include <setjmp.h>
#include <signal.h>

#define CUPS_LIB_NAME "libcups.so.2"

namespace psp
{
class CUPSWrapper
{
    oslModule		m_pLib;
    osl::Mutex		m_aGetPPDMutex;
    bool            m_bPPDThreadRunning;

    int				(*m_pcupsPrintFile)(const char*, const char*, const char*, int, cups_option_t*);
    int				(*m_pcupsGetDests)(cups_dest_t**);
    void			(*m_pcupsSetDests)(int,cups_dest_t*);
    void			(*m_pcupsFreeDests)(int,cups_dest_t*);
    const char*		(*m_pcupsGetPPD)(const char*);
    int				(*m_pcupsMarkOptions)(ppd_file_t*,int,cups_option_t*);
    int				(*m_pcupsAddOption)(const char*,const char*,int,cups_option_t**);
    void			(*m_pcupsFreeOptions)(int,cups_option_t*);
    ppd_file_t*		(*m_pppdOpenFile)(const char* pFile);
    void			(*m_pppdClose)(ppd_file_t*);
    const char*		(*m_pcupsServer)();
    void			(*m_pcupsSetPasswordCB)(const char*(cb)(const char*));
    const char*		(*m_pcupsUser)();
    void			(*m_pcupsSetUser)(const char*);
    const char*     (*m_pcupsGetOption)(const char*,int,cups_option_t*);

    oslGenericFunction loadSymbol( const char* );
public:
    CUPSWrapper();
    ~CUPSWrapper();

    bool isValid();

    int cupsGetDests(cups_dest_t** pDests)
    { return m_pcupsGetDests(pDests); }

    void cupsSetDests( int nDests, cups_dest_t* pDests )
    { m_pcupsSetDests( nDests, pDests ); }

    void cupsFreeDests(int nDests, cups_dest_t* pDests)
    { m_pcupsFreeDests(nDests, pDests); }

    int cupsPrintFile( const char* pPrinter,
                       const char* pFileName,
                       const char* pTitle,
                       int nOptions,
                   cups_option_t* pOptions )
    { return m_pcupsPrintFile( pPrinter, pFileName, pTitle, nOptions, pOptions ); }

    rtl::OString cupsGetPPD( const char* pPrinter );

    int cupsMarkOptions(ppd_file_t* pPPD, int nOptions, cups_option_t* pOptions )
    { return m_pcupsMarkOptions(pPPD, nOptions, pOptions); }

    int cupsAddOption( const char* pName, const char* pValue, int nOptions, cups_option_t** pOptions )
    { return m_pcupsAddOption( pName, pValue, nOptions, pOptions ); }

    void cupsFreeOptions( int nOptions, cups_option_t* pOptions )
    { m_pcupsFreeOptions( nOptions, pOptions ); }

    ppd_file_t* ppdOpenFile( const char* pFileName )
    { return m_pppdOpenFile( pFileName ); }

    void ppdClose( ppd_file_t* pPPD )
    { m_pppdClose( pPPD ); }

    const char	*cupsServer(void)
    { return m_pcupsServer(); }

    const char	*cupsUser(void)
    { return m_pcupsUser(); }
    
    void cupsSetPasswordCB(const char *(*cb)(const char *))
    { m_pcupsSetPasswordCB( cb ); }

    void cupsSetUser(const char *user)
    { m_pcupsSetUser( user ); }
    
    const char* cupsGetOption(const char* name, int num_options, cups_option_t* options)
    { return m_pcupsGetOption( name, num_options, options ); }
    
};
}

using namespace psp;
using namespace osl;

using ::rtl::OUString;
using ::rtl::OUStringBuffer;
using ::rtl::OUStringToOString;
using ::rtl::OStringToOUString;
using ::rtl::OUStringHash;
using ::rtl::OString;

/*
 *  CUPSWrapper class
 */

oslGenericFunction CUPSWrapper::loadSymbol( const char* pSymbol )
{
    OUString aSym( OUString::createFromAscii( pSymbol ) );
    oslGenericFunction pSym = osl_getFunctionSymbol( m_pLib, aSym.pData );
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "%s %s\n", pSymbol, pSym ? "found" : "not found" );
#endif
    return pSym;
}

CUPSWrapper::CUPSWrapper() 
        : m_pLib( NULL ),
          m_bPPDThreadRunning( false )
{
#ifdef ENABLE_CUPS
    OUString aLib( RTL_CONSTASCII_USTRINGPARAM( CUPS_LIB_NAME ) );
    m_pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
    if( ! m_pLib )
    {
        aLib = OUString( RTL_CONSTASCII_USTRINGPARAM( SAL_MODULENAME( "cups" ) ) );
        m_pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
    }
#endif

    if( ! m_pLib )
    {
#if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "no cups library found\n" );
#endif
        return;
    }

    m_pcupsPrintFile	 	= (int(*)(const char*,const char*,const char*,int,cups_option_t*))
        loadSymbol( "cupsPrintFile" );
    m_pcupsGetDests			= (int(*)(cups_dest_t**))
        loadSymbol( "cupsGetDests" );
    m_pcupsSetDests			= (void(*)(int,cups_dest_t*))
        loadSymbol( "cupsSetDests" );
    m_pcupsFreeDests		= (void(*)(int,cups_dest_t*))
        loadSymbol( "cupsFreeDests" );
    m_pcupsGetPPD			= (const char*(*)(const char*))
        loadSymbol( "cupsGetPPD" );
    m_pcupsMarkOptions		= (int(*)(ppd_file_t*,int,cups_option_t*))
        loadSymbol( "cupsMarkOptions" );
    m_pcupsAddOption		= (int(*)(const char*,const char*,int,cups_option_t**))
        loadSymbol( "cupsAddOption" );
    m_pcupsFreeOptions		= (void(*)(int,cups_option_t*))
        loadSymbol( "cupsFreeOptions" );
    m_pppdOpenFile			= (ppd_file_t*(*)(const char*))
        loadSymbol( "ppdOpenFile" );
    m_pppdClose				= (void(*)(ppd_file_t*))
        loadSymbol( "ppdClose" );
    m_pcupsServer			= (const char*(*)())
        loadSymbol( "cupsServer" );
    m_pcupsUser				= (const char*(*)())
        loadSymbol( "cupsUser" );
    m_pcupsSetPasswordCB	= (void(*)(const char*(*)(const char*)))
        loadSymbol( "cupsSetPasswordCB" );
    m_pcupsSetUser			= (void(*)(const char*))
        loadSymbol( "cupsSetUser" );
    m_pcupsGetOption        = (const char*(*)(const char*,int,cups_option_t*))
        loadSymbol( "cupsGetOption" );

    if( ! (
           m_pcupsPrintFile					&&
           m_pcupsGetDests					&&
           m_pcupsSetDests					&&
           m_pcupsFreeDests					&&
           m_pcupsGetPPD					&&
           m_pcupsMarkOptions				&&
           m_pcupsAddOption					&&
           m_pcupsServer					&&
           m_pcupsUser						&&
           m_pcupsSetPasswordCB				&&
           m_pcupsSetUser					&&
           m_pcupsFreeOptions				&&
           m_pppdOpenFile					&&
           m_pppdClose                      &&
           m_pcupsGetOption
           ) )
    {
        osl_unloadModule( m_pLib );
        m_pLib = NULL;
    }
}

CUPSWrapper::~CUPSWrapper()
{
    if( m_pLib )
        osl_unloadModule( m_pLib );
}

bool CUPSWrapper::isValid()
{
    return m_pLib != NULL;
}

typedef const char*(*PPDFunction)(const char*);
struct GetPPDAttribs
{
    PPDFunction         m_pFunction;
    osl::Condition		m_aCondition;
    OString			    m_aParameter;
    OString			    m_aResult;
    oslThread			m_aThread;
    int                 m_nRefs;
    bool*               m_pResetRunning;
    osl::Mutex*         m_pSyncMutex;
    
    GetPPDAttribs( PPDFunction pFn, const char * m_pParameter,
                   bool* pResetRunning, osl::Mutex* pSyncMutex )
            : m_pFunction( pFn ),
              m_aParameter( m_pParameter ),
              m_pResetRunning( pResetRunning ),
              m_pSyncMutex( pSyncMutex )
    {
        m_nRefs = 2;
        m_aCondition.reset();
    }
    
    ~GetPPDAttribs()
    {
        if( m_aResult.getLength() )
            unlink( m_aResult.getStr() );
    }
    
    void unref()
    {
        if( --m_nRefs == 0 )
        {
            *m_pResetRunning = false;
            delete this;
        }
    }
    
    void executeCall()
    {
        // This CUPS method is not at all thread-safe we need
        // to dup the pointer to a static buffer it returns ASAP
        OString aResult = m_pFunction( m_aParameter );
        MutexGuard aGuard( *m_pSyncMutex );
        m_aResult = aResult;
        m_aCondition.set();
        unref();
    }
    
    OString waitResult( TimeValue *pDelay )
    {
        m_pSyncMutex->release();
        
        if (m_aCondition.wait( pDelay ) != Condition::result_ok
            )
        {
            #if OSL_DEBUG_LEVEL > 1
            fprintf( stderr, "cupsGetPPD %s timed out\n",
            (const sal_Char *) m_aParameter
            );
            #endif
        }
        m_pSyncMutex->acquire();
        
        OString aRetval = m_aResult;
        m_aResult = OString();
        unref();
        
        return aRetval;
    }
};

extern "C" {
    static void getPPDWorker(void* pData)
    {
        GetPPDAttribs* pAttribs = (GetPPDAttribs*)pData;
        pAttribs->executeCall();
    }
}

OString CUPSWrapper::cupsGetPPD( const char* pPrinter )
{
    OString aResult;

    m_aGetPPDMutex.acquire();
    // if one thread hangs in cupsGetPPD already, don't start another
    if( ! m_bPPDThreadRunning )
    {
        m_bPPDThreadRunning = true;
        GetPPDAttribs* pAttribs = new GetPPDAttribs( m_pcupsGetPPD,
                                                     pPrinter,
                                                     &m_bPPDThreadRunning,
                                                     &m_aGetPPDMutex );
        
        oslThread aThread = osl_createThread( getPPDWorker, pAttribs );

        TimeValue aValue;
        aValue.Seconds = 5;
        aValue.Nanosec = 0;

        // NOTE: waitResult release and acquires the GetPPD mutex
        aResult = pAttribs->waitResult( &aValue );
        osl_destroyThread( aThread );
    }
    m_aGetPPDMutex.release();

    return aResult;
}

#ifdef ENABLE_CUPS
static const char* setPasswordCallback( const char* pIn )
{
    const char* pRet = NULL;
    
    PrinterInfoManager& rMgr = PrinterInfoManager::get();
    if( rMgr.getType() == PrinterInfoManager::CUPS ) // sanity check
        pRet = static_cast<CUPSManager&>(rMgr).authenticateUser( pIn );
    return pRet;
}
#endif

/*
 *  CUPSManager class
 */

CUPSManager* CUPSManager::tryLoadCUPS()
{
    CUPSManager* pManager = NULL;
#ifdef ENABLE_CUPS
    static const char* pEnv = getenv( "SAL_DISABLE_CUPS" );
    
    if( ! pEnv || ! *pEnv )
    {
        // try to load CUPS
        CUPSWrapper* pWrapper = new CUPSWrapper();
        if( pWrapper->isValid() )
            pManager = new CUPSManager( pWrapper );
        else
            delete pWrapper;
    }
#endif
    return pManager;
}

extern "C"
{
static void run_dest_thread_stub( void* pThis )
{
    CUPSManager::runDestThread( pThis );
}
}

CUPSManager::CUPSManager( CUPSWrapper* pWrapper ) :
        PrinterInfoManager( CUPS ),
        m_pCUPSWrapper( pWrapper ),
        m_nDests( 0 ),
        m_pDests( NULL ),
        m_bNewDests( false )
{
    m_aDestThread = osl_createThread( run_dest_thread_stub, this );
}

CUPSManager::~CUPSManager()
{
    if( m_aDestThread )
    {
        // if the thread is still running here, then
        // cupsGetDests is hung; terminate the thread instead of joining
        osl_terminateThread( m_aDestThread );
        osl_destroyThread( m_aDestThread );
    }

    if( m_nDests && m_pDests )
        m_pCUPSWrapper->cupsFreeDests( m_nDests, (cups_dest_t*)m_pDests );
    delete m_pCUPSWrapper;
}

void CUPSManager::runDestThread( void* pThis )
{
    ((CUPSManager*)pThis)->runDests();
}

static sigjmp_buf aViolationBuffer;

extern "C"
{
    static void lcl_signal_action(int nSignal)
    {
        fprintf( stderr, "Signal %d during fontconfig initialization called, ignoring fontconfig\n", nSignal );
        siglongjmp( aViolationBuffer, 1 );
    }
}

void CUPSManager::runDests()
{
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "starting cupsGetDests\n" );
#endif
    cups_dest_t* pDests = NULL;

    // #i86306# prepare against really broken CUPS installations / missing servers
    
    // install signal handler for SEGV, BUS and ABRT
    struct sigaction act;
    struct sigaction oact[3];

    act.sa_handler = lcl_signal_action;
    act.sa_flags   = 0;
    sigemptyset(&(act.sa_mask));
    
    int nSegvSignalInstalled = sigaction(SIGSEGV, &act, &oact[0]);
    int nBusSignalInstalled = sigaction(SIGBUS, &act, &oact[1]);
    int nAbortSignalInstalled = sigaction(SIGABRT, &act, &oact[2]);

    // prepare against a signal during FcInit or FcConfigGetCurrent
    if( sigsetjmp( aViolationBuffer, ~0 ) == 0 )
    {
        int nDests = m_pCUPSWrapper->cupsGetDests( &pDests );
        #if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "came out of cupsGetDests\n" );
        #endif
        
        osl::MutexGuard aGuard( m_aCUPSMutex );
        m_nDests = nDests;
        m_pDests = pDests;
        m_bNewDests = true;
        #if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "finished cupsGetDests\n" );
        #endif
    }
    else
    {
        #if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "cupsGetDests crashed, not using CUPS\n" );
        #endif
    }
    
    // restore old signal handlers
    if( nSegvSignalInstalled == 0 )
        sigaction( SIGSEGV, &oact[0], NULL );
    if( nBusSignalInstalled == 0 )
        sigaction( SIGBUS, &oact[1], NULL );
    if( nAbortSignalInstalled == 0 )
        sigaction( SIGABRT, &oact[2], NULL );
}

void CUPSManager::initialize()
{
    // get normal printers, clear printer list
    PrinterInfoManager::initialize();
    
#ifdef ENABLE_CUPS
    // check whether thread has completed
    // if not behave like old printing system
    osl::MutexGuard aGuard( m_aCUPSMutex );
    
    if( ! m_bNewDests )
        return;

    // dest thread has run, clean up
    if( m_aDestThread )
    {
        osl_joinWithThread( m_aDestThread );
        osl_destroyThread( m_aDestThread );
        m_aDestThread = NULL;
    }
    m_bNewDests = false;

    // clear old stuff
    m_aCUPSDestMap.clear();

    if( ! (m_nDests && m_pDests ) )
        return;
    
    if( isCUPSDisabled() )
        return;

    // check for CUPS server(?) > 1.2
    // since there is no API to query, check for options that were
    // introduced in dests with 1.2
    // this is needed to check for %%IncludeFeature support
    // (#i65684#, #i65491#)
    bool bUsePDF = false;
    cups_dest_t* pDest = ((cups_dest_t*)m_pDests);
    const char* pOpt = m_pCUPSWrapper->cupsGetOption( "printer-info",
                                                      pDest->num_options,
                                                      pDest->options );
    if( pOpt )
    {
        m_bUseIncludeFeature = true;
        bUsePDF = true;
        if( m_aGlobalDefaults.m_nPSLevel == 0 && m_aGlobalDefaults.m_nPDFDevice == 0 )
            m_aGlobalDefaults.m_nPDFDevice = 1;
    }
    // do not send include JobPatch; CUPS will insert that itself
    // TODO: currently unknwon which versions of CUPS insert JobPatches
    // so currently it is assumed CUPS = don't insert JobPatch files
    m_bUseJobPatch = false;

    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
    int nPrinter = m_nDests;
    
    // reset global default PPD options; these are queried on demand from CUPS
    m_aGlobalDefaults.m_pParser = NULL;
    m_aGlobalDefaults.m_aContext = PPDContext();

    // add CUPS printers, should there be a printer
    // with the same name as a CUPS printer, overwrite it
    while( nPrinter-- )
    {
        pDest = ((cups_dest_t*)m_pDests)+nPrinter;
        OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
        if( pDest->instance && *pDest->instance )
        {
            OUStringBuffer aBuf( 256 );
            aBuf.append( aPrinterName );
            aBuf.append( sal_Unicode( '/' ) );
            aBuf.append( OStringToOUString( pDest->instance, aEncoding ) );
            aPrinterName = aBuf.makeStringAndClear();
        }

        // initialize printer with possible configuration from psprint.conf
        bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
        Printer aPrinter = m_aPrinters[ aPrinterName ];
        if( bSetToGlobalDefaults )
            aPrinter.m_aInfo = m_aGlobalDefaults;
        aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
        if( pDest->is_default )
            m_aDefaultPrinter = aPrinterName;

        for( int k = 0; k < pDest->num_options; k++ )
        {
            if(!strcmp(pDest->options[k].name, "printer-info"))
                aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
            if(!strcmp(pDest->options[k].name, "printer-location"))
                aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
        }


        OUStringBuffer aBuf( 256 );
        aBuf.appendAscii( "CUPS:" );
        aBuf.append( aPrinterName );
        // note: the parser that goes with the PrinterInfo
        // is created implicitly by the JobData::operator=()
        // when it detects the NULL ptr m_pParser.
        // if we wanted to fill in the parser here this
        // would mean we'd have to download PPDs for each and
        // every printer - which would be really bad runtime
        // behaviour
        aPrinter.m_aInfo.m_pParser = NULL;
        aPrinter.m_aInfo.m_aContext.setParser( NULL );
        boost::unordered_map< OUString, PPDContext, OUStringHash >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
        if( c_it != m_aDefaultContexts.end() )
        {
            aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
            aPrinter.m_aInfo.m_aContext = c_it->second;
        }
        if( bUsePDF && aPrinter.m_aInfo.m_nPSLevel == 0 && aPrinter.m_aInfo.m_nPDFDevice == 0 )
            aPrinter.m_aInfo.m_nPDFDevice = 1;
        aPrinter.m_aInfo.m_aDriverName = aBuf.makeStringAndClear();
        aPrinter.m_bModified = false;

        m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
        m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
    }

    // remove everything that is not a CUPS printer and not
    // a special purpose printer (PDF, Fax)
    std::list< OUString > aRemovePrinters;
    for( boost::unordered_map< OUString, Printer, OUStringHash >::iterator it = m_aPrinters.begin();
         it != m_aPrinters.end(); ++it )
    {
        if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
            continue;

        if( it->second.m_aInfo.m_aFeatures.getLength() > 0 )
            continue;
        aRemovePrinters.push_back( it->first );
    }
    while( aRemovePrinters.begin() != aRemovePrinters.end() )
    {
        m_aPrinters.erase( aRemovePrinters.front() );
        aRemovePrinters.pop_front();
    }

    m_pCUPSWrapper->cupsSetPasswordCB( setPasswordCallback );
#endif // ENABLE_CUPS
}

#ifdef ENABLE_CUPS
static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
{
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
    for( int i = 0; i < pPPDGroup->num_options; i++ )
    {
        ppd_option_t* pOption = pPPDGroup->options + i;
        for( int n = 0; n < pOption->num_choices; n++ )
        {
            ppd_choice_t* pChoice = pOption->choices + n;
            if( pChoice->marked )
            {
                const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
                if( pKey )
                {
                    const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
                    if( pValue )
                    {
                        if( pValue != pKey->getDefaultValue() )
                        {
                            rContext.setValue( pKey, pValue, true );
#if OSL_DEBUG_LEVEL > 1
                            fprintf( stderr, "key %s is set to %s\n", pOption->keyword, pChoice->choice );
#endif
                            
                        }                       
#if OSL_DEBUG_LEVEL > 1
                        else
                            fprintf( stderr, "key %s is defaulted to %s\n", pOption->keyword, pChoice->choice );
#endif
                    }
#if OSL_DEBUG_LEVEL > 1
                    else
                        fprintf( stderr, "caution: value %s not found in key %s\n", pChoice->choice, pOption->keyword );
#endif
                }
#if OSL_DEBUG_LEVEL > 1
                else
                    fprintf( stderr, "caution: key %s not found in parser\n", pOption->keyword );
#endif
            }
        }
    }

    // recurse through subgroups
    for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
    {
        updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
    }
}
#endif // ENABLE_CUPS

const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
{
    const PPDParser* pNewParser = NULL;
    OUString aPrinter;

    if( rPrinter.compareToAscii( "CUPS:", 5 ) == 0 )
        aPrinter = rPrinter.copy( 5 );
    else
        aPrinter = rPrinter;

#ifdef ENABLE_CUPS
    if( m_aCUPSMutex.tryToAcquire() )
    {
        if( m_nDests && m_pDests && ! isCUPSDisabled() )
        {
            boost::unordered_map< OUString, int, OUStringHash >::iterator dest_it =
            m_aCUPSDestMap.find( aPrinter );
            if( dest_it != m_aCUPSDestMap.end() )
            {
                cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + dest_it->second;
                OString aPPDFile = m_pCUPSWrapper->cupsGetPPD( pDest->name );
                #if OSL_DEBUG_LEVEL > 1
                fprintf( stderr, "PPD for %s is %s\n", OUStringToOString( aPrinter, osl_getThreadTextEncoding() ).getStr(), aPPDFile.getStr() );
                #endif
                if( aPPDFile.getLength() )
                {
                    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
                    OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
                    // update the printer info with context information
                    ppd_file_t* pPPD = m_pCUPSWrapper->ppdOpenFile( aPPDFile.getStr() );
                    if( pPPD )
                    {
                        // create the new parser
                        PPDParser* pCUPSParser = new PPDParser( aFileName );
                        pCUPSParser->m_aFile = rPrinter;
                        pNewParser = pCUPSParser;
                        
                        /*int nConflicts =*/ m_pCUPSWrapper->cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
                        #if OSL_DEBUG_LEVEL > 1
                        fprintf( stderr, "processing the following options for printer %s (instance %s):\n",
                        pDest->name, pDest->instance );
                        for( int k = 0; k < pDest->num_options; k++ )
                            fprintf( stderr, "   \"%s\" = \"%s\"\n",
                        pDest->options[k].name,
                        pDest->options[k].value );
                        #endif
                        PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
                        
                        // remember the default context for later use
                        PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
                        rContext.setParser( pNewParser );
                        // set system default paper; printer CUPS PPD options
                        // may overwrite it
                        setDefaultPaper( rContext );
                        for( int i = 0; i < pPPD->num_groups; i++ )
                            updatePrinterContextInfo( pPPD->groups + i, rContext );
                        
                        rInfo.m_pParser = pNewParser;
                        rInfo.m_aContext = rContext;
                        
                        // clean up the mess
                        m_pCUPSWrapper->ppdClose( pPPD );                        
                    }
                    #if OSL_DEBUG_LEVEL > 1
                    else
                        fprintf( stderr, "ppdOpenFile failed, falling back to generic driver\n" );
                    #endif
                    
                    // remove temporary PPD file
                    unlink( aPPDFile.getStr() );
                }
                #if OSL_DEBUG_LEVEL > 1
                else
                    fprintf( stderr, "cupsGetPPD failed, falling back to generic driver\n" );
                #endif
            }
            #if OSL_DEBUG_LEVEL > 1
            else
                fprintf( stderr, "no dest found for printer %s\n", OUStringToOString( aPrinter, osl_getThreadTextEncoding() ).getStr() );
            #endif
        }
        m_aCUPSMutex.release();
    }
    #if OSL_DEBUG_LEVEL >1
    else
        fprintf( stderr, "could not acquire CUPS mutex !!!\n" );
    #endif
    #endif // ENABLE_CUPS

    if( ! pNewParser )
    {
        // get the default PPD
        pNewParser = PPDParser::getParser( String( RTL_CONSTASCII_USTRINGPARAM( "SGENPRT" ) ) );
        
        PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
        
        rInfo.m_pParser = pNewParser;
        rInfo.m_aContext.setParser( pNewParser );
    }

    return pNewParser;
}

void CUPSManager::setupJobContextData(
    JobData&
#ifdef ENABLE_CUPS
    rData
#endif
)
{
#ifdef ENABLE_CUPS
    boost::unordered_map< OUString, int, OUStringHash >::iterator dest_it =
        m_aCUPSDestMap.find( rData.m_aPrinterName );

    if( dest_it == m_aCUPSDestMap.end() )
        return PrinterInfoManager::setupJobContextData( rData );

    boost::unordered_map< OUString, Printer, OUStringHash >::iterator p_it =
        m_aPrinters.find( rData.m_aPrinterName );
    if( p_it == m_aPrinters.end() ) // huh ?
    {
#if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "CUPS printer list in disorder, no dest for printer %s !\n", OUStringToOString( rData.m_aPrinterName, osl_getThreadTextEncoding() ).getStr() );
#endif
        return;
    }

    if( p_it->second.m_aInfo.m_pParser == NULL )
    {
        // in turn calls createCUPSParser
        // which updates the printer info
        p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
    }
    if( p_it->second.m_aInfo.m_aContext.getParser() == NULL )
    {
        OUString aPrinter;
        if( p_it->second.m_aInfo.m_aDriverName.compareToAscii( "CUPS:", 5 ) == 0 )
            aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
        else
            aPrinter = p_it->second.m_aInfo.m_aDriverName;

        p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
    }

    rData.m_pParser		= p_it->second.m_aInfo.m_pParser;
    rData.m_aContext	= p_it->second.m_aInfo.m_aContext;
#endif
}

FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
{
    OSL_TRACE( "endSpool: %s, %s",
               rtl::OUStringToOString( rPrintername, RTL_TEXTENCODING_UTF8 ).getStr(),
              bQuickCommand ? "true" : "false" );
    
    if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
    {
        OSL_TRACE( "defer to PrinterInfoManager::startSpool" );
        return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
    }
    
#ifdef ENABLE_CUPS
    OUString aTmpURL, aTmpFile;
    osl_createTempFile( NULL, NULL, &aTmpURL.pData );
    osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
    OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
    FILE* fp = fopen( aSysFile.getStr(), "w" );
    if( fp )
        m_aSpoolFiles[fp] = aSysFile;
    
    return fp;
#else
    return NULL;
#endif
}

struct less_ppd_key : public ::std::binary_function<double, double, bool>
{
    bool operator()(const PPDKey* left, const PPDKey* right) 
    { return left->getOrderDependency() < right->getOrderDependency(); }
};

void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions ) const
{
    rNumOptions = 0;
    *rOptions = NULL;

    // emit features ordered to OrderDependency
    // ignore features that are set to default

    // sanity check
    if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
    {
        int i;
        int nKeys = rJob.m_aContext.countValuesModified();
        ::std::vector< const PPDKey* > aKeys( nKeys );
        for(  i = 0; i < nKeys; i++ )
            aKeys[i] = rJob.m_aContext.getModifiedKey( i );
        ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );

        for( i = 0; i < nKeys; i++ )
        {
            const PPDKey* pKey = aKeys[i];
            const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
            if(pValue && pValue->m_eType == eInvocation && pValue->m_aValue.Len() )
            {
                OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
                OString aValue = OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US );
                rNumOptions = m_pCUPSWrapper->cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, (cups_option_t**)rOptions );
            }
        }
    }
    
    if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
    {
        rtl::OString aVal( rtl::OString::valueOf( sal_Int32( rJob.m_nCopies ) ) );
        rNumOptions = m_pCUPSWrapper->cupsAddOption( "copies", aVal.getStr(), rNumOptions, (cups_option_t**)rOptions );
    }
    if( ! bBanner )
    {
        rNumOptions = m_pCUPSWrapper->cupsAddOption( "job-sheets", "none", rNumOptions, (cups_option_t**)rOptions );
    }
}

int CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner )
{
    OSL_TRACE( "endSpool: %s, %s, copy count = %d",
               rtl::OUStringToOString( rPrintername, RTL_TEXTENCODING_UTF8 ).getStr(),
               rtl::OUStringToOString( rJobTitle, RTL_TEXTENCODING_UTF8 ).getStr(),
               rDocumentJobData.m_nCopies
               );
    
    int nJobID = 0;

    osl::MutexGuard aGuard( m_aCUPSMutex );
    
    boost::unordered_map< OUString, int, OUStringHash >::iterator dest_it =
        m_aCUPSDestMap.find( rPrintername );
    if( dest_it == m_aCUPSDestMap.end() )
    {
        OSL_TRACE( "defer to PrinterInfoManager::endSpool" );
        return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner );
    }

    #ifdef ENABLE_CUPS
    boost::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
    if( it != m_aSpoolFiles.end() )
    {
        fclose( pFile );
        rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
        
        // setup cups options
        int nNumOptions = 0;
        cups_option_t* pOptions = NULL;
        getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, (void**)&pOptions );

        cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + dest_it->second;
        nJobID = m_pCUPSWrapper->cupsPrintFile( pDest->name,
        it->second.getStr(),
        OUStringToOString( rJobTitle, aEnc ).getStr(),
        nNumOptions, pOptions );
#if OSL_DEBUG_LEVEL > 1
        fprintf( stderr, "cupsPrintFile( %s, %s, %s, %d, %p ) returns %d\n",
                    pDest->name,
                    it->second.getStr(),
                    OUStringToOString( rJobTitle, aEnc ).getStr(),
                    nNumOptions,
                    pOptions,
                    nJobID
                    );
        for( int n = 0; n < nNumOptions; n++ )
            fprintf( stderr, "    option %s=%s\n", pOptions[n].name, pOptions[n].value );
        OString aCmd( "cp " );
        aCmd = aCmd + it->second;
        aCmd = aCmd + OString( " $HOME/cupsprint.ps" );
        system( aCmd.getStr() );
#endif
        
        unlink( it->second.getStr() );
        m_aSpoolFiles.erase( pFile );
        if( pOptions )
            m_pCUPSWrapper->cupsFreeOptions( nNumOptions, pOptions );
    }
#endif // ENABLE_CUPS

    return nJobID;
}


void CUPSManager::changePrinterInfo( const OUString& rPrinter, const PrinterInfo& rNewInfo )
{
    PrinterInfoManager::changePrinterInfo( rPrinter, rNewInfo );
}

bool CUPSManager::checkPrintersChanged( bool bWait )
{
    bool bChanged = false;
    if( bWait )
    {
        if(  m_aDestThread )
        {
            // initial asynchronous detection still running
            #if OSL_DEBUG_LEVEL > 1
            fprintf( stderr, "syncing cups discovery thread\n" );
            #endif
            osl_joinWithThread( m_aDestThread );
            osl_destroyThread( m_aDestThread );
            m_aDestThread = NULL;
            #if OSL_DEBUG_LEVEL > 1
            fprintf( stderr, "done: syncing cups discovery thread\n" );
            #endif
        }
        else
        {
            // #i82321# check for cups printer updates
            // with this change the whole asynchronous detection in a thread is
            // almost useless. The only relevance left is for some stalled systems
            // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
            // (see vcl/unx/source/gdi/salprnpsp.cxx)
            // so that checkPrintersChanged( true ) will never be called
            
            // there is no way to query CUPS whether the printer list has changed
            // so get the dest list anew
            if( m_nDests && m_pDests )
                m_pCUPSWrapper->cupsFreeDests( m_nDests, (cups_dest_t*)m_pDests );
            m_nDests = 0;
            m_pDests = NULL;
            runDests();
        }
    }
    if( m_aCUPSMutex.tryToAcquire() )
    {
        bChanged = m_bNewDests;
        m_aCUPSMutex.release();
    }

    if( ! bChanged )
    {
        bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
        // #i54375# ensure new merging with CUPS list in :initialize
        if( bChanged )
            m_bNewDests = true;
    }
    
    if( bChanged )
        initialize();

    return bChanged;
}

bool CUPSManager::addPrinter( const OUString& rName, const OUString& rDriver )
{
    // don't touch the CUPS printers
    if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() ||
        rDriver.compareToAscii( "CUPS:", 5 ) == 0
        )
        return false;
    return PrinterInfoManager::addPrinter( rName, rDriver );
}

bool CUPSManager::removePrinter( const OUString& rName, bool bCheck )
{
    // don't touch the CUPS printers
    if( m_aCUPSDestMap.find( rName ) != m_aCUPSDestMap.end() )
        return false;
    return PrinterInfoManager::removePrinter( rName, bCheck );
}

bool CUPSManager::setDefaultPrinter( const OUString& rName )
{
    bool bSuccess = false;
#ifdef ENABLE_CUPS
    boost::unordered_map< OUString, int, OUStringHash >::iterator nit =
        m_aCUPSDestMap.find( rName );
    if( nit != m_aCUPSDestMap.end() && m_aCUPSMutex.tryToAcquire() )
    {
        cups_dest_t* pDests = (cups_dest_t*)m_pDests;
        for( int i = 0; i < m_nDests; i++ )
            pDests[i].is_default = 0;
        pDests[ nit->second ].is_default = 1;
        m_pCUPSWrapper->cupsSetDests( m_nDests, (cups_dest_t*)m_pDests );
        m_aDefaultPrinter = rName;
        m_aCUPSMutex.release();
        bSuccess = true;
    }
    else
#endif
        bSuccess = PrinterInfoManager::setDefaultPrinter( rName );
        
    return bSuccess;
}

bool CUPSManager::writePrinterConfig()
{
#ifdef ENABLE_CUPS
    bool bDestModified = false;
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();

    for( boost::unordered_map< OUString, Printer, OUStringHash >::iterator prt =
             m_aPrinters.begin(); prt != m_aPrinters.end(); ++prt )
    {
        boost::unordered_map< OUString, int, OUStringHash >::iterator nit =
            m_aCUPSDestMap.find( prt->first );
        if( nit == m_aCUPSDestMap.end() )
            continue;
        
        if( ! prt->second.m_bModified )
            continue;

        if( m_aCUPSMutex.tryToAcquire() )
        {
            bDestModified = true;
            cups_dest_t* pDest = ((cups_dest_t*)m_pDests) + nit->second;
            PrinterInfo& rInfo = prt->second.m_aInfo;

            // create new option list
            int nNewOptions = 0;
            cups_option_t* pNewOptions = NULL; 
            int nValues = rInfo.m_aContext.countValuesModified();
            for( int i = 0; i < nValues; i++ )
            {
                const PPDKey* pKey = rInfo.m_aContext.getModifiedKey( i );
                const PPDValue* pValue = rInfo.m_aContext.getValue( pKey );
                if( pKey && pValue ) // sanity check
                {
                    OString aName = OUStringToOString( pKey->getKey(), aEncoding );
                    OString aValue = OUStringToOString( pValue->m_aOption, aEncoding );
                    nNewOptions = m_pCUPSWrapper->cupsAddOption( aName.getStr(), aValue.getStr(), nNewOptions, &pNewOptions );
                }
            }
            // set PPD options on CUPS dest
            m_pCUPSWrapper->cupsFreeOptions( pDest->num_options, pDest->options );
            pDest->num_options = nNewOptions;
            pDest->options = pNewOptions;
            m_aCUPSMutex.release();
        }
    }
    if( bDestModified && m_aCUPSMutex.tryToAcquire() )
    {
        m_pCUPSWrapper->cupsSetDests( m_nDests, (cups_dest_t*)m_pDests );
        m_aCUPSMutex.release();
    }
#endif // ENABLE_CUPS

    return PrinterInfoManager::writePrinterConfig();
}

bool CUPSManager::addOrRemovePossible() const
{
    return (m_nDests && m_pDests && ! isCUPSDisabled())? false : PrinterInfoManager::addOrRemovePossible();
}

#include <rtsname.hxx>

const char* CUPSManager::authenticateUser( const char* /*pIn*/ )
{
    const char* pRet = NULL;

#ifdef ENABLE_CUPS
    OUString aLib(RTL_CONSTASCII_USTRINGPARAM( _XSALSET_LIBNAME ));
    oslModule pLib = osl_loadModule( aLib.pData, SAL_LOADMODULE_LAZY );
    if( pLib )
    {
        OUString aSym( RTL_CONSTASCII_USTRINGPARAM( "Sal_authenticateQuery" ) );
        bool (*getpw)( const OString& rServer, OString& rUser, OString& rPw) =
            (bool(*)(const OString&,OString&,OString&))osl_getFunctionSymbol( pLib, aSym.pData );
        if( getpw )
        {
            osl::MutexGuard aGuard( m_aCUPSMutex );

            OString aUser = m_pCUPSWrapper->cupsUser();
            OString aServer = m_pCUPSWrapper->cupsServer();
            OString aPassword;
            if( getpw( aServer, aUser, aPassword ) )
            {       
                m_aPassword = aPassword;
                m_aUser = aUser;
                m_pCUPSWrapper->cupsSetUser( m_aUser.getStr() );
                pRet = m_aPassword.getStr();
            }   	
        }
        osl_unloadModule( pLib );
    }
#if OSL_DEBUG_LEVEL > 1
    else fprintf( stderr, "loading of module %s failed\n", OUStringToOString( aLib, osl_getThreadTextEncoding() ).getStr() );
#endif
#endif // ENABLE_CUPS

    return pRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
