////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.ags.Map;
import com.esri.ags.events.WebMapEvent;
import com.esri.ags.geometry.Extent;
import com.esri.ags.layers.CSVLayer;
import com.esri.ags.layers.FeatureLayer;
import com.esri.ags.layers.Layer;
import com.esri.ags.layers.WebTiledLayer;
import com.esri.ags.layers.supportClasses.TileInfo;
import com.esri.ags.portal.WebMapUtil;
import com.esri.ags.portal.supportClasses.PortalItem;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.ConfigLayer;
import com.esri.builder.model.ConfigMap;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalLayer;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.IconFetchEvent;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.supportClasses.PortalLayerIconFetcher;
import com.esri.builder.views.popups.NoticePopUp;

import flash.display.DisplayObject;
import flash.events.Event;

import mx.collections.ArrayCollection;
import mx.core.FlexGlobals;
import mx.events.CloseEvent;
import mx.logging.ILogger;
import mx.managers.PopUpManager;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.Responder;
import mx.utils.ObjectUtil;

public final class SwitchingMapsController
{
    private static const LOG:ILogger = LogUtil.createLogger(SwitchingMapsController);

    private var doNotShowLayerNotAddedPopUp:Boolean;
    private var isCreatingLayersFromWebMap:Boolean;
    private var webMapUtil:WebMapUtil;
    private var mapConfigXML:XML;
    private var noticePopUp:NoticePopUp;

    public function SwitchingMapsController()
    {
        AppEvent.addListener(AppEvent.SWITCH_MAPS, onSwitchMaps, false, 0, true);
        AppEvent.addListener(AppEvent.CONFIG_XML_SAVED, configXMLSaved, false, 0, true);

        webMapUtil = new WebMapUtil();
        webMapUtil.ignorePopUps = true;
        webMapUtil.addEventListener(WebMapEvent.CREATE_MAP_BY_ID_COMPLETE, createMapByIdCompleteHandler);
    }

    private function onSwitchMaps(event:AppEvent):void
    {
        if (event.data == "webmap")
        {
            if (Model.instance.config.configMap.basemaps.hasLayers
                || Model.instance.config.configMap.operationalLayers.hasLayers)
            {
                showNoticePopUp("webmap", ResourceManager.getInstance().getString("BuilderStrings", "noticePopUp.basemapsToWebmapContent"));
            }
            else
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "webmap");
            }
        }
        else if (event.data == "layers")
        {
            if (Model.instance.config.configMap && Model.instance.config.configMap.itemId)
            {
                showNoticePopUp("layers", ResourceManager.getInstance().getString("BuilderStrings", "noticePopUp.webMapToBasemapsContent"), true);
            }
            else
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "layers");
            }
        }
    }

    private function showNoticePopUp(newView:String, content:String, showBusyCursor:Boolean = false):void
    {
        noticePopUp = new NoticePopUp();
        noticePopUp.newView = newView;
        noticePopUp.content = content;
        noticePopUp.showBusyCursor = showBusyCursor;
        noticePopUp.addEventListener("isWaitingChanged", isWaitingChangedHandler);
        noticePopUp.addEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler, false, 0, true);
        PopUpManager.addPopUp(noticePopUp, FlexGlobals.topLevelApplication as DisplayObject, true);
        PopUpManager.centerPopUp(noticePopUp);
    }

    private function isWaitingChangedHandler(event:Event):void
    {
        noticePopUp.removeEventListener("isWaitingChanged", isWaitingChangedHandler);
        if (noticePopUp.isWaiting)
        {
            createBasemapsAndOperationalLayersFromWebMap();
        }
    }

    private function noticePopUp_closeHandler(event:CloseEvent):void
    {
        var noticePopUp:NoticePopUp = event.currentTarget as NoticePopUp;
        if (noticePopUp.changeView)
        {
            if (noticePopUp.newView == "webmap")
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "webmap");
            }
        }
        noticePopUp.removeEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler);
        PopUpManager.removePopUp(event.currentTarget as NoticePopUp);
        noticePopUp = null;
    }

    private function createBasemapsAndOperationalLayersFromWebMap():void
    {
        LOG.info("creating basemap & op layers from web map");

        var webMapId:String = Model.instance.config.configMap.itemId;

        webMapUtil.portalURL = PortalModel.getInstance().portalURL;
        webMapUtil.proxyURL = Model.instance.proxyURL;
        webMapUtil.createMapById(webMapId);
    }

    private function createMapByIdCompleteHandler(event:WebMapEvent):void
    {
        isCreatingLayersFromWebMap = true;

        var map:Map = event.map;

        var unsupportedLayers:Array = []

        var title:String = event.item.title;

        var initialextent:String;
        var extent:Extent = map.extent;
        if (extent)
        {
            var extentArr:Array = [ extent.xmin, extent.ymin, extent.xmax, extent.ymax ];
            initialextent = extentArr.join(" ");
        }

        XML.ignoreComments = false;
        XML.ignoreWhitespace = false;
        XML.prettyIndent = 4;

        mapConfigXML = <map initialextent={initialextent} top="40" wraparound180="true" addarcgisbasemaps={Model.instance.config.configMap.addArcGISBasemaps}/>
        if (!extent)
        {
            delete mapConfigXML.map.@initialextent;
        }

        var baseMapTitle:String;
        if (event.itemData.baseMap)
        {
            baseMapTitle = event.itemData.baseMap.title;
        }

        var baseLayers:XML = <basemaps/>;
        var opLayers:XML = <operationallayers/>;

        var supportedTileInfo:TileInfo;
        var layers:Array = (map.layers as ArrayCollection).toArray();
        for (var i:int = 0; i < layers.length; i++)
        {
            var layer:Layer = layers[i];

            if (layer is FeatureLayer && !(layer is CSVLayer))
            {
                var feaLyr:FeatureLayer = layer as FeatureLayer;
                if (feaLyr.featureCollection)
                {
                    unsupportedLayers.push(layer.name);
                    map.removeLayer(layer);
                    continue;
                }
                else
                {
                    for each (var layerObject:Object in event.itemData.operationalLayers)
                    {
                        if (layerObject.title == feaLyr.name
                            && layerObject.url == feaLyr.url)
                        {
                            feaLyr.isEditable = !layerObject.capabilities ||
                                layerObject.capabilities.indexOf("Editing") != -1;
                            break;
                        }
                    }
                }
            }
            if (layer is WebTiledLayer)
            {
                var webTiledLayer:WebTiledLayer = layer as WebTiledLayer;
                if (!supportedTileInfo)
                {
                    supportedTileInfo = new WebTiledLayer().tileInfo;
                }
                var hasSupportedTileInfo:Boolean = ObjectUtil.compare(supportedTileInfo, webTiledLayer.tileInfo) == 0;
                if (!hasSupportedTileInfo)
                {
                    unsupportedLayers.push(layer.name);
                    map.removeLayer(layer);
                    continue;
                }
            }

            var isOpLayer:Boolean = layer.id.indexOf("base") != 0;
            var lyrXML:XML;

            if (isOpLayer)
            {
                lyrXML = ConfigLayer.operationalLayerToXML(layer, layer.name);
                if (lyrXML)
                {
                    opLayers.appendChild(lyrXML);
                }
            }
            else
            {
                if (!baseMapTitle && i == 0)
                {
                    baseMapTitle = layer.name;
                }

                lyrXML = ConfigLayer.basemapLayerToXML(layer, baseMapTitle);
                if (lyrXML)
                {
                    if (opLayers.children().length() > 0)
                    {
                        lyrXML.@reference = true;
                    }

                    baseLayers.appendChild(lyrXML);
                }
            }
        }

        mapConfigXML.appendChild(baseLayers);

        if (opLayers.hasComplexContent())
        {
            mapConfigXML.appendChild(opLayers);
        }

        assignBasemapIcon(event.itemData, mapConfigXML, proceedToSaveConfig);

        function proceedToSaveConfig():void
        {
            if (unsupportedLayers.length)
            {
                noticePopUp.isWaiting = false;
                showLayerNotAddedPopUp(unsupportedLayers);
            }
            else
            {
                saveNewMapConfig();
            }
        }
    }

    private function assignBasemapIcon(itemData:Object, mapConfigXML:XML, afterFunction:Function):void
    {
        var basemapItemId:String = findBasemapLayerItemId(itemData);
        if (basemapItemId)
        {
            PortalModel.getInstance()
                .portal.getItem(basemapItemId, new Responder(getItem_resultHandler,
                                                             getItem_faultHandler));

            function getItem_resultHandler(item:PortalItem):void
            {
                LOG.debug("fetched basemap item: {0}", basemapItemId);
                var iconFetcher:PortalLayerIconFetcher = new PortalLayerIconFetcher();
                iconFetcher.addEventListener(IconFetchEvent.FETCH_COMPLETE, iconFetcher_fetchCompleteHandler);
                iconFetcher.fetchIconURL(new PortalLayer(item));

                function iconFetcher_fetchCompleteHandler(event:IconFetchEvent):void
                {
                    iconFetcher.removeEventListener(IconFetchEvent.FETCH_COMPLETE, iconFetcher_fetchCompleteHandler);

                    if (mapConfigXML.basemaps.children().length() > 0 && event.iconPath)
                    {
                        mapConfigXML.basemaps.child(0).@icon = event.iconPath;
                    }

                    afterFunction();
                }
            }

            function getItem_faultHandler(fault:Fault):void
            {
                LOG.debug("could not fetch basemap item: {0}", fault.toString());
                afterFunction();
            }
        }
        else
        {
            afterFunction();
        }
    }

    private function findBasemapLayerItemId(itemData:Object):String
    {
        var basemapLayerItemId:String;

        if (itemData.baseMap)
        {
            var layersObjects:Array = itemData.baseMap.baseMapLayers;
            for each (var layerObject:Object in layersObjects)
            {
                if (layerObject.itemId && !layerObject.isReference)
                {
                    basemapLayerItemId = layerObject.itemId;
                    break;
                }
            }
        }

        return basemapLayerItemId;
    }

    private function showLayerNotAddedPopUp(layerNames:Array):void
    {
        var layerNotAddedPopUp:NoticePopUp = new NoticePopUp;
        var content:String = ResourceManager.getInstance().getString("BuilderStrings", "mapsView.convertWebMapToLayers.layersNotAdded");
        content += "\n";
        for each (var layerName:String in layerNames)
        {
            content += "   " + layerName + "\n"
        }
        content += ResourceManager.getInstance().getString("BuilderStrings", "mapsView.convertWebMapToLayers.doYouWantToContinue");
        layerNotAddedPopUp.content = content;
        layerNotAddedPopUp.addEventListener(CloseEvent.CLOSE, layerNotAddedPopUp_closeHandler, false, 0, true);
        PopUpManager.addPopUp(layerNotAddedPopUp, FlexGlobals.topLevelApplication as DisplayObject, true);
        PopUpManager.centerPopUp(layerNotAddedPopUp);
    }

    private function layerNotAddedPopUp_closeHandler(event:CloseEvent):void
    {
        var layerNotAddedPopUp:NoticePopUp = event.currentTarget as NoticePopUp;
        if (layerNotAddedPopUp.changeView)
        {
            saveNewMapConfig();
        }
        layerNotAddedPopUp.removeEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler);
        PopUpManager.removePopUp(event.currentTarget as NoticePopUp);
        layerNotAddedPopUp = null;
    }

    private function saveNewMapConfig():void
    {
        if (noticePopUp)
        {
            noticePopUp.isWaiting = false;
            if (noticePopUp.changeView)
            {
                saveNewMapConfig2();
            }
        }
        else
        {
            saveNewMapConfig2();
        }
    }

    private function saveNewMapConfig2():void
    {
        LOG.debug("converting map XML {0}", mapConfigXML.toXMLString());

        Model.instance.config.configMap = ConfigMap.decodeXML(mapConfigXML);
        Model.instance.configBasemapsList.importLayers(Model.instance.config.configMap.basemaps.getAllLayers());
        Model.instance.configOpsLayerList.importLayers(Model.instance.config.configMap.operationalLayers.getAllLayers());
        AppEvent.dispatch(AppEvent.SAVE_CONFIG_XML);
    }

    private function configXMLSaved(event:AppEvent):void
    {
        if (isCreatingLayersFromWebMap)
        {
            Model.instance.webmap = null;
            Model.instance.webMapConfigBasemapList.removeAllLayers();
            Model.instance.webMapConfigOpLayerList.removeAllLayers();
            isCreatingLayersFromWebMap = false;
            AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "layers");
        }
    }
}

}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.ags.Map;
import com.esri.ags.events.WebMapEvent;
import com.esri.ags.geometry.Extent;
import com.esri.ags.layers.CSVLayer;
import com.esri.ags.layers.FeatureLayer;
import com.esri.ags.layers.Layer;
import com.esri.ags.layers.WebTiledLayer;
import com.esri.ags.layers.supportClasses.TileInfo;
import com.esri.ags.portal.WebMapUtil;
import com.esri.ags.portal.supportClasses.PortalItem;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.ConfigLayer;
import com.esri.builder.model.ConfigMap;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalLayer;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.IconFetchEvent;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.supportClasses.PortalLayerIconFetcher;
import com.esri.builder.views.popups.NoticePopUp;

import flash.display.DisplayObject;
import flash.events.Event;

import mx.collections.ArrayCollection;
import mx.core.FlexGlobals;
import mx.events.CloseEvent;
import mx.logging.ILogger;
import mx.managers.PopUpManager;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.Responder;
import mx.utils.ObjectUtil;

public final class SwitchingMapsController
{
    private static const LOG:ILogger = LogUtil.createLogger(SwitchingMapsController);

    private var isCreatingLayersFromWebMap:Boolean;
    private var webMapUtil:WebMapUtil;
    private var mapConfigXML:XML;
    private var noticePopUp:NoticePopUp;

    public function SwitchingMapsController()
    {
        AppEvent.addListener(AppEvent.SWITCH_MAPS, onSwitchMaps, false, 0, true);
        AppEvent.addListener(AppEvent.CONFIG_XML_SAVED, configXMLSaved, false, 0, true);

        webMapUtil = new WebMapUtil();
        webMapUtil.ignorePopUps = true;
        webMapUtil.addEventListener(WebMapEvent.CREATE_MAP_BY_ID_COMPLETE, createMapByIdCompleteHandler);
    }

    private function onSwitchMaps(event:AppEvent):void
    {
        if (event.data == "webmap")
        {
            if (Model.instance.config.configMap.basemaps.hasLayers
                || Model.instance.config.configMap.operationalLayers.hasLayers)
            {
                showNoticePopUp("webmap", ResourceManager.getInstance().getString("BuilderStrings", "noticePopUp.basemapsToWebmapContent"));
            }
            else
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "webmap");
            }
        }
        else if (event.data == "layers")
        {
            if (Model.instance.config.configMap && Model.instance.config.configMap.itemId)
            {
                showNoticePopUp("layers", ResourceManager.getInstance().getString("BuilderStrings", "noticePopUp.webMapToBasemapsContent"), true);
            }
            else
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "layers");
            }
        }
    }

    private function showNoticePopUp(newView:String, content:String, showBusyCursor:Boolean = false):void
    {
        noticePopUp = new NoticePopUp();
        noticePopUp.newView = newView;
        noticePopUp.content = content;
        noticePopUp.showBusyCursor = showBusyCursor;
        noticePopUp.addEventListener("isWaitingChanged", isWaitingChangedHandler);
        noticePopUp.addEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler, false, 0, true);
        PopUpManager.addPopUp(noticePopUp, FlexGlobals.topLevelApplication as DisplayObject, true);
        PopUpManager.centerPopUp(noticePopUp);
    }

    private function isWaitingChangedHandler(event:Event):void
    {
        noticePopUp.removeEventListener("isWaitingChanged", isWaitingChangedHandler);
        if (noticePopUp.isWaiting)
        {
            createBasemapsAndOperationalLayersFromWebMap();
        }
    }

    private function noticePopUp_closeHandler(event:CloseEvent):void
    {
        var noticePopUp:NoticePopUp = event.currentTarget as NoticePopUp;
        if (noticePopUp.changeView)
        {
            if (noticePopUp.newView == "webmap")
            {
                AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "webmap");
            }
        }
        noticePopUp.removeEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler);
        PopUpManager.removePopUp(event.currentTarget as NoticePopUp);
        noticePopUp = null;
    }

    private function createBasemapsAndOperationalLayersFromWebMap():void
    {
        LOG.info("creating basemap & op layers from web map");

        var webMapId:String = Model.instance.config.configMap.itemId;

        webMapUtil.portalURL = PortalModel.getInstance().portalURL;
        webMapUtil.proxyURL = Model.instance.proxyURL;
        webMapUtil.createMapById(webMapId);
    }

    private function createMapByIdCompleteHandler(event:WebMapEvent):void
    {
        isCreatingLayersFromWebMap = true;

        var map:Map = event.map;

        var unsupportedLayers:Array = [];

        var initialExtent:String;
        var extent:Extent = map.extent;
        if (extent)
        {
            var extentArr:Array = [ extent.xmin, extent.ymin, extent.xmax, extent.ymax ];
            initialExtent = extentArr.join(" ");
        }

        XML.ignoreComments = false;
        XML.ignoreWhitespace = false;
        XML.prettyIndent = 4;

        mapConfigXML = <map initialextent={initialExtent} top="40" wraparound180="true" addarcgisbasemaps={Model.instance.config.configMap.addArcGISBasemaps}/>;
        if (!extent)
        {
            delete mapConfigXML.map.@initialextent;
        }

        var baseMapTitle:String;
        if (event.itemData.baseMap)
        {
            baseMapTitle = event.itemData.baseMap.title;
        }

        var baseLayers:XML = <basemaps/>;
        var opLayers:XML = <operationallayers/>;

        var supportedTileInfo:TileInfo;
        var layers:Array = (map.layers as ArrayCollection).toArray();
        for (var i:int = 0; i < layers.length; i++)
        {
            var layer:Layer = layers[i];

            if (layer is FeatureLayer && !(layer is CSVLayer))
            {
                var feaLyr:FeatureLayer = layer as FeatureLayer;
                if (feaLyr.featureCollection)
                {
                    unsupportedLayers.push(layer.name);
                    map.removeLayer(layer);
                    continue;
                }
                else
                {
                    for each (var layerObject:Object in event.itemData.operationalLayers)
                    {
                        if (layerObject.title == feaLyr.name
                            && layerObject.url == feaLyr.url)
                        {
                            feaLyr.isEditable = !layerObject.capabilities ||
                                layerObject.capabilities.indexOf("Editing") != -1;
                            break;
                        }
                    }
                }
            }
            if (layer is WebTiledLayer)
            {
                var webTiledLayer:WebTiledLayer = layer as WebTiledLayer;
                if (!supportedTileInfo)
                {
                    supportedTileInfo = new WebTiledLayer().tileInfo;
                }
                var hasSupportedTileInfo:Boolean = ObjectUtil.compare(supportedTileInfo, webTiledLayer.tileInfo) == 0;
                if (!hasSupportedTileInfo)
                {
                    unsupportedLayers.push(layer.name);
                    map.removeLayer(layer);
                    continue;
                }
            }

            var isOpLayer:Boolean = layer.id.indexOf("base") != 0;
            var lyrXML:XML;

            if (isOpLayer)
            {
                lyrXML = ConfigLayer.operationalLayerToXML(layer, layer.name);
                if (lyrXML)
                {
                    opLayers.appendChild(lyrXML);
                }
            }
            else
            {
                if (!baseMapTitle && i == 0)
                {
                    baseMapTitle = layer.name;
                }

                lyrXML = ConfigLayer.basemapLayerToXML(layer, baseMapTitle);
                if (lyrXML)
                {
                    if (opLayers.children().length() > 0)
                    {
                        lyrXML.@reference = true;
                    }

                    baseLayers.appendChild(lyrXML);
                }
            }
        }

        mapConfigXML.appendChild(baseLayers);

        if (opLayers.hasComplexContent())
        {
            mapConfigXML.appendChild(opLayers);
        }

        assignBasemapIcon(event.itemData, mapConfigXML, proceedToSaveConfig);

        function proceedToSaveConfig():void
        {
            if (unsupportedLayers.length)
            {
                noticePopUp.isWaiting = false;
                showLayerNotAddedPopUp(unsupportedLayers);
            }
            else
            {
                saveNewMapConfig();
            }
        }
    }

    private function assignBasemapIcon(itemData:Object, mapConfigXML:XML, afterFunction:Function):void
    {
        var basemapItemId:String = findBasemapLayerItemId(itemData);
        if (basemapItemId)
        {
            PortalModel.getInstance()
                .portal.getItem(basemapItemId, new Responder(getItem_resultHandler,
                                                             getItem_faultHandler));

            function getItem_resultHandler(item:PortalItem):void
            {
                LOG.debug("fetched basemap item: {0}", basemapItemId);
                var iconFetcher:PortalLayerIconFetcher = new PortalLayerIconFetcher();
                iconFetcher.addEventListener(IconFetchEvent.FETCH_COMPLETE, iconFetcher_fetchCompleteHandler);
                iconFetcher.fetchIconURL(new PortalLayer(item));

                function iconFetcher_fetchCompleteHandler(event:IconFetchEvent):void
                {
                    iconFetcher.removeEventListener(IconFetchEvent.FETCH_COMPLETE, iconFetcher_fetchCompleteHandler);

                    if (mapConfigXML.basemaps.children().length() > 0 && event.iconPath)
                    {
                        mapConfigXML.basemaps.child(0).@icon = event.iconPath;
                    }

                    afterFunction();
                }
            }

            function getItem_faultHandler(fault:Fault):void
            {
                LOG.debug("could not fetch basemap item: {0}", fault.toString());
                afterFunction();
            }
        }
        else
        {
            afterFunction();
        }
    }

    private function findBasemapLayerItemId(itemData:Object):String
    {
        var basemapLayerItemId:String;

        if (itemData.baseMap)
        {
            var layersObjects:Array = itemData.baseMap.baseMapLayers;
            for each (var layerObject:Object in layersObjects)
            {
                if (layerObject.itemId && !layerObject.isReference)
                {
                    basemapLayerItemId = layerObject.itemId;
                    break;
                }
            }
        }

        return basemapLayerItemId;
    }

    private function showLayerNotAddedPopUp(layerNames:Array):void
    {
        var layerNotAddedPopUp:NoticePopUp = new NoticePopUp;
        var content:String = ResourceManager.getInstance().getString("BuilderStrings", "mapsView.convertWebMapToLayers.layersNotAdded");
        content += "\n";
        for each (var layerName:String in layerNames)
        {
            content += "   " + layerName + "\n"
        }
        content += ResourceManager.getInstance().getString("BuilderStrings", "mapsView.convertWebMapToLayers.doYouWantToContinue");
        layerNotAddedPopUp.content = content;
        layerNotAddedPopUp.addEventListener(CloseEvent.CLOSE, layerNotAddedPopUp_closeHandler, false, 0, true);
        PopUpManager.addPopUp(layerNotAddedPopUp, FlexGlobals.topLevelApplication as DisplayObject, true);
        PopUpManager.centerPopUp(layerNotAddedPopUp);
    }

    private function layerNotAddedPopUp_closeHandler(event:CloseEvent):void
    {
        var layerNotAddedPopUp:NoticePopUp = event.currentTarget as NoticePopUp;
        if (layerNotAddedPopUp.changeView)
        {
            saveNewMapConfig();
        }
        layerNotAddedPopUp.removeEventListener(CloseEvent.CLOSE, noticePopUp_closeHandler);
        PopUpManager.removePopUp(event.currentTarget as NoticePopUp);
        layerNotAddedPopUp = null;
    }

    private function saveNewMapConfig():void
    {
        if (noticePopUp)
        {
            noticePopUp.isWaiting = false;
            if (noticePopUp.changeView)
            {
                saveNewMapConfig2();
            }
        }
        else
        {
            saveNewMapConfig2();
        }
    }

    private function saveNewMapConfig2():void
    {
        LOG.debug("converting map XML {0}", mapConfigXML.toXMLString());

        Model.instance.config.configMap = ConfigMap.decodeXML(mapConfigXML);
        Model.instance.configBasemapsList.importLayers(Model.instance.config.configMap.basemaps.getAllLayers());
        Model.instance.configOpsLayerList.importLayers(Model.instance.config.configMap.operationalLayers.getAllLayers());
        AppEvent.dispatch(AppEvent.SAVE_CONFIG_XML);
    }

    private function configXMLSaved(event:AppEvent):void
    {
        if (isCreatingLayersFromWebMap)
        {
            Model.instance.webmap = null;
            Model.instance.webMapConfigBasemapList.removeAllLayers();
            Model.instance.webMapConfigOpLayerList.removeAllLayers();
            isCreatingLayersFromWebMap = false;
            AppEvent.dispatch(AppEvent.MAPS_SWITCHED, "layers");
        }
    }
}

}

