////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2005-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.printing
{

import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
import flash.geom.Rectangle;
import flash.printing.PrintJob;
import flash.printing.PrintJobOptions;
import mx.core.FlexVersion;
import mx.core.IFlexDisplayObject;
import mx.core.IUIComponent;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;

use namespace mx_internal;

/**
 *  The FlexPrintJob class is a wrapper for the flash.printing.PrintJob class.
 *  It supports automatically slicing and paginating the output on multilple pages,
 *  and scaling the grid contents to fit the printer's page size.
 *
 *  @includeExample examples/FormPrintHeader.mxml -noswf
 *  @includeExample examples/FormPrintFooter.mxml -noswf
 *  @includeExample examples/FormPrintView.mxml -noswf
 *  @includeExample examples/PrintDataGridExample.mxml
 *
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class FlexPrintJob
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function FlexPrintJob()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private var printJob:PrintJob = new PrintJob();

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  pageHeight
    //----------------------------------

    /**
     *  @private
     *  Storage for the pageHeight property.
     */
    private var _pageHeight:Number = 0;

    /**
     *  The height  of the printable area on the printer page; 
     *  it does not include any user-set margins. 
     *  It is set after start() method returns.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get pageHeight():Number
    {
        return _pageHeight;
    }

    //----------------------------------
    //  pageWidth
    //----------------------------------

    /**
     *  @private
     *  Storage for the pageWidth property.
     */
    private var _pageWidth:Number = 0;
    
    /**
     *  The width of the printable area on the printer page;
     *  it does not include any user-set margins.
     *  This property is set after <code>start()</code> method returns.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get pageWidth():Number
    {
        return _pageWidth;
    }

    //----------------------------------
    //  printAsBitmap
    //----------------------------------

    /**
     *  @private
     *  Storage for the printAsBitmap property.
     */
    private var _printAsBitmap:Boolean = true;

    /**
     *  Specifies whether to print the job content as a bitmap (<code>true</code>)
     *  or in vector format (<code>false</code>). 
     *  Printing as a bitmap supports output that includes a bitmap image with 
     *  alpha transparency or color effects. 
     *  If the content does not include any bitmap images with
     *  alpha transparency or color effects, you can print in higher quality
     *  vector format by setting the <code>printAsBitmap</code> property to 
     *  <code>false</code>.
     * 
     *  @default true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get printAsBitmap():Boolean
    {
        return _printAsBitmap;
    }

    /**
     *  @private
     */
    public function set printAsBitmap(value:Boolean):void
    {
        _printAsBitmap = value;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Initializes the PrintJob object.
     *  Displays the operating system printer dialog to the user.
     *  Flex sets the <code>pageWidth</code> and <code>pageHeight</code>
     *  properties after this call returns.
     *
     *  @return <code>true</code> if the user clicks OK
     *  when the print dialog box appears, or <code>false</code> if the user
     *  clicks Cancel or if an error occurs.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function start():Boolean
    {
        var ok:Boolean = printJob.start();
        
        if (ok)
        {
            _pageWidth = printJob.pageWidth;
            _pageHeight = printJob.pageHeight;
        }
        
        return ok;
    }

    /**
     *  Adds a UIComponent object to the list of objects being printed.
     *  Call this method after the <code>start()</code> method returns.
     *  Each call to this method starts a new page, so you should format 
     *  your objects in page-sized chunks. 
     *  You can use the PrintDataGrid class to span a data grid across
     *  multiple pages.
     * 
     *  @see PrintDataGrid
     *  @see FlexPrintJobScaleType
     *
     *  @param obj The Object to be printed.
     * 
     *  @param scaleType The scaling technique to use to control how the 
     *  object fits on one or more printed pages. 
     *  Must be one of the constant values defined in the FlexPrintJobScaleType
     *  class.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function addObject(obj:IUIComponent,
                              scaleType:String = "matchWidth"):void
    {
        var objWidth:Number;
        var objHeight:Number;
        
        var objPercWidth:Number;
        var objPercHeight:Number;

        var n:int;
        var i:int;
        var j:int;

        var child:IFlexDisplayObject;
        var childPercentSizes:Object = {};

        var appExplicitWidth:Number;
        var appExplicitHeight:Number;
        var applicationClass:Class = Class(obj.systemManager.getDefinitionByName("mx.core::Application"));
        var fxApplicationClass:Class = Class(obj.systemManager.getDefinitionByName("spark.components::Application"));

        if ((applicationClass && obj is applicationClass) || 
            (fxApplicationClass && obj is fxApplicationClass))
        {
            // The following loop is required only for scenario where 
            // application may have a few children with percent
            // width or height.
            n = obj["numElements"];
            for (i = 0; i < n; i++)
            {
                child = IFlexDisplayObject(obj["getElementAt"](i));
                
                if (child is UIComponent &&
                    (!isNaN(UIComponent(child).percentWidth) ||
                     !isNaN(UIComponent(child).percentHeight)))
                {
                    childPercentSizes[child.name] = {};
                    
                    if (!isNaN(UIComponent(child).percentWidth) &&
                        isNaN(UIComponent(child).explicitWidth))
                    {
                        childPercentSizes[child.name].percentWidth =
                            UIComponent(child).percentWidth;
                        UIComponent(child).percentWidth = NaN;
                        UIComponent(child).explicitWidth =
                            UIComponent(child).width;
                    }

                    if (!isNaN(UIComponent(child).percentHeight) &&
                        isNaN(UIComponent(child).explicitHeight))
                    {
                        childPercentSizes[child.name].percentHeight =
                            UIComponent(child).percentHeight;
                        UIComponent(child).percentHeight = NaN;
                        UIComponent(child).explicitHeight =
                            UIComponent(child).height;
                    }
                }
            }

            if (!isNaN(UIComponent(obj).explicitWidth) 
                && !isNaN(UIComponent(obj).explicitHeight))
            {
                appExplicitWidth = UIComponent(obj).explicitWidth;
                appExplicitHeight = UIComponent(obj).explicitHeight;
                
                UIComponent(obj).explicitWidth = NaN;
                UIComponent(obj).explicitHeight = NaN;

                UIComponent(obj).measuredWidth = appExplicitWidth;
                UIComponent(obj).measuredHeight = appExplicitHeight;
            }

            if (isNaN(obj.percentWidth) && isNaN(obj.percentHeight))
                UIComponent(obj).invalidateSizeFlag = false;

            UIComponent(obj).validateSize();

            objWidth = obj.measuredWidth;
            objHeight = obj.measuredHeight;
			if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_4_0)
			{
				objWidth *= obj.scaleX;
				objHeight *= obj.scaleY;
			}
        }
        else
        {
            // Lock if the content is percent width or height.
            if (!isNaN(obj.percentWidth) && isNaN(obj.explicitWidth))
            {
                objPercWidth = obj.percentWidth;
                obj.percentWidth = NaN;
                obj.explicitWidth = obj.width;
            }
            
            if (!isNaN(obj.percentHeight) && isNaN(obj.explicitHeight))
            {
                objPercHeight = obj.percentHeight;
                obj.percentHeight = NaN;
                obj.explicitHeight = obj.height;
            }
            
            objWidth = obj.getExplicitOrMeasuredWidth();
            objHeight = obj.getExplicitOrMeasuredHeight();
			if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_4_0)
			{
				objWidth *= obj.scaleX;
				objHeight *= obj.scaleY;
			}
        }

        var widthRatio:Number = _pageWidth/objWidth;
        var heightRatio:Number = _pageHeight/objHeight;

        var ratio:Number = 1;

        if (scaleType == FlexPrintJobScaleType.SHOW_ALL)
        {
            // Smaller of the two ratios for showAll.
            ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio;
        }
        else if (scaleType == FlexPrintJobScaleType.FILL_PAGE)
        {
            // Bigger of the two ratios for fillPage.
            ratio = (widthRatio > heightRatio) ? widthRatio : heightRatio;
        }
        else if (scaleType == FlexPrintJobScaleType.NONE)
        {
        }
        else if (scaleType == FlexPrintJobScaleType.MATCH_HEIGHT)
        {
            ratio = heightRatio;
        }
        else
        {
            ratio = widthRatio;
        }

        // Scale it to the required value.
        obj.scaleX *= ratio;
        obj.scaleY *= ratio;
        
        UIComponentGlobals.layoutManager.usePhasedInstantiation = false;
        UIComponentGlobals.layoutManager.validateNow(); 

        var arrPrintData:Array = prepareToPrintObject(obj);

        if ((applicationClass && obj is applicationClass) || 
            (fxApplicationClass && obj is fxApplicationClass))
        {
            objWidth *= ratio;
            objHeight *= ratio;
        }
        else
        {
            objWidth = obj.getExplicitOrMeasuredWidth();
            objHeight = obj.getExplicitOrMeasuredHeight();
			if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_4_0)
			{
				objWidth *= obj.scaleX;
				objHeight *= obj.scaleY;
			}
        }

        // Find the number of pages required in vertical and horizontal.
        var hPages:int = Math.ceil(objWidth / _pageWidth);
        var vPages:int = Math.ceil(objHeight / _pageHeight);

        // when sent to addPage, scaling is to be ignored.
        var incrX:Number = _pageWidth / ratio;
        var incrY:Number = _pageHeight / ratio;

        var lastPageWidth:Number = (objWidth % _pageWidth) / ratio;
        var lastPageHeight:Number = (objHeight % _pageHeight) / ratio;

        for (j = 0; j < vPages; j++)
        {
            for (i = 0; i < hPages; i++)
            {
                var r:Rectangle =
                    new Rectangle(i * incrX, j * incrY, incrX, incrY);

                // For last pages send only the remaining amount
                // so that rest of the paper is printed white
                // else it prints that in gray.
                if (i == hPages - 1 && lastPageWidth != 0)
                    r.width = lastPageWidth;

                if (j == vPages - 1 && lastPageHeight != 0)
                    r.height = lastPageHeight;

                // The final edge may have got fractioned as
                // contents may not be complete multiple of pageWidth/Height.
                // This may result in a blank area at the end of page.
                // Tthis rounding off ensures no small blank area in the end 
                // but results in some part of next page getting reprinted
                // this page but it does not result in loss of any information.
                r.width = Math.ceil(r.width);
                r.height = Math.ceil(r.height);

                var printJobOptions:PrintJobOptions = new PrintJobOptions();
                printJobOptions.printAsBitmap = _printAsBitmap;

                printJob.addPage(Sprite(obj), r, printJobOptions);
            }
        }

        finishPrintObject(obj, arrPrintData);

        // Scale it back.
        obj.scaleX /= ratio;
        obj.scaleY /= ratio;

        if ((applicationClass && obj is applicationClass) || 
            (fxApplicationClass && obj is fxApplicationClass))
        {
            if (!isNaN(appExplicitWidth)) //&& !isNaN(appExplicitHeight))
            {
				// use setActualSize so it doesn't invalidate.
				// nobody else should be resizing unless it is a sub-app
				UIComponent(obj).setActualSize(appExplicitWidth,appExplicitHeight);
				
				// is it a sub-app?
				if (!obj.systemManager.isTopLevelRoot())
				{
					// invalidate for sub-apps since they have to be re-layed out by
					// the SWFLoader in some cases
                	UIComponent(obj).explicitWidth = appExplicitWidth;
                	UIComponent(obj).explicitHeight = appExplicitHeight;
				}

                appExplicitWidth = NaN;
                appExplicitHeight = NaN;

                UIComponent(obj).measuredWidth = 0;
                UIComponent(obj).measuredHeight = 0;
            }

            // The following loop is required only for scenario
            // where application may have a few children
            // with percent width or height.
            n = obj["numElements"];
            for (i = 0; i < n; i++)
            {
                child = IFlexDisplayObject(obj["getElementAt"](i));
                if (child is UIComponent && childPercentSizes[child.name])
                {
                    var childPercentSize:Object = childPercentSizes[child.name];
                    if (childPercentSize &&
                        !isNaN(childPercentSize.percentWidth))
                    {
                        UIComponent(child).percentWidth =
                            childPercentSize.percentWidth;
                        UIComponent(child).explicitWidth = NaN;
                    }

                    if (childPercentSize &&
                        !isNaN(childPercentSize.percentHeight))
                    {
                        UIComponent(child).percentHeight =
                            childPercentSize.percentHeight;
                        UIComponent(child).explicitHeight = NaN;
                    }
                }
            }
            UIComponent(obj).invalidateSizeFlag = false;
            UIComponent(obj).validateSize();
        }
        else
        {
            // Unlock if the content was percent width or height.
            if (!isNaN(objPercWidth))
            {
                obj.percentWidth = objPercWidth;
                obj.explicitWidth = NaN;
            }

            if (!isNaN(objPercHeight))
            {
                obj.percentHeight = objPercHeight;
                obj.explicitHeight = NaN;
            }
        }

        UIComponentGlobals.layoutManager.usePhasedInstantiation = false;
        UIComponentGlobals.layoutManager.validateNow(); 
    }

    /**
     *  Sends the added objects to the printer to start printing.
     *  Call this method after you have used the <code>addObject()</code>
     *  method to add the print pages.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function send():void
    {
        printJob.send();
    }

    /**
     *  @private
     *  Prepare the target and its parents to print.
     *  If the content is inside a Container with scrollBars,
     *  it still gets printed all right. 
     */
    private function prepareToPrintObject(target:IUIComponent):Array
    {
        var arrPrintData:Array = [];

        var obj:DisplayObject 
            = (target is DisplayObject) ? DisplayObject(target) : null;
        var index:Number = 0;
        
        while (obj)
        {
            if (obj is UIComponent)
                arrPrintData[index++] 
                    = UIComponent(obj).prepareToPrint(UIComponent(target));
            else if (obj is DisplayObject && !(obj is Stage))
            {
                arrPrintData[index++] = DisplayObject(obj).mask;
                DisplayObject(obj).mask = null;
            }

            obj = (obj.parent is DisplayObject) ?
                  DisplayObject(obj.parent) :
                  null;
        }
        
        return arrPrintData;
    }

    /**
     *  @private
     *  Reverts the target and its parents back from Print state, 
     */
    private function finishPrintObject(target:IUIComponent,
                                       arrPrintData:Array):void
    {
        var obj:DisplayObject 
            = (target is DisplayObject) ? DisplayObject(target) : null;
        var index:Number = 0;
        while (obj)
        {
            if (obj is UIComponent)
                UIComponent(obj).finishPrint(arrPrintData[index++],
                                                UIComponent(target));
            else if (obj is DisplayObject && !(obj is Stage))
            {
                DisplayObject(obj).mask = arrPrintData[index++];
            }

            obj = (obj.parent is DisplayObject) ?
                  DisplayObject(obj.parent) :
                  null;
        }
    }
}

}
