package com.lafabrick.converter
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    
    import mx.rpc.xml.Schema;
    import mx.rpc.xml.SchemaManager;
    import mx.rpc.xml.SchemaTypeRegistry;
    import mx.rpc.xml.XMLDecoder;
    import mx.rpc.xml.XMLEncoder;
    
    /**
     * Distached when the import of XML to Object is complete
     */
    [Event("importSuccess", type="com.lafabrick.converter.XSDEvent")]
    
    /**
     * Distached when the export of Object to XML is complete
     */
    [Event("exportSuccess", type="com.lafabrick.converter.XSDEvent")]
    
    /**
     * Distached when there's a problem on loading XSD Schema, or during export/import operations
     */
    [Event("conversionError", type="com.lafabrick.converter.XSDEvent")]
    
    public class XSDConverter extends EventDispatcher
    {
        /**
         * @private
         */
        private var _schema:Schema;
        /**
         * @private
         */
        private var _xmlEncoder:XMLEncoder;
        /**
         * @private
         */
        private var _xmlDecoder:XMLDecoder;
        /**
         * @private
         */
        private var _schemaManager:SchemaManager;
        /**
         * @private
         */
        private var _schemaTypeRegistry : SchemaTypeRegistry;
        
        /**
         * Constructor
         */
        public function XSDConverter()
        {
            _schema = new Schema();
            _xmlDecoder = new XMLDecoder();
            _xmlEncoder = new XMLEncoder();
            _schemaManager = new SchemaManager();
            
            _xmlDecoder.schemaManager = _schemaManager;
            _xmlEncoder.schemaManager = _schemaManager;
            
            _schemaTypeRegistry = SchemaTypeRegistry.getInstance();
        }
        
        /**
         * @private
         * registration of all schema Types and related Classes
         */
        private function registerSchemaType( schemaTypes : Vector.<XSDType> = null ) : void
        {
            for each( var obj : XSDType in schemaTypes ) {
                _schemaTypeRegistry.registerClass(new QName(_schema.targetNamespace.uri, obj.typeName), obj.classMapping);
            }
        }
        
        /**
         * @private
         * unregistration of all schema Types
         */
        private function unregisterSchemaType( schemaTypes : Vector.<XSDType> = null ) : void
        {
            for each( var obj : XSDType in schemaTypes ) {
                _schemaTypeRegistry.unregisterClass(new QName(_schema.targetNamespace.uri, obj.typeName) );
            }
        }
        
        /**
         * import a target XML to Object, following an XSD Schema file
         * @param xml source xml. Can be a String, XML, XMLList
         * @param xsdUrl the url of the target XSD Schema
         * @param schemaTypes a Vector of XSDType Object. Define all schema type of the XSD, with the mapped class
         * @see com.lafabrick.converter.XSDType
         * @see com.lafabrick.converter.XSDEvent
         */
        public function importFromXml( xml : *, xsdUrl : String, schemaTypes : Vector.<XSDType> = null ) : void
        {
            _schemaManager.reset();
            
            var urlLoader : URLLoader = new URLLoader();
            urlLoader.addEventListener(IOErrorEvent.IO_ERROR, loadIoError );
            urlLoader.addEventListener(
                    Event.COMPLETE, 
                    function( event : Event ) : void {
                        
                        _schema.xml = XML( urlLoader.data );
                        _schemaManager.addSchema( _schema );
                        var evt : XSDEvent;
                        
                        try {
                            registerSchemaType( schemaTypes );
                            var obj : * = _xmlDecoder.decode( xml, new QName( _schema.targetNamespace.uri, _schema.xml.*::element.@name) );
                            
                            unregisterSchemaType( schemaTypes );
                            
                            evt = new XSDEvent( XSDEvent.IMPORT_SUCCESS );
                            evt.data = obj;
                            evt.message = "import complete";
                            dispatchEvent( evt );
                        }
                        catch( e:Error ) {
                            evt = new XSDEvent( XSDEvent.CONVERSION_ERROR );
                            evt.message = "[catch Error] problem decoding target XML ( Error message : "+e.message+" )";
                            dispatchEvent( evt );
                        }
                    } );
            
            urlLoader.load( new URLRequest( xsdUrl ) );    
        }    
        
        /**
         * import a target XML to Object, following an XSD Schema file
         * @param target target Object
         * @param xsdUrl the url of the target XSD Schema
         * @param schemaTypes a Vector of XSDType Object. Define all schema type of the XSD, with the mapped class
         * @see com.lafabrick.converter.XSDType
         * @see com.lafabrick.converter.XSDEvent
         */
        public function exportToXml( target : Object, xsdUrl : String, schemaTypes : Vector.<XSDType> = null ) : void
        {
            _schemaManager.reset();
            
            var urlLoader : URLLoader = new URLLoader();
            urlLoader.addEventListener(IOErrorEvent.IO_ERROR, loadIoError );
            urlLoader.addEventListener(
                Event.COMPLETE, 
                function( event : Event ) : void {
                    
                    _schema.xml = XML( urlLoader.data );
                    _schemaManager.addSchema( _schema );
                    var evt : XSDEvent;
                    
                    try {    
                        registerSchemaType( schemaTypes );
                        var obj : * = _xmlEncoder.encode( target, new QName( _schema.targetNamespace.uri, _schema.xml.*::element.@name) );
                        
                        unregisterSchemaType( schemaTypes );
                        
                        evt = new XSDEvent( XSDEvent.EXPORT_SUCCESS );
                        evt.data = obj;
                        evt.message = "export complete";
                        dispatchEvent( evt );
                    }
                    catch( e:Error ) {
                        evt = new XSDEvent( XSDEvent.CONVERSION_ERROR );
                        evt.message = "[catch Error] problem encoding target object ( Error message : "+e.message+" )";
                        dispatchEvent( evt );
                    }
                    
                } );
            
            urlLoader.load( new URLRequest( xsdUrl ) );
        }
        
        /**
         * @private
         * Distached an XSDEvent.CONVERSION_ERROR event, when there's a problem on loading XSD Schema
         */
        private function loadIoError( event : IOErrorEvent ) : void
        {
            var evt : XSDEvent = new XSDEvent( XSDEvent.CONVERSION_ERROR );
            evt.message = "[IOError] problem loading the XSD schema ( IOError message : "+event.text+" )";
            dispatchEvent( evt );
        }
    }
}