Clean up.

Clean up.

* Add missing semicolons.

* Rename variable.

* Remove unused variables.


package com.kemsky
{
    /**
     * @private
     * Creates partially applied function
     * @param func source
     * @param args arguments to apply to func
     * @return partially applied function
     */
    public function curry(func:Function, ... args:Array):*
    {
        var arity:int = func.length;

        function currying(func:Function, arity:int, args:Array):*
        {
            return function curried(...left):*
            {
                if(left.length + args.length < arity)
                {
                    return currying(func, arity, args.concat(left));
                }
                return func.apply(this, args.concat(left));
            }
        }

        return currying(func, arity, args);
    }
}

package com.kemsky
{
    /**
     * Creates partially applied function
     * @param func source
     * @param args arguments to apply to func
     * @return partially applied function
     */
    public function curry(func:Function, ... args:Array):*
    {
        var arity:int = func.length;

        function currying(func:Function, arity:int, args:Array):*
        {
            return function curried(...left):*
            {
                if(left.length + args.length < arity)
                {
                    return currying(func, arity, args.concat(left));
                }
                return func.apply(this, args.concat(left));
            }
        }

        return currying(func, arity, args);
    }
}

rename prop to member

rename prop to member


////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.builder.components.LogFileTarget;
import com.esri.builder.components.ToolTip;
import com.esri.builder.controllers.supportClasses.Settings;
import com.esri.builder.controllers.supportClasses.WellKnownDirectories;
import com.esri.builder.controllers.supportClasses.WidgetTypeLoader;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.controllers.supportClasses.MachineDisplayName;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;
import com.esri.builder.views.popups.UnknownErrorPopUp;

