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

#include "vcl/svapp.hxx"
#include "vcl/sysdata.hxx"

#include "aqua/salvd.h"
#include "aqua/salinst.h"
#include "aqua/salgdi.h"
#include "aqua/saldata.hxx"
#include "aqua/salframe.h"

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

SalVirtualDevice* AquaSalInstance::CreateVirtualDevice( SalGraphics* pGraphics,
	long nDX, long nDY, sal_uInt16 nBitCount, const SystemGraphicsData *pData )
{
    // #i92075# can be called first in a thread
    SalData::ensureThreadAutoreleasePool();
    
	return new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >( pGraphics ), nDX, nDY, nBitCount, pData );
}

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

void AquaSalInstance::DestroyVirtualDevice( SalVirtualDevice* pDevice )
{
	delete pDevice;
}

// =======================================================================

AquaSalVirtualDevice::AquaSalVirtualDevice( AquaSalGraphics* pGraphic, long nDX, long nDY, sal_uInt16 nBitCount, const SystemGraphicsData *pData )
:	mbGraphicsUsed( false )
,	mxBitmapContext( NULL )
,	mnBitmapDepth( 0 )
,	mxLayer( NULL )
{
    if( pGraphic && pData && pData->rCGContext )
	{
	    // Create virtual device based on existing SystemGraphicsData
        // We ignore nDx and nDY, as the desired size comes from the SystemGraphicsData
        mbForeignContext = true;        // the mxContext is from pData
	    mpGraphics = new AquaSalGraphics( /*pGraphic*/ );
	    mpGraphics->SetVirDevGraphics( mxLayer, pData->rCGContext );
	}
	else
	{
	    // create empty new virtual device
        mbForeignContext = false;           // the mxContext is created within VCL
        mpGraphics = new AquaSalGraphics(); // never fails
		mnBitmapDepth = nBitCount;

        // inherit resolution from reference device
        if( pGraphic )
        {
            AquaSalFrame* pFrame = pGraphic->getGraphicsFrame();
            if( pFrame && AquaSalFrame::isAlive( pFrame ) )
            {
                mpGraphics->setGraphicsFrame( pFrame );
                mpGraphics->copyResolution( *pGraphic );
            }
        }

        if( nDX && nDY )
            SetSize( nDX, nDY );
        
        // NOTE: if SetSize does not succeed, we just ignore the nDX and nDY
	}
}

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

AquaSalVirtualDevice::~AquaSalVirtualDevice()
{
	if( mpGraphics )
	{
		mpGraphics->SetVirDevGraphics( NULL, NULL );
		delete mpGraphics;
		mpGraphics = 0;
	}
	Destroy();
}

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

void AquaSalVirtualDevice::Destroy()
{
    if( mbForeignContext ) {
        // Do not delete mxContext that we have received from outside VCL
		mxLayer = NULL;
        return;
    }

	if( mxLayer )
	{
		if( mpGraphics )
			mpGraphics->SetVirDevGraphics( NULL, NULL );
		CGLayerRelease( mxLayer );
		mxLayer = NULL;
	}

	if( mxBitmapContext )
	{
		void* pRawData = CGBitmapContextGetData( mxBitmapContext );
		rtl_freeMemory( pRawData );
		CGContextRelease( mxBitmapContext );
		mxBitmapContext = NULL;
	}
}

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

SalGraphics* AquaSalVirtualDevice::GetGraphics()
{
	if( mbGraphicsUsed || !mpGraphics )
		return 0;

	mbGraphicsUsed = true;
	return mpGraphics;
}

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

void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* )
{
	mbGraphicsUsed = false;
}

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

sal_Bool AquaSalVirtualDevice::SetSize( long nDX, long nDY )
{
    if( mbForeignContext )
    {
        // Do not delete/resize mxContext that we have received from outside VCL
        return true;
    }

    if( mxLayer )
    {
    	const CGSize aSize = CGLayerGetSize( mxLayer );
        if( (nDX == aSize.width) && (nDY == aSize.height) )
	    {
	        // Yay, we do not have to do anything :)
			return true;
	    }
    }

    Destroy();

	// create a Quartz layer matching to the intended virdev usage
	CGContextRef xCGContext = NULL;
	if( mnBitmapDepth && (mnBitmapDepth < 16) )
	{
		mnBitmapDepth = 8;  // TODO: are 1bit vdevs worth it?
		const CGColorSpaceRef aCGColorSpace = GetSalData()->mxGraySpace;
		const CGBitmapInfo aCGBmpInfo = kCGImageAlphaNone;
		const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;

		void* pRawData = rtl_allocateMemory( nBytesPerRow * nDY );
		mxBitmapContext = ::CGBitmapContextCreate( pRawData, nDX, nDY,
			mnBitmapDepth, nBytesPerRow, aCGColorSpace, aCGBmpInfo );
		xCGContext = mxBitmapContext;
	}
	else
	{
		// default to a NSView target context, any will do
        AquaSalFrame* pSalFrame = mpGraphics->getGraphicsFrame();
        if( !pSalFrame || !AquaSalFrame::isAlive( pSalFrame ))
            if( !GetSalData()->maFrames.empty() )
                pSalFrame = *GetSalData()->maFrames.begin();
        if( pSalFrame )
        {
            // #i91990#
            NSWindow* pWindow = pSalFrame->getWindow();
            if ( pWindow )
            {
                NSGraphicsContext* pNSContext = [NSGraphicsContext graphicsContextWithWindow: pWindow];
                if( pNSContext )
                    xCGContext = reinterpret_cast<CGContextRef>([pNSContext graphicsPort]);                
            }
            else
            {
                // fall back to a bitmap context
                mnBitmapDepth = 32;
                const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
                const CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
                const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
                
                void* pRawData = rtl_allocateMemory( nBytesPerRow * nDY );
                mxBitmapContext = ::CGBitmapContextCreate( pRawData, nDX, nDY,
                                                           8, nBytesPerRow, aCGColorSpace, aCGBmpInfo );
                xCGContext = mxBitmapContext;
            }
        }
	}

	DBG_ASSERT( xCGContext, "no context" );
    
	const CGSize aNewSize = { nDX, nDY };
	mxLayer = CGLayerCreateWithContext( xCGContext, aNewSize, NULL );

	if( mxLayer && mpGraphics )
	{
		// get the matching Quartz context
		CGContextRef xDrawContext = CGLayerGetContext( mxLayer );
		mpGraphics->SetVirDevGraphics( mxLayer, xDrawContext, mnBitmapDepth );
	}

	return (mxLayer != NULL);
}

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

void AquaSalVirtualDevice::GetSize( long& rWidth, long& rHeight )
{
	if( mxLayer )
	{
    	const CGSize aSize = CGLayerGetSize( mxLayer );
		rWidth = static_cast<long>(aSize.width);
		rHeight = static_cast<long>(aSize.height);
	}
	else
	{
		rWidth = 0;
		rHeight = 0;
	}
}