package component.list
{
    import caurina.transitions.Tweener;
    
    import component.layout.FluidLayout;
    import component.layout.ILayout;
    import component.layout.VerticalLayout;
    
    import flash.display.DisplayObject;
    import flash.external.ExternalInterface;
    import flash.geom.Rectangle;
    import flash.utils.Dictionary;
    
    import mx.collections.ArrayCollection;
    import mx.core.Container;
    import mx.core.IDataRenderer;
    import mx.core.IFactory;
    import mx.core.UIComponent;
    import mx.events.CollectionEvent;
    import mx.events.CollectionEventKind;
    import mx.events.FlexEvent;

    public class CustomLayoutList extends Container
    {
        public static const TWEEN_DURATION_SECONDS : Number = 1;
        
        public var itemRenderer : IFactory;
        
        private var isLayoutChanged : Boolean = false;
        private var _layout : ILayout;
        [Bindable]
        public function set layout(value : ILayout) : void
        {
            if (value != null && value != _layout)
            {
                _layout = value;
                isLayoutChanged = true;
                
                invalidateProperties();
            }
        }
        public function get layout() : ILayout
        {
            return _layout;
        }
        
        private var isDataProviderChanged : Boolean = false;
        private var _dataProvider : ArrayCollection;
        [Bindable]
        public function set dataProvider(value : ArrayCollection) : void
        {
            if (value != null && value != _dataProvider)
            {
                if (_dataProvider != null)
                {
                    _dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, handleCollectionChange);
                }
                
                _dataProvider = value;
                _dataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, handleCollectionChange);
                
                isDataProviderChanged = true;
                invalidateProperties();
            }
        }
        public function get dataProvider() : ArrayCollection
        {
            return _dataProvider;
        }
        
        public var listItems : ArrayCollection = new ArrayCollection();
        public var dataToItemMap : Dictionary = new Dictionary();
        public var removedItems : ArrayCollection = new ArrayCollection();
        
        private var creationCompleted : Boolean = false;
        
        public function CustomLayoutList() 
        {
            super();
            
            addEventListener(FlexEvent.CREATION_COMPLETE, handleCreationComplete);
            
            initializeExternalInterface();
        }
        
        public function collapseAll() : void
        {
            for each (var item : Object in listItems)
            {
                if (item is IExpandableListItem)
                {
                    IExpandableListItem(item).collapse();
                }
            }
        }
        
        public function expandAll() : void
        {
            for each (var item : Object in listItems)
            {
                if (item is IExpandableListItem)
                {
                    IExpandableListItem(item).expand();
                }
            }
        }
        
        override protected function commitProperties() : void
        {
            super.commitProperties();
            
            if (isDataProviderChanged)
            {
                updateList();
                isDataProviderChanged = false;
            }
            
            if (isLayoutChanged && creationCompleted)
            {
                var dimensions : Array = layout.updateLayout(this, listItems, removedItems);
                tweenToNewDimensions(dimensions);
                isLayoutChanged = false;
            }
        }
        
        private function tweenToNewDimensions(dimensions : Array) : void
        {
            for (var i : int = 0; i < dimensions.length; i++)
            {
                var dimension : Rectangle = Rectangle(dimensions[i]);
                if (dimension == null)
                    continue;
                
                var item : DisplayObject = DisplayObject(listItems.getItemAt(i));
                Tweener.addTween(item, 
                    {x : dimension.x, y : dimension.y, width : dimension.width, height : dimension.height, 
                    time : TWEEN_DURATION_SECONDS,
                    onUpdate : function() : void { invalidateSize(); invalidateDisplayList(); }});
            }
        } 
        
        private function updateList() : void 
        {
            resetList();
            initializeListItems();
            isLayoutChanged = true;
            
            invalidateProperties();
        }
        
        private function resetList() : void 
        {
            removeAllChildren();
            dataToItemMap = new Dictionary();
            listItems.removeAll();
            removedItems.removeAll();
        }
        
        private function initializeListItems() : void 
        {
            for each (var itemData : Object in _dataProvider)
            {
                addItem(itemData);
            }
        }
        
        private function addItems(itemsData : Array) : void 
        {
            for each (var itemData : Object in itemsData)
            {
                addItem(itemData);
            }
        }
        
        private function removeItems(itemsData : Array) : void 
        {
            for each (var itemData : Object in itemsData)
            {
                removeItem(itemData);
            }
        }
        
        private function addItem(itemData : Object) : void
        {
            var item : IDataRenderer = itemRenderer.newInstance() as IDataRenderer;
            item.data = itemData;
            
            listItems.addItem(item);
            dataToItemMap[itemData] = item;
            
            UIComponent(item).addEventListener(ResizableItemChangeEvent.DIMENSIONS_CHANGE, handleItemDimensionsChange);
            
            addChild(UIComponent(item));
        }
        
        private function removeItem(itemData : Object) : void 
        {
            var item : UIComponent = UIComponent(dataToItemMap[itemData]);
            listItems.removeItemAt(listItems.getItemIndex(item));
            removeChild(item);
            delete dataToItemMap[itemData];
            
            item.removeEventListener(ResizableItemChangeEvent.DIMENSIONS_CHANGE, handleItemDimensionsChange);
            
            isLayoutChanged = true;
            invalidateProperties();
        }
        
        private function tempRemoveItems(itemsData : Array) : void 
        {
            for each (var itemData : Object in itemsData)
            {
                tempRemoveItem(itemData);
            }
        }
        
        private function tempRemoveItem(itemData : Object) : void 
        {
            var item : UIComponent = UIComponent(dataToItemMap[itemData]);
            removeChild(item);
            
            item.x = item.y = 0;
            
            removedItems.addItem(item);
            
            isLayoutChanged = true;
            invalidateProperties();
        }
        
        private function restoreItems(items : Array) : void 
        {
            for each (var item : Object in items)
            {
                restoreItem(item);
            }
        }
        
        private function restoreItem(item : Object) : void
        {
            removedItems.removeItemAt(removedItems.getItemIndex(item));
            addChild(UIComponent(item));
            
            isLayoutChanged = true;
            invalidateProperties();
        }
        
        private function refreshList() : void 
        {
            var itemsDataToRemove : Array = [];
            var item : IDataRenderer;
            var itemData : Object;
            for  each (item in listItems)
            {
                itemData = item.data;
                if (_dataProvider.getItemIndex(itemData) == -1 && DisplayObject(item).parent != null)
                {
                    itemsDataToRemove.push(itemData);
                }
            }
            tempRemoveItems(itemsDataToRemove);
            
            var itemsToRestore : Array = [];
            for each (item in removedItems)
            {
                itemData = item.data;
                if (_dataProvider.getItemIndex(itemData) != -1)
                {
                    itemsToRestore.push(item);
                }
            }
            restoreItems(itemsToRestore);
        }
        
        private function handleCollectionChange(e : CollectionEvent) : void 
        {
            switch (e.kind)
            {
                case CollectionEventKind.ADD:
                    addItems(e.items);
                break;
                
                case CollectionEventKind.REMOVE:
                    removeItems(e.items);
                break;
                
                case CollectionEventKind.REFRESH:
                    refreshList();
                break;
            }
        }
        
        private function handleItemDimensionsChange(e : ResizableItemChangeEvent) : void
        {
            isLayoutChanged = true;
            invalidateProperties();
        }
        
        private function handleCreationComplete(e : FlexEvent) : void 
        {
            creationCompleted = true;
            
            isLayoutChanged = true;
            invalidateProperties();
        }
        
        // Methods exposed only for automation testing purposes:
        private function initializeExternalInterface() : void 
        {
            ExternalInterface.addCallback("getCustomLayoutListData", getCustomLayoutListData);
            ExternalInterface.addCallback("getCustomLayoutListLayout", getCustomLayoutListLayout);
        }
        
        public function getCustomLayoutListData(delimiter : String, ...rest) : String
        {
            var result : String = "";
            for (var i : int = 0; i < _dataProvider.length; i++)
            {
                var itemData : Object = _dataProvider.getItemAt(i);
                result += itemData.toString();
                if (i < _dataProvider.length - 1)
                {
                    result += delimiter;
                }
            }
            return result;
        }
        
        public function getCustomLayoutListLayout(...rest) : String
        {
            var result : String;
            if (layout is FluidLayout)
            {
                return "FluidLayout";
            }
            else if (layout is VerticalLayout)
            {
                return "VerticalLayout";
            }
            return result;
        }
        
    }
}