import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.events.UncaughtErrorEvent;
import flash.filesystem.File;
import flash.net.SharedObject;
import flash.net.URLRequestDefaults;
import flash.system.Capabilities;

import mx.core.FlexGlobals;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.managers.ToolTipManager;
import mx.resources.ResourceManager;
import mx.rpc.AsyncResponder;

import spark.components.WindowedApplication;

public final class ApplicationCompleteController
{
    private static const LOG:ILogger = LogUtil.createLogger(ApplicationCompleteController);

    private var widgetTypeLoader:WidgetTypeLoader;

    private var startupSettings:Settings;

    public function ApplicationCompleteController()
    {
        AppEvent.addListener(AppEvent.APPLICATION_COMPLETE, onApplicationComplete, false, 0, true);
    }

    private function onApplicationComplete(event:AppEvent):void
    {
        applicationComplete(event.data as WindowedApplication);
        Model.instance.config.isDirty = false;
    }

    private function applicationComplete(app:WindowedApplication):void
    {
        if (Log.isInfo())
        {
            LOG.info('Starting up application.');
        }

        exportDependenciesToAppStorage();
        loadUserPreferences();
        loadWebMapSearchHistory();
        // Disable HTTP cache for HTML component
        URLRequestDefaults.cacheResponse = false;

        // Setup some XML global properties
        XML.ignoreComments = true;
        XML.ignoreWhitespace = true;
        XML.prettyIndent = 4;

        ToolTipManager.toolTipClass = com.esri.builder.components.ToolTip;

        // Can only have access to 'loaderInfo' when the app is complete.
        app.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler);

        widgetTypeLoader = new WidgetTypeLoader();
        widgetTypeLoader.addEventListener(Event.COMPLETE, widgetTypeLoader_completeHandler);
        widgetTypeLoader.loadWidgetTypes();
    }

    private function exportDependenciesToAppStorage():void
    {
        try
        {
            if (Log.isInfo())
            {
                LOG.info('Creating required folders.');
            }

            WellKnownDirectories.getInstance().customFlexViewer.createDirectory();
            WellKnownDirectories.getInstance().customModules.createDirectory();
        }
        catch (error:Error)
        {
            BuilderAlert.show(ResourceManager.getInstance().getString('BuilderStrings', 'startup.couldNotCreateCustomDirectories'), ResourceManager.getInstance().getString('BuilderStrings', 'error'));
        }
    }

    private function loadUserPreferences():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading user preferences...');
        }

        const so:SharedObject = SharedObject.getLocal(Model.USER_PREF);

        startupSettings = new Settings();

        startupSettings.webServerURL = so.data.baseURL;
        startupSettings.webServerFolder = so.data.baseLoc;
        startupSettings.locale = so.data.locale ? so.data.locale : getPreferredLocale();
        startupSettings.bingKey = so.data.bingKey;
        startupSettings.proxyURL = so.data.httpProxy;

        if (so.data.hasOwnProperty("userPortalURL"))
        {
            //updated property for v3.1+
            startupSettings.portalURL = so.data.userPortalURL;
            startupSettings.geocodeURL = so.data.geocodeURL;
            startupSettings.directionsURL = so.data.directionsURL;
            startupSettings.printTaskURL = so.data.printTaskURL;
        }
        else
        {
            startupSettings.portalURL = (so.data.portalURL) ? so.data.portalURL : PortalModel.DEFAULT_PORTAL_URL;
        }

        if (so.data.hasOwnProperty("userGeometryServiceURL"))
        {
            //updated property for v3.1+
            startupSettings.geometryServiceURL = so.data.userGeometryServiceURL;
        }
        else
        {
            startupSettings.geometryServiceURL = so.data.userGeometryServiceURL = (so.data.geometryServiceURL) ? so.data.geometryServiceURL : "http://tasks.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer";
        }

        if (so.data.hasOwnProperty("isTutorialModeEnabled"))
        {
            startupSettings.isTutorialModeEnabled = so.data.isTutorialModeEnabled;
        }
        else if (so.data.tutorialModeSettings)
        {
            startupSettings.isTutorialModeEnabled = so.data.tutorialModeSettings.isTutorialModeEnabled;
        }

        so.close();

        if (!startupSettings.webServerFolder)
        {
            if (Capabilities.os.toLowerCase().indexOf('win') > -1)
            {
                startupSettings.webServerFolder = 'C:\\inetpub\\wwwroot\\flexviewers';
            }
            else if (Capabilities.os.toLowerCase().indexOf('mac') > -1)
            {
                startupSettings.webServerFolder = File.userDirectory.nativePath + '/Sites/flexviewers';
            }
            else if (Capabilities.os.toLowerCase().indexOf('linux') > -1)
            {
                startupSettings.webServerFolder = '/var/httpd/wwwroot/flexviewers'; // TODO - check this !!
            }
        }

        if (startupSettings.webServerURL)
        {
            applySettings();
        }
        else
        {
            tryGetHostName();
        }
    }

    private function applySettings():void
    {
        Model.instance.importSettings(startupSettings);
        startupSettings = null;
    }

    private function tryGetHostName():void
    {
        MachineDisplayName.getInstance().resolve(new AsyncResponder(result, fault));

        function result(hostname:String, token:Object):void
        {
            initBaseURL(hostname.toLowerCase());
            applySettings();
        }

        function fault(fault:Object, token:Object):void
        {
            initBaseURL("localhost");
            applySettings();
        }
    }

    private function initBaseURL(hostname:String):void
    {
        if (Capabilities.os.toLowerCase().indexOf('win') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/flexviewers';
        }
        else if (Capabilities.os.toLowerCase().indexOf('mac') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/~' + File.userDirectory.name + '/flexviewers';
        }
        else if (Capabilities.os.toLowerCase().indexOf('linux') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/flexviewers'; // TODO - check this !!
        }
    }

    private function getPreferredLocale():String
    {
        const preferredLocales:Array = ResourceManager.getInstance().getPreferredLocaleChain();
        return (preferredLocales.length > 0) ? preferredLocales[0] : 'en_US';
    }

    private function loadWebMapSearchHistory():void
    {
        if (Log.isInfo())
        {
            LOG.info("Loading web map search history");
        }

        const so:SharedObject = SharedObject.getLocal(Model.USER_PREF);
        var historyString:String = so.data.webMapSearchHistory;
        if (historyString != null)
        {
            Model.instance.webMapSearchHistory = historyString.split("##");
        }
        so.close();
    }

    private function uncaughtErrorHandler(event:UncaughtErrorEvent):void
    {
        event.preventDefault();

        if (Log.isFatal())
        {
            if (event.error is Error) //could be ErrorEvent
            {
                LOG.fatal('Uncaught error: {0}', event.error.getStackTrace());
            }
        }

        const logFile:File = File.applicationStorageDirectory.resolvePath(LogFileTarget.LOG_FILE_NAME);
        const errorContent:String = ResourceManager.getInstance().getString('BuilderStrings', 'unknownErrorMessage',
                                                                            [ event.error.toString(), logFile.nativePath ]);
        const errorPopUp:UnknownErrorPopUp = new UnknownErrorPopUp();
        errorPopUp.errorContent = errorContent;
        errorPopUp.open(FlexGlobals.topLevelApplication as DisplayObjectContainer, true);
    }

    private function setStatusAndAlert(text:String):void
    {
        Model.instance.status = text;
        BuilderAlert.show(text, ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }

    protected function widgetTypeLoader_completeHandler(event:Event):void
    {
        (event.currentTarget as WidgetTypeLoader).removeEventListener(Event.COMPLETE, widgetTypeLoader_completeHandler);
        validateSettings();
    }

    private function validateSettings():void
    {
        AppEvent.dispatch(AppEvent.SAVE_SETTINGS, Model.instance.exportSettings());
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed 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.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.builder.components.LogFileTarget;
import com.esri.builder.components.ToolTip;
import com.esri.builder.controllers.supportClasses.Settings;
import com.esri.builder.controllers.supportClasses.WellKnownDirectories;
import com.esri.builder.controllers.supportClasses.WidgetTypeLoader;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.controllers.supportClasses.MachineDisplayName;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.views.BuilderAlert;
import com.esri.builder.views.popups.UnknownErrorPopUp;

import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.events.UncaughtErrorEvent;
import flash.filesystem.File;
import flash.net.SharedObject;
import flash.net.URLRequestDefaults;
import flash.system.Capabilities;

import mx.core.FlexGlobals;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.managers.ToolTipManager;
import mx.resources.ResourceManager;
import mx.rpc.AsyncResponder;

import spark.components.WindowedApplication;

public final class ApplicationCompleteController
{
    private static const LOG:ILogger = LogUtil.createLogger(ApplicationCompleteController);

    private var widgetTypeLoader:WidgetTypeLoader;

    private var startupSettings:Settings;

    public function ApplicationCompleteController()
    {
        AppEvent.addListener(AppEvent.APPLICATION_COMPLETE, onApplicationComplete, false, 0, true);
    }

    private function onApplicationComplete(event:AppEvent):void
    {
        applicationComplete(event.data as WindowedApplication);
        Model.instance.config.isDirty = false;
    }

    private function applicationComplete(app:WindowedApplication):void
    {
        if (Log.isInfo())
        {
            LOG.info('Starting up application.');
        }

        exportDependenciesToAppStorage();
        loadUserPreferences();
        loadWebMapSearchHistory();
        // Disable HTTP cache for HTML component
        URLRequestDefaults.cacheResponse = false;

        // Setup some XML global properties
        XML.ignoreComments = true;
        XML.ignoreWhitespace = true;
        XML.prettyIndent = 4;

        ToolTipManager.toolTipClass = com.esri.builder.components.ToolTip;

        // Can only have access to 'loaderInfo' when the app is complete.
        app.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, uncaughtErrorHandler);

        widgetTypeLoader = new WidgetTypeLoader();
        widgetTypeLoader.addEventListener(Event.COMPLETE, widgetTypeLoader_completeHandler);
        widgetTypeLoader.loadWidgetTypes();
    }

    private function exportDependenciesToAppStorage():void
    {
        try
        {
            if (Log.isInfo())
            {
                LOG.info('Creating required folders.');
            }

            WellKnownDirectories.getInstance().customFlexViewer.createDirectory();
            WellKnownDirectories.getInstance().customModules.createDirectory();
        }
        catch (error:Error)
        {
            BuilderAlert.show(ResourceManager.getInstance().getString('BuilderStrings', 'startup.couldNotCreateCustomDirectories'), ResourceManager.getInstance().getString('BuilderStrings', 'error'));
        }
    }

    private function loadUserPreferences():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading user preferences...');
        }

        migratePreviousSettingsFolder();
        const so:SharedObject = SharedObject.getLocal(Model.USER_PREF);

        startupSettings = new Settings();

        startupSettings.webServerURL = so.data.baseURL;
        startupSettings.webServerFolder = so.data.baseLoc;
        startupSettings.locale = so.data.locale ? so.data.locale : getPreferredLocale();
        startupSettings.bingKey = so.data.bingKey;
        startupSettings.proxyURL = so.data.httpProxy;

        if (so.data.hasOwnProperty("userPortalURL"))
        {
            //updated property for v3.1+
            startupSettings.portalURL = so.data.userPortalURL;
            startupSettings.geocodeURL = so.data.geocodeURL;
            startupSettings.directionsURL = so.data.directionsURL;
            startupSettings.printTaskURL = so.data.printTaskURL;
        }
        else
        {
            startupSettings.portalURL = (so.data.portalURL) ? so.data.portalURL : PortalModel.DEFAULT_PORTAL_URL;
        }

        if (so.data.hasOwnProperty("userGeometryServiceURL"))
        {
            //updated property for v3.1+
            startupSettings.geometryServiceURL = so.data.userGeometryServiceURL;
        }
        else
        {
            startupSettings.geometryServiceURL = so.data.userGeometryServiceURL = (so.data.geometryServiceURL) ? so.data.geometryServiceURL : "http://tasks.arcgisonline.com/ArcGIS/rest/services/Geometry/GeometryServer";
        }

        if (so.data.hasOwnProperty("isTutorialModeEnabled"))
        {
            startupSettings.isTutorialModeEnabled = so.data.isTutorialModeEnabled;
        }
        else if (so.data.tutorialModeSettings)
        {
            startupSettings.isTutorialModeEnabled = so.data.tutorialModeSettings.isTutorialModeEnabled;
        }

        so.close();

        if (!startupSettings.webServerFolder)
        {
            if (Capabilities.os.toLowerCase().indexOf('win') > -1)
            {
                startupSettings.webServerFolder = 'C:\\inetpub\\wwwroot\\flexviewers';
            }
            else if (Capabilities.os.toLowerCase().indexOf('mac') > -1)
            {
                startupSettings.webServerFolder = File.userDirectory.nativePath + '/Sites/flexviewers';
            }
            else if (Capabilities.os.toLowerCase().indexOf('linux') > -1)
            {
                startupSettings.webServerFolder = '/var/httpd/wwwroot/flexviewers'; // TODO - check this !!
            }
        }

        if (startupSettings.webServerURL)
        {
            applySettings();
        }
        else
        {
            tryGetHostName();
        }
    }

    private function migratePreviousSettingsFolder():void
    {
        var previousBuilderSettingsFolder:File =
            File.applicationStorageDirectory.resolvePath("#SharedObjects/AppBuilder.swf/");

        if (!previousBuilderSettingsFolder.exists)
        {
            return;
        }

        var filesOrFolders:Array = previousBuilderSettingsFolder.getDirectoryListing();

        if (filesOrFolders.length == 0)
        {
            return;
        }

        if (Log.isInfo())
        {
            LOG.info('Transferring previous settings folder');
        }

        var currentBuilderSettingsFolder:File =
            File.applicationStorageDirectory.resolvePath("#SharedObjects/Builder.swf/");

        try
        {
            for each (var fileOrFolder:File in filesOrFolders)
            {
                if (Log.isInfo())
                {
                    LOG.info('Transferring {0}', fileOrFolder.name);
                }
                fileOrFolder.moveTo(currentBuilderSettingsFolder.resolvePath(fileOrFolder.name),
                                    true);
            }
        }
        catch (error:Error)
        {
            if (Log.isInfo())
            {
                LOG.info('Could not transfer previous settings folder: {0} - {1}',
                         error.errorID, error.toString());
            }
        }
    }

    private function applySettings():void
    {
        Model.instance.importSettings(startupSettings);
        startupSettings = null;
    }

    private function tryGetHostName():void
    {
        MachineDisplayName.getInstance().resolve(new AsyncResponder(result, fault));

        function result(hostname:String, token:Object):void
        {
            initBaseURL(hostname.toLowerCase());
            applySettings();
        }

        function fault(fault:Object, token:Object):void
        {
            initBaseURL("localhost");
            applySettings();
        }
    }

    private function initBaseURL(hostname:String):void
    {
        if (Capabilities.os.toLowerCase().indexOf('win') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/flexviewers';
        }
        else if (Capabilities.os.toLowerCase().indexOf('mac') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/~' + File.userDirectory.name + '/flexviewers';
        }
        else if (Capabilities.os.toLowerCase().indexOf('linux') > -1)
        {
            startupSettings.webServerURL = 'http://' + hostname + '/flexviewers'; // TODO - check this !!
        }
    }

    private function getPreferredLocale():String
    {
        const preferredLocales:Array = ResourceManager.getInstance().getPreferredLocaleChain();
        return (preferredLocales.length > 0) ? preferredLocales[0] : 'en_US';
    }

    private function loadWebMapSearchHistory():void
    {
        if (Log.isInfo())
        {
            LOG.info("Loading web map search history");
        }

        const so:SharedObject = SharedObject.getLocal(Model.USER_PREF);
        var historyString:String = so.data.webMapSearchHistory;
        if (historyString != null)
        {
            Model.instance.webMapSearchHistory = historyString.split("##");
        }
        so.close();
    }

    private function uncaughtErrorHandler(event:UncaughtErrorEvent):void
    {
        event.preventDefault();

        if (Log.isFatal())
        {
            if (event.error is Error) //could be ErrorEvent
            {
                LOG.fatal('Uncaught error: {0}', event.error.getStackTrace());
            }
        }

        const logFile:File = File.applicationStorageDirectory.resolvePath(LogFileTarget.LOG_FILE_NAME);
        const errorContent:String = ResourceManager.getInstance().getString('BuilderStrings', 'unknownErrorMessage',
                                                                            [ event.error.toString(), logFile.nativePath ]);
        const errorPopUp:UnknownErrorPopUp = new UnknownErrorPopUp();
        errorPopUp.errorContent = errorContent;
        errorPopUp.open(FlexGlobals.topLevelApplication as DisplayObjectContainer, true);
    }

    private function setStatusAndAlert(text:String):void
    {
        Model.instance.status = text;
        BuilderAlert.show(text, ResourceManager.getInstance().getString('BuilderStrings', 'error'));
    }

    protected function widgetTypeLoader_completeHandler(event:Event):void
    {
        (event.currentTarget as WidgetTypeLoader).removeEventListener(Event.COMPLETE, widgetTypeLoader_completeHandler);
        validateSettings();
    }

    private function validateSettings():void
    {
        AppEvent.dispatch(AppEvent.SAVE_SETTINGS, Model.instance.exportSettings());
    }
}
}

Migrate any existing settings (<= 3.3).

Migrate any existing settings (<= 3.3).


package aerys.minko.scene.node.texture
{
	import aerys.minko.render.effect.basic.BasicStyle;
	import aerys.minko.render.resource.Texture3DResource;
	import aerys.minko.scene.action.texture.TextureAction;
	import aerys.minko.scene.node.AbstractScene;

	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.geom.Matrix;

	/**
	 * The BitmapTexture class enables the use of BitmapData objects as
	 * textures.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class BitmapTexture extends AbstractScene implements ITexture
	{
		private static const TMP_MATRIX : Matrix = new Matrix();

		private var _version	: uint				= 0;
		private var _data		: BitmapData		= null;
		private var _mipmapping	: Boolean			= false;
		private var _styleProp	: int				= -1;
		private var _resource	: Texture3DResource	= new Texture3DResource();

		public function get version()		: uint				{ return _version;		}
		public function get styleProperty() : int				{ return _styleProp;	}
		public function get resource()		: Texture3DResource	{ return _resource;		}
		public function get mipmapping()	: Boolean			{ return _mipmapping;	}
		protected function get data() 			: BitmapData		{ return _data;			}

		protected function get bitmapData()	: BitmapData
		{
			return _data;
		}

		public function set styleProperty(value : int) : void
		{
			_styleProp = value;
		}

		public function updateFromBitmapData(value 		: BitmapData,
											 smooth		: Boolean	= true,
											 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint = value.width;
			var bitmapHeight	: uint = value.height;
			var w				: int;
			var h				: int;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (!_data || _data.width != w || _data.height != h)
			{
				if (_data)
					_data.dispose();
				_data = new BitmapData(w, h, value.transparent, 0);
			}

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_data.draw(value, TMP_MATRIX, null, null, null, smooth);
			}
			else
			{
				_data.draw(value, null, null, null, null, smooth);
			}

			++_version;

			_resource.setContentFromBitmapData(_data, _mipmapping);
		}

		public function BitmapTexture(bitmapData 	: BitmapData 	= null,
									  mipmapping	: Boolean		= true,
									  styleProp		: int			= -1)
		{
			_mipmapping	= mipmapping;
			_styleProp	= styleProp != -1 ? styleProp : BasicStyle.DIFFUSE;

			if (bitmapData)
				updateFromBitmapData(bitmapData);

			actions[0] = TextureAction.textureAction;
		}

		public static function fromDisplayObject(source : DisplayObject,
												 size 	: int 		= 0,
												 smooth : Boolean 	= false) : BitmapTexture
		{
			var bmp : BitmapData = new BitmapData(size || source.width,
												  size || source.height,
												  true,
												  0);

			if (size)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(source.width / size, source.height / size);
			}

			bmp.draw(source, TMP_MATRIX, null, null, null, smooth);

			return new BitmapTexture(bmp);
		}
	}
}

package aerys.minko.scene.node.texture
{
	import aerys.minko.ns.minko;
	import aerys.minko.render.effect.basic.BasicStyle;
	import aerys.minko.render.resource.Texture3DResource;
	import aerys.minko.scene.action.texture.TextureAction;
	import aerys.minko.scene.node.AbstractScene;

	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.geom.Matrix;

	/**
	 * The BitmapTexture class enables the use of BitmapData objects as
	 * textures.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */

	use namespace minko;

	public class BitmapTexture extends AbstractScene implements ITexture
	{
		private static const TMP_MATRIX : Matrix = new Matrix();

		private var _version	: uint				= 0;
		private var _data		: BitmapData		= null;
		private var _mipmapping	: Boolean			= false;
		private var _styleProp	: int				= -1;
		private var _resource	: Texture3DResource	= new Texture3DResource();

		public function get version()		: uint				{ return _version;		}
		public function get styleProperty() : int				{ return _styleProp;	}
		public function get resource()		: Texture3DResource	{ return _resource;		}
		public function get mipmapping()	: Boolean			{ return _mipmapping;	}
		minko function get data() 			: BitmapData		{ return _data;			}

		protected function get bitmapData()	: BitmapData
		{
			return _data;
		}

		public function set styleProperty(value : int) : void
		{
			_styleProp = value;
		}

		public function updateFromBitmapData(value 		: BitmapData,
											 smooth		: Boolean	= true,
											 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint = value.width;
			var bitmapHeight	: uint = value.height;
			var w				: int;
			var h				: int;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (!_data || _data.width != w || _data.height != h)
			{
				if (_data)
					_data.dispose();
				_data = new BitmapData(w, h, value.transparent, 0);
			}

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_data.draw(value, TMP_MATRIX, null, null, null, smooth);
			}
			else
			{
				_data.draw(value, null, null, null, null, smooth);
			}

			++_version;

			_resource.setContentFromBitmapData(_data, _mipmapping);
		}

		public function BitmapTexture(bitmapData 	: BitmapData 	= null,
									  mipmapping	: Boolean		= true,
									  styleProp		: int			= -1)
		{
			_mipmapping	= mipmapping;
			_styleProp	= styleProp != -1 ? styleProp : BasicStyle.DIFFUSE;

			if (bitmapData)
				updateFromBitmapData(bitmapData);

			actions[0] = TextureAction.textureAction;
		}

		public static function fromDisplayObject(source : DisplayObject,
												 size 	: int 		= 0,
												 smooth : Boolean 	= false) : BitmapTexture
		{
			var bmp : BitmapData = new BitmapData(size || source.width,
												  size || source.height,
												  true,
												  0);

			if (size)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(source.width / size, source.height / size);
			}

			bmp.draw(source, TMP_MATRIX, null, null, null, smooth);

			return new BitmapTexture(bmp);
		}
	}
}

Move data getter from "protected" to "minko" namespace


package ss5player {

	/**
	 * "SS5Player for AS3" is a SpriteStudio animation playback library.
	 * This class includes constants for version tracking.
	 * @author PROMOTAL Inc.
	 */
	public class SS5Player {

		/**
		 * Current version of SS5Player.
		 */
		public static const VERSION:String = "0.2.0";

		public function SS5Player() {
			super();
		}
	}
}

package ss5player {

	/**
	 * "SS5Player for AS3" is a SpriteStudio animation playback library.
	 * This class includes constants for version tracking.
	 * @author PROMOTAL Inc.
	 */
	public class SS5Player {

		/**
		 * Current version of SS5Player.
		 */
		public static const VERSION:String = "0.2.1";

		public function SS5Player() {
			super();
		}
	}
}

bump version

bump version


package {

import flash.display.Sprite;
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.external.ExternalInterface;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.getQualifiedClassName;
import flash.system.Security;

/**
 * Audio main.
 */
public class SoundMain extends Sprite {

    private var ns:String;
    private var debug:Boolean;

    private var sounds:Object;

    private var silence:ByteArray;

    private static const BYTES_PER_SAMPLE:int = 4 * 2; // Stereo 32-bit float
    private static const MAX_SAMPLE_SIZE:int = 4096 * BYTES_PER_SAMPLE;
    private static const MIN_SAMPLE_SIZE:int = 2048 * BYTES_PER_SAMPLE;

    /**
     * Constructor
     */
    public function SoundMain() {
        if (!ExternalInterface.available) {
            throw new Error("ExternalInterface not available");
        }

        ns = loaderInfo.parameters["namespace"] || getQualifiedClassName(this);
        debug = false;

        Security.allowDomain("*");
        Security.allowInsecureDomain("*");

        sounds = {};

        silence = new ByteArray();
        for (var n:int = 0; n < 2048; n++) {
            silence.writeFloat(0);
            silence.writeFloat(0);
        }

        /**
         * Javascript can call these
         */
        ExternalInterface.addCallback("__create", create);
        ExternalInterface.addCallback("__buffer", buffer);
        ExternalInterface.addCallback("__load", load);
        ExternalInterface.addCallback("__play", play);
        ExternalInterface.addCallback("__setDebug", setDebug);
        ExternalInterface.addCallback("__stop", stop);
        ExternalInterface.addCallback("__destroy", destroy);

        invoke("__onFlashInitialized");
    }

    private function setDebug(val:Boolean):void {
        invoke("__onLog", "setDebug");
        ExternalInterface.marshallExceptions = val;
        debug = val;
    }

    /**
     * Creates a new Audio
     */
    private function create(id:int):void {
        sounds[id] = {
            instance: new Sound(),
            buffer: new ByteArray(),
            channel: null
        };
        sounds[id].instance.addEventListener(SampleDataEvent.SAMPLE_DATA,
                                             handleSampleData(id));
        invoke("__onLog", "Max: " + String(MAX_SAMPLE_SIZE));
        invoke("__onLog", "Min: " + String(MIN_SAMPLE_SIZE));
    }

    /**
     * Sample data handler
     */
    private function handleSampleData(id:int):Function {
        var cursor:Number = 0;
        return function(event:SampleDataEvent):void {
            var buffer:ByteArray = sounds[id].buffer;
            var available:int = buffer.length - cursor;
            if (available < MIN_SAMPLE_SIZE) {
                if (available > 0) {
                    event.data.writeBytes(buffer, cursor, available);
                }
                var pad:int = MIN_SAMPLE_SIZE - available;
                var reps:int = Math.ceil(pad / silence.length);
                for (var i:int = 0; i < reps; i++) {
                    event.data.writeBytes(silence, 0, silence.length);
                }
                buffer.clear();
                cursor = 0;
            } else {
                var len:int = Math.min(MAX_SAMPLE_SIZE, available);
                event.data.writeBytes(buffer, cursor, len);
                cursor += len;
            }
        };
    }

    /**
     * Buffers some audio
     */
    private function buffer(id:int, bytes:String):void {
        for (var i:Number = 0; i < bytes.length; i++) {
            var sample:Number = (bytes.charCodeAt(i) - 32767) / 32767;
            sounds[id].buffer.writeFloat(sample);
            sounds[id].buffer.writeFloat(sample);
        }
    }

    /**
     * Loads audio from URL
     */
    private function load(id:int, uri:String):void {
        if (uri.substr(0, 4) == "data") {
            invoke("__onLog", "Data URI not supported");
            return;
        }
        sounds[id].instance.load(new URLRequest(uri));
    }

    /**
     * Plays audio
     */
    private function play(id:int, startTime:Number = 0, loops:int = 1):void {
        if (sounds[id].channel) {
            invoke("__onLog", "Already playing this sound");
            return;
        }
        sounds[id].channel = sounds[id].instance.play(startTime, loops);
    }

    /**
     * Stops playing audio
     */
    private function stop(id:int):void {
        if (sounds[id].channel) {
            sounds[id].channel.stop();
            sounds[id].channel = null;
        }
    }

    /**
     * Cleans up resources
     */
    private function destroy(id:int):void {
        stop(id);
        sounds[id].buffer.clear();
        sounds[id].buffer = null;
        sounds[id].channel = null;
        sounds[id].instance = null;
        delete sounds[id];
    }

    /**
     * Conveniently invoke a function in Javascript.
     *
     * @param method String The method to call.
     */
    private function invoke(method:String, ...params):void {
        params = params || [];
        ExternalInterface.call.apply(ExternalInterface,
                [ns + "." + method].concat(params));
    }

}

}

package {

import flash.display.Sprite;
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.external.ExternalInterface;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.getQualifiedClassName;
import flash.system.Security;

/**
 * Audio main.
 */
public class SoundMain extends Sprite {

    private var ns:String;
    private var debug:Boolean;

    private var sounds:Object;

    private var silence:ByteArray;

    private static const BYTES_PER_SAMPLE:int = 4 * 2; // Stereo 32-bit float
    private static const MAX_SAMPLE_SIZE:int = 4096 * BYTES_PER_SAMPLE;
    private static const MIN_SAMPLE_SIZE:int = 2048 * BYTES_PER_SAMPLE;

    /**
     * Constructor
     */
    public function SoundMain() {
        if (!ExternalInterface.available) {
            throw new Error("ExternalInterface not available");
        }

        ns = loaderInfo.parameters["namespace"] || getQualifiedClassName(this);
        debug = false;

        Security.allowDomain("*");
        Security.allowInsecureDomain("*");

        sounds = {};

        silence = new ByteArray();
        for (var n:int = 0; n < 2048; n++) {
            silence.writeFloat(0);
            silence.writeFloat(0);
        }

        /**
         * Javascript can call these
         */
        ExternalInterface.addCallback("__create", create);
        ExternalInterface.addCallback("__buffer", buffer);
        ExternalInterface.addCallback("__load", load);
        ExternalInterface.addCallback("__play", play);
        ExternalInterface.addCallback("__setDebug", setDebug);
        ExternalInterface.addCallback("__stop", stop);
        ExternalInterface.addCallback("__destroy", destroy);

        invoke("__onFlashInitialized");
    }

    private function setDebug(val:Boolean):void {
        invoke("__onLog", "setDebug");
        ExternalInterface.marshallExceptions = val;
        debug = val;
    }

    /**
     * Creates a new Audio
     */
    private function create(id:int):void {
        sounds[id] = {
            instance: new Sound(),
            buffer: [],
            channel: null
        };
        sounds[id].instance.addEventListener(SampleDataEvent.SAMPLE_DATA,
                                             handleSampleData(id));
        invoke("__onLog", "Max: " + String(MAX_SAMPLE_SIZE));
        invoke("__onLog", "Min: " + String(MIN_SAMPLE_SIZE));
    }

    /**
     * Sample data handler
     */
    private function handleSampleData(id:int):Function {
        var cursor:Number = 0;
        return function(event:SampleDataEvent):void {
            var bytesWritten:int = 0;
            while (bytesWritten < MAX_SAMPLE_SIZE
                && sounds[id].buffer.length > 0
            ) {
                var frame:ByteArray = sounds[id].buffer.shift();
                event.data.writeBytes(frame, 0, frame.length);
                bytesWritten += frame.length;
                frame.clear();
            }
            var pad:int = MIN_SAMPLE_SIZE - bytesWritten;
            var reps:int = Math.ceil(pad / silence.length);
            for (var i:int = 0; i < reps; i++) {
                event.data.writeBytes(silence, 0, silence.length);
            }
        };
    }

    /**
     * Buffers some audio
     */
    private function buffer(id:int, bytes:String):void {
        var frame:ByteArray = new ByteArray();
        for (var i:Number = 0; i < bytes.length; i++) {
            var sample:Number = (bytes.charCodeAt(i) - 32767) / 32767;
            frame.writeFloat(sample);
            frame.writeFloat(sample);
            if (frame.length >= MAX_SAMPLE_SIZE) {
                sounds[id].buffer.push(frame);
                frame = new ByteArray();
            }
        }
        sounds[id].buffer.push(frame);
    }

    /**
     * Loads audio from URL
     */
    private function load(id:int, uri:String):void {
        if (uri.substr(0, 4) == "data") {
            invoke("__onLog", "Data URI not supported");
            return;
        }
        sounds[id].instance.load(new URLRequest(uri));
    }

    /**
     * Plays audio
     */
    private function play(id:int, startTime:Number = 0, loops:int = 1):void {
        if (sounds[id].channel) {
            invoke("__onLog", "Already playing this sound");
            return;
        }
        sounds[id].channel = sounds[id].instance.play(startTime, loops);
    }

    /**
     * Stops playing audio
     */
    private function stop(id:int):void {
        if (sounds[id].channel) {
            sounds[id].channel.stop();
            sounds[id].channel = null;
        }
    }

    /**
     * Cleans up resources
     */
    private function destroy(id:int):void {
        stop(id);
        sounds[id].buffer.clear();
        sounds[id].buffer = null;
        sounds[id].channel = null;
        sounds[id].instance = null;
        delete sounds[id];
    }

    /**
     * Conveniently invoke a function in Javascript.
     *
     * @param method String The method to call.
     */
    private function invoke(method:String, ...params):void {
        params = params || [];
        ExternalInterface.call.apply(ExternalInterface,
                [ns + "." + method].concat(params));
    }

}

}

Fix to allow old buffers to be freed once used


package aerys.minko.scene.node.group
{
	import aerys.minko.ns.minko;
	import aerys.minko.scene.action.group.AnimationGroupAction;
	import aerys.minko.type.animation.TimeLabel;
	import aerys.minko.type.animation.timeline.ITimeline;

	import flash.utils.getTimer;

	use namespace minko;

	public class AnimationGroup extends Group
	{
		minko var _timelines					: Vector.<ITimeline>;

		minko var _isPlaying					: Boolean;

		minko var _loopBeginTime				: uint;
		minko var _loopEndTime					: uint;

		minko var _lastTimerTick				: uint;
		minko var _currentTime					: uint;

		private var _totalTime					: uint;

		private var _labels						: Vector.<TimeLabel>;

		public function get isPlaying() : Boolean
		{
			return _isPlaying;
		}

		/**
		 * @return Specifies the number of the frame in which the playhead is located in the timeline of the AnimationGroup instance.
		 */
		public function get currentTime() : int
		{
			return _currentTime;
		}

		/**
		 * @return The current label in which the playhead is located in the timeline of the AnimationGroup instance.
		 */
		public function get currentLabel() : String
		{
			if (_labels.length == 0)
			{
				return null;
			}
			else
			{
				var labelCount 		: uint = _labels.length;
				var bottomLabelId	: uint = 0;
				var upperLabelId	: uint = labelCount;
				while (upperLabelId - bottomLabelId > 1)
				{
					var labelId : uint = (bottomLabelId + upperLabelId) >> 1;
					if (_labels[labelId].time > _currentTime)
						upperLabelId = labelId;
					else
						bottomLabelId = labelId;
				}

				return _labels[bottomLabelId].name;
			}
		}

		/**
		 * @return Returns an array of TimeLabel objects from the current scene.
		 * A copy is made: modifying this vector will have no effect
		 */
		public function get labels() : Vector.<TimeLabel>
		{
			return _labels.slice();
		}

		public function set labels(v : Vector.<TimeLabel>) : void
		{
			_labels = v.slice();
			_labels = _labels.sort(TimeLabel.sort);
		}

		/**
		 * @return The total number of frames in the AnimationGroup instance.
		 */
		public function get totalTime() : int
		{
			return _totalTime;
		}

		public function AnimationGroup(timelines	: Vector.<ITimeline>,
									   labels		: Vector.<TimeLabel> = null,
									   ...children)
		{
			super(children);

			_timelines	= timelines;
			_labels		= labels || new Vector.<TimeLabel>();

			var timelineCount : uint = _timelines.length;
			for (var timelineId : uint = 0; timelineId < timelineCount; ++timelineId)
				if (_totalTime < _timelines[timelineId].duration)
					_totalTime = _timelines[timelineId].duration;

			setPlaybackWindow(0, _totalTime);
			gotoAndPlay(0);

			actions.unshift(AnimationGroupAction.animationGroupAction);
		}

		public function gotoAndPlay(time : Object) : void
		{
			var timeValue : uint = getTime(time);

			if (timeValue < _loopBeginTime || timeValue > _loopEndTime)
				throw new Error('Time value is outside of playback window. To reset playback window, call resetPlaybackWindow.');

			_currentTime = timeValue;
			play();
		}

		public function gotoAndStop(time : Object) : void
		{
			var timeValue : uint = getTime(time);

			if (timeValue < _loopBeginTime || timeValue > _loopEndTime)
				throw new Error('Time value is outside of playback window. To reset playback window, call resetPlaybackWindow.');

			_currentTime = timeValue;
			stop();
		}

		public function play() : void
		{
			_isPlaying		= true;
			_lastTimerTick	= getTimer();
		}

		public function stop() : void
		{
			_isPlaying = false;
		}

		public function setPlaybackWindow(beginTime	: Object = null,
										  endTime	: Object = null) : void
		{
			_loopBeginTime	= beginTime != null ? getTime(beginTime) : 0;
			_loopEndTime	= endTime != null ? getTime(endTime) : _totalTime;

			if (_currentTime < _loopBeginTime || _currentTime > _loopEndTime)
				_currentTime = _loopBeginTime;
		}

		public function resetPlaybackWindow() : void
		{
			setPlaybackWindow();
		}

		private function getTime(time : Object) : uint
		{
			var timeValue : uint;

			if (time is uint || time is int || time is Number)
			{
				timeValue = uint(time);
			}
			else if (time is String)
			{
				var labelCount : uint = _labels.length;
				for (var labelId : uint = 0; labelId < labelCount; ++labelId)
					if (_labels[labelId].name == time)
						timeValue = _labels[labelId].time;
			}
			else
			{
				throw new Error('Invalid argument type. Must be uint or String');
			}

			return timeValue;
		}
	}
}

package aerys.minko.scene.node.group
{
	import aerys.minko.ns.minko;
	import aerys.minko.scene.action.group.AnimationGroupAction;
	import aerys.minko.type.animation.TimeLabel;
	import aerys.minko.type.animation.timeline.ITimeline;

	import flash.utils.getTimer;

	use namespace minko;

	public class AnimationGroup extends Group
	{
		minko var _timelines					: Vector.<ITimeline>;

		minko var _isPlaying					: Boolean;

		minko var _loopBeginTime				: uint;
		minko var _loopEndTime					: uint;

		minko var _lastTimerTick				: uint;
		minko var _currentTime					: uint;

		private var _totalTime					: uint;

		private var _labels						: Vector.<TimeLabel>;

		public function get isPlaying() : Boolean
		{
			return _isPlaying;
		}

		/**
		 * @return Specifies the number of the frame in which the playhead is located in the timeline of the AnimationGroup instance.
		 */
		public function get currentTime() : int
		{
			return _currentTime;
		}

		/**
		 * @return The current label in which the playhead is located in the timeline of the AnimationGroup instance.
		 */
		public function get currentLabel() : String
		{
			if (_labels.length == 0)
			{
				return null;
			}
			else
			{
				var labelCount 		: uint = _labels.length;
				var bottomLabelId	: uint = 0;
				var upperLabelId	: uint = labelCount;
				while (upperLabelId - bottomLabelId > 1)
				{
					var labelId : uint = (bottomLabelId + upperLabelId) >> 1;
					if (_labels[labelId].time > _currentTime)
						upperLabelId = labelId;
					else
						bottomLabelId = labelId;
				}

				return _labels[bottomLabelId].name;
			}
		}

		/**
		 * @return Returns an array of TimeLabel objects from the current scene.
		 * A copy is made: modifying this vector will have no effect
		 */
		public function get labels() : Vector.<TimeLabel>
		{
			return _labels.slice();
		}

		public function set labels(v : Vector.<TimeLabel>) : void
		{
			_labels = v.slice();
			_labels = _labels.sort(TimeLabel.sort);
		}

		/**
		 * @return The total number of frames in the AnimationGroup instance.
		 */
		public function get totalTime() : int
		{
			return _totalTime;
		}

		public function AnimationGroup(timelines	: Vector.<ITimeline>,
									   labels		: Vector.<TimeLabel> = null,
									   ...children)
		{
			super(children);

			_timelines	= timelines;
			_labels		= labels || new Vector.<TimeLabel>();

			var timelineCount : uint = _timelines.length;
			for (var timelineId : uint = 0; timelineId < timelineCount; ++timelineId)
				if (_totalTime < _timelines[timelineId].duration)
					_totalTime = _timelines[timelineId].duration;

			setPlaybackWindow(0, _totalTime);
			gotoAndPlay(0);

			actions.unshift(AnimationGroupAction.animationGroupAction);
		}

		public function gotoAndPlay(time : Object) : void
		{
			var timeValue : uint = getTime(time);

			if (timeValue < _loopBeginTime || timeValue > _loopEndTime)
				throw new Error('Time value is outside of playback window. To reset playback window, call resetPlaybackWindow.');

			_currentTime = timeValue;
			play();
		}

		public function gotoAndStop(time : Object) : void
		{
			var timeValue : uint = getTime(time);

			if (timeValue < _loopBeginTime || timeValue > _loopEndTime)
				throw new Error('Time value is outside of playback window. To reset playback window, call resetPlaybackWindow.');

			_currentTime = timeValue;
			stop();
		}

		public function play() : void
		{
			_isPlaying		= true;
			_lastTimerTick	= getTimer();
		}

		public function stop() : void
		{
			_isPlaying = false;
		}

		public function setPlaybackWindow(beginTime	: Object = null,
										  endTime	: Object = null) : void
		{
			_loopBeginTime	= beginTime != null ? getTime(beginTime) : 0;
			_loopEndTime	= endTime != null ? getTime(endTime) : _totalTime;

			if (_currentTime < _loopBeginTime || _currentTime > _loopEndTime)
				_currentTime = _loopBeginTime;
		}

		public function resetPlaybackWindow() : void
		{
			setPlaybackWindow();
		}

		private function getTime(time : Object) : uint
		{
			var timeValue : uint;

			if (time is uint || time is int || time is Number)
			{
				timeValue = uint(time);
			}
			else if (time is String)
			{
				var labelCount : uint = _labels.length;
				for (var labelId : uint = 0; labelId < labelCount; ++labelId)
					if (_labels[labelId].name == time)
						timeValue = _labels[labelId].time;
			}
			else
			{
				throw new Error('Invalid argument type. Must be uint or String');
			}

			return timeValue;
		}

		public function clone() : AnimationGroup
		{
			var clone		: AnimationGroup = new AnimationGroup(_timelines, _labels);
			var numChildren	: uint = this.numChildren;

			for (var childId : uint = 0; childId < numChildren; ++childId)
				clone.addChild(getChildAt(childId));

			return clone;
		}
	}
}

Add a clone method to AnimationGroup

Add a clone method to AnimationGroup