using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using FairyGUI.Utils;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace FairyGUI
{
    /// <summary>
    /// A UI Package contains a description file and some texture, sound assets.
    /// </summary>
    public class UIPackage
    {
        /// <summary>
        /// Unload UIAssetBundle by FairyGUI system.
        /// if use AssetBundlesManager set value to false
        /// then unload UIAssetBundle by AssetBundlesManager
        /// </summary>
        public static bool unloadBundleByFGUI = true;

        /// <summary>
        /// The event is triggered when all reference to this package item dropped.
        /// </summary>
        public static event Action<PackageItem> onReleaseResource;

        /// <summary>
        /// Package id. It is generated by the Editor.
        /// </summary>
        public string id { get; private set; }

        /// <summary>
        /// Package name.
        /// </summary>
        public string name { get; private set; }

        /// <summary>
        /// Use this callback to provide resources to the package.
        /// </summary>
        /// <param name="name">Resource name without extension.</param>
        /// <param name="extension">Resource extension. e.g. '.png' '.wav'</param>
        /// <param name="type">Resource type. e.g. 'Texture' 'AudioClip'</param>
        /// <param name="destroyMethod">How to destroy this resource.</param>
        /// <returns></returns>
        public delegate object LoadResource(string name, string extension, System.Type type,
            out DestroyMethod destroyMethod);

        /// <summary>
        /// A async load resource callback. After loaded, you should call item.owner.SetItemAsset.
        /// </summary>
        /// <param name="name">Resource name without extension.</param>
        /// <param name="extension">Resource extension. e.g. '.png' '.wav'</param>
        /// <param name="type">Resource type. e.g. 'Texture' 'AudioClip'</param>
        /// <param name="item">Resource item object.</param>
        /// <returns></returns>
        public delegate void LoadResourceAsync(string name, string extension, System.Type type, PackageItem item);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        public delegate void CreateObjectCallback(GObject result);

        List<PackageItem> _items;
        Dictionary<string, PackageItem> _itemsById;
        Dictionary<string, PackageItem> _itemsByName;
        Dictionary<string, string>[] _dependencies;
        string _assetPath;
        string[] _branches;
        internal int _branchIndex;
        AssetBundle _resBundle;
        string _customId;
        bool _fromBundle;

        LoadResource _loadFunc;
        LoadResourceAsync _loadAsyncFunc;

        class AtlasSprite
        {
            public PackageItem atlas;
            public Rect rect = new Rect();
            public Vector2 offset = new Vector2();
            public Vector2 originalSize = new Vector2();
            public bool rotated;
        }

        Dictionary<string, AtlasSprite> _sprites;

        static Dictionary<string, UIPackage> _packageInstById = new Dictionary<string, UIPackage>();
        static Dictionary<string, UIPackage> _packageInstByName = new Dictionary<string, UIPackage>();
        static List<UIPackage> _packageList = new List<UIPackage>();
        static string _branch;
        static Dictionary<string, string> _vars = new Dictionary<string, string>();

        internal static int _constructing;

        public const string URL_PREFIX = "ui://";

#if UNITY_EDITOR
        static LoadResource _loadFromAssetsPath =
            (string name, string extension, System.Type type, out DestroyMethod destroyMethod) =>
            {
                destroyMethod = DestroyMethod.Unload;
                return AssetDatabase.LoadAssetAtPath(name + extension, type);
            };
#endif

        static LoadResource _loadFromResourcesPath =
            (string name, string extension, System.Type type, out DestroyMethod destroyMethod) =>
            {
                destroyMethod = DestroyMethod.Unload;
                // LoadKit.Instance.LoadAsset();
                return Resources.Load(name, type);
            };

        public UIPackage()
        {
            _items = new List<PackageItem>();
            _itemsById = new Dictionary<string, PackageItem>();
            _itemsByName = new Dictionary<string, PackageItem>();
            _sprites = new Dictionary<string, AtlasSprite>();

            _branchIndex = -1;
        }

        /// <summary>
        /// 
        /// </summary>
        public static string branch
        {
            get { return _branch; }
            set
            {
                _branch = value;
                bool empty = string.IsNullOrEmpty(_branch);
                var iter = _packageInstById.GetEnumerator();
                while (iter.MoveNext())
                {
                    UIPackage pkg = iter.Current.Value;
                    if (empty)
                        pkg._branchIndex = -1;
                    else if (pkg._branches != null)
                        pkg._branchIndex = Array.IndexOf(pkg._branches, value);
                }

                iter.Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public static string GetVar(string key)
        {
            string value;
            if (_vars.TryGetValue(key, out value))
                return value;
            else
                return null;
        }

        /// <summary>
        /// 
        /// </summary>
        public static void SetVar(string key, string value)
        {
            if (value == null)
                _vars.Remove(key);
            else
                _vars[key] = value;
        }

        /// <summary>
        /// Return a UIPackage with a certain id.
        /// </summary>
        /// <param name="id">ID of the package.</param>
        /// <returns>UIPackage</returns>
        public static UIPackage GetById(string id)
        {
            UIPackage pkg;
            if (_packageInstById.TryGetValue(id, out pkg))
                return pkg;
            else
                return null;
        }

        /// <summary>
        /// Return a UIPackage with a certain name.
        /// </summary>
        /// <param name="name">Name of the package.</param>
        /// <returns>UIPackage</returns>
        public static UIPackage GetByName(string name)
        {
            UIPackage pkg;
            if (_packageInstByName.TryGetValue(name, out pkg))
                return pkg;
            else
                return null;
        }

        /// <summary>
        /// Add a UI package from assetbundle.
        /// </summary>
        /// <param name="bundle">A assetbundle.</param>
        /// <returns>UIPackage</returns>
        public static UIPackage AddPackage(AssetBundle bundle)
        {
            return AddPackage(bundle, bundle, null);
        }

        /// <summary>
        /// Add a UI package from two assetbundles. desc and res can be same.
        /// </summary>
        /// <param name="desc">A assetbunble contains description file.</param>
        /// <param name="res">A assetbundle contains resources.</param>
        /// <returns>UIPackage</returns>
        public static UIPackage AddPackage(AssetBundle desc, AssetBundle res)
        {
            return AddPackage(desc, res, null);
        }

        /// <summary>
        /// Add a UI package from two assetbundles with a optional main asset name.
        /// </summary>
        /// <param name="desc">A assetbunble contains description file.</param>
        /// <param name="res">A assetbundle contains resources.</param>
        /// <param name="mainAssetName">Main asset name. e.g. Basics_fui</param>
        /// <returns>UIPackage</returns>
        public static UIPackage AddPackage(AssetBundle desc, AssetBundle res, string mainAssetName)
        {
            byte[] source = null;
            if (!string.IsNullOrEmpty(mainAssetName))
            {
                TextAsset ta = desc.LoadAsset<TextAsset>(mainAssetName);
                if (ta != null)
                    source = ta.bytes;
            }
            else
            {
                string[] names = desc.GetAllAssetNames();
                string searchPattern = "_fui";
                foreach (string n in names)
                {
                    if (n.IndexOf(searchPattern) != -1)
                    {
                        TextAsset ta = desc.LoadAsset<TextAsset>(n);
                        if (ta != null)
                        {
                            source = ta.bytes;
                            mainAssetName = Path.GetFileNameWithoutExtension(n);
                            break;
                        }
                    }
                }
            }

            if (source == null)
                throw new Exception("FairyGUI: no package found in this bundle.");

            if (unloadBundleByFGUI && desc != res)
                desc.Unload(true);

            ByteBuffer buffer = new ByteBuffer(source);

            UIPackage pkg = new UIPackage();
            pkg._resBundle = res;
            pkg._fromBundle = true;
            int pos = mainAssetName.IndexOf("_fui");
            if (pos != -1)
                mainAssetName = mainAssetName.Substring(0, pos);
            if (!pkg.LoadPackage(buffer, mainAssetName))
                return null;

            _packageInstById[pkg.id] = pkg;
            _packageInstByName[pkg.name] = pkg;
            _packageList.Add(pkg);

            return pkg;
        }

        /*public static UIPackage AddPackage(byte[] source, string assetName, LoadResource loadFunc)
        {
            ByteBuffer buffer = new ByteBuffer(source);

            UIPackage pkg = new UIPackage();
            if (!pkg.LoadPackage(buffer, assetName))
                return null;

            _packageInstById[pkg.id] = pkg;
            _packageInstByName[pkg.name] = pkg;
            _packageList.Add(pkg);
            pkg._loadFunc = loadFunc;

            return pkg;
        }*/

        /// <summary>
        /// Add a UI package from a path relative to Unity Resources path.
        /// </summary>
        /// <param name="descFilePath">Path relative to Unity Resources path.</param>
        /// <returns>UIPackage</returns>
        public static UIPackage AddPackage(string descFilePath)
        {
            if (descFilePath.StartsWith("Assets/"))
            {
#if UNITY_EDITOR
                return AddPackage(descFilePath, _loadFromAssetsPath);
#else
                Debug.LogWarning("FairyGUI: failed to load package in '" + descFilePath + "'");
                return null;
#endif
            }
            else
                return AddPackage(descFilePath, _loadFromResourcesPath);
        }

        /// <summary>
        /// 使用自定义的加载方式载入一个包。
        /// </summary>
        /// <param name="assetPath">包资源路径。</param>
        /// <param name="loadFunc">载入函数</param>
        /// <returns></returns>
        public static UIPackage AddPackage(string assetPath, LoadResource loadFunc)
        {
            if (_packageInstById.ContainsKey(assetPath))
                return _packageInstById[assetPath];

            DestroyMethod dm;
            TextAsset asset = (TextAsset)loadFunc(assetPath + "_fui", ".bytes", typeof(TextAsset), out dm);
            if (asset == null)
            {
                if (Application.isPlaying)
                {
                    // throw new Exception("FairyGUI: Cannot load ui package in '" + assetPath + "'");
                }
                else
                    Debug.LogWarning("FairyGUI: Cannot load ui package in '" + assetPath + "'");
            }

            ByteBuffer buffer = new ByteBuffer(asset.bytes);

            UIPackage pkg = new UIPackage();
            pkg._loadFunc = loadFunc;
            pkg._assetPath = assetPath;
            if (!pkg.LoadPackage(buffer, assetPath))
                return null;

            _packageInstById[pkg.id] = pkg;
            _packageInstByName[pkg.name] = pkg;
            _packageInstById[assetPath] = pkg;
            _packageList.Add(pkg);
            return pkg;
        }

        /// <summary>
        /// Load Package by custom load method.
        /// </summary>
        /// <param name="descData">Description file data</param>
        /// <param name="assetNamePrefix">Prefix of the resource file name. The file name would be in format of 'assetNamePrefix_resFileName'. It can be empty.</param>
        /// <param name="loadFunc">Load method</param>
        /// <returns></returns>
        public static UIPackage AddPackage(byte[] descData, string assetNamePrefix, LoadResource loadFunc)
        {
            ByteBuffer buffer = new ByteBuffer(descData);

            UIPackage pkg = new UIPackage();
            pkg._loadFunc = loadFunc;
            if (!pkg.LoadPackage(buffer, assetNamePrefix))
                return null;

            _packageInstById[pkg.id] = pkg;
            _packageInstByName[pkg.name] = pkg;
            _packageList.Add(pkg);

            return pkg;
        }

        /// <summary>
        /// Load Package async by custom load method.
        /// </summary>
        /// <param name="descData">Description file data</param>
        /// <param name="assetNamePrefix">refix of the resource file name. The file name would be in format of 'assetNamePrefix_resFileName'. It can be empty.</param>
        /// <param name="loadFunc">Load method</param>
        /// <returns></returns>
        public static UIPackage AddPackage(byte[] descData, string assetNamePrefix, LoadResourceAsync loadFunc)
        {
            ByteBuffer buffer = new ByteBuffer(descData);

            UIPackage pkg = new UIPackage();
            pkg._loadAsyncFunc = loadFunc;
            if (!pkg.LoadPackage(buffer, assetNamePrefix))
                return null;

            _packageInstById[pkg.id] = pkg;
            _packageInstByName[pkg.name] = pkg;
            _packageList.Add(pkg);

            return pkg;
        }

        /// <summary>
        /// Remove a package. All resources in this package will be disposed.
        /// </summary>
        /// <param name="packageIdOrName"></param>
        public static void RemovePackage(string packageIdOrName)
        {
            UIPackage pkg = null;
            if (!_packageInstById.TryGetValue(packageIdOrName, out pkg))
            {
                if (!_packageInstByName.TryGetValue(packageIdOrName, out pkg))
                    throw new Exception("FairyGUI: '" + packageIdOrName + "' is not a valid package id or name.");
            }

            pkg.Dispose();
            _packageInstById.Remove(pkg.id);
            if (pkg._customId != null)
                _packageInstById.Remove(pkg._customId);
            if (pkg._assetPath != null)
                _packageInstById.Remove(pkg._assetPath);
            _packageInstByName.Remove(pkg.name);
            _packageList.Remove(pkg);
        }

        /// <summary>
        /// 
        /// </summary>
        public static void RemoveAllPackages()
        {
            if (_packageInstById.Count > 0)
            {
                UIPackage[] pkgs = _packageList.ToArray();

                foreach (UIPackage pkg in pkgs)
                {
                    pkg.Dispose();
                }
            }

            _packageList.Clear();
            _packageInstById.Clear();
            _packageInstByName.Clear();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static List<UIPackage> GetPackages()
        {
            return _packageList;
        }

        /// <summary>
        /// Create a UI object.
        /// </summary>
        /// <param name="pkgName">Package name.</param>
        /// <param name="resName">Resource name.</param>
        /// <returns>A UI object.</returns>
        public static GObject CreateObject(string pkgName, string resName)
        {
            UIPackage pkg = GetByName(pkgName);
            if (pkg != null)
                return pkg.CreateObject(resName);
            else
                return null;
        }

        /// <summary>
        ///  Create a UI object.
        /// </summary>
        /// <param name="pkgName">Package name.</param>
        /// <param name="resName">Resource name.</param>
        /// <param name="userClass">Custom implementation of this object.</param>
        /// <returns>A UI object.</returns>
        public static GObject CreateObject(string pkgName, string resName, System.Type userClass)
        {
            UIPackage pkg = GetByName(pkgName);
            if (pkg != null)
                return pkg.CreateObject(resName, userClass);
            else
                return null;
        }

        /// <summary>
        /// Create a UI object.
        /// </summary>
        /// <param name="url">Resource url.</param>
        /// <returns>A UI object.</returns>
        public static GObject CreateObjectFromURL(string url)
        {
            PackageItem pi = GetItemByURL(url);
            if (pi != null)
                return pi.owner.CreateObject(pi, null);
            else
                return null;
        }

        /// <summary>
        /// Create a UI object.
        /// </summary>
        /// <param name="url">Resource url.</param>
        /// <param name="userClass">Custom implementation of this object.</param>
        /// <returns>A UI object.</returns>
        public static GObject CreateObjectFromURL(string url, System.Type userClass)
        {
            PackageItem pi = GetItemByURL(url);
            if (pi != null)
                return pi.owner.CreateObject(pi, userClass);
            else
                return null;
        }

        public static void CreateObjectAsync(string pkgName, string resName, CreateObjectCallback callback)
        {
            UIPackage pkg = GetByName(pkgName);
            if (pkg != null)
                pkg.CreateObjectAsync(resName, callback);
            else
                Debug.LogError("FairyGUI: package not found - " + pkgName);
        }

        public static void CreateObjectFromURL(string url, CreateObjectCallback callback)
        {
            PackageItem pi = GetItemByURL(url);
            if (pi != null)
                AsyncCreationHelper.CreateObject(pi, callback);
            else
                Debug.LogError("FairyGUI: resource not found - " + url);
        }

        /// <summary>
        /// Get a asset with a certain name.
        /// </summary>
        /// <param name="pkgName">Package name.</param>
        /// <param name="resName">Resource name.</param>
        /// <returns>If resource is atlas, returns NTexture; If resource is sound, returns AudioClip.</returns>
        public static object GetItemAsset(string pkgName, string resName)
        {
            UIPackage pkg = GetByName(pkgName);
            if (pkg != null)
                return pkg.GetItemAsset(resName);
            else
                return null;
        }

        /// <summary>
        /// Get a asset with a certain name.
        /// </summary>
        /// <param name="url">Resource url.</param>
        /// <returns>If resource is atlas, returns NTexture; If resource is sound, returns AudioClip.</returns>
        public static object GetItemAssetByURL(string url)
        {
            PackageItem item = GetItemByURL(url);
            if (item == null)
                return null;

            return item.owner.GetItemAsset(item);
        }

        /// <summary>
        /// Get url of an item in package.
        /// </summary>
        /// <param name="pkgName">Package name.</param>
        /// <param name="resName">Resource name.</param>
        /// <returns>Url.</returns>
        public static string GetItemURL(string pkgName, string resName)
        {
            UIPackage pkg = GetByName(pkgName);
            if (pkg == null)
                return null;

            PackageItem pi;
            if (!pkg._itemsByName.TryGetValue(resName, out pi))
                return null;

            return URL_PREFIX + pkg.id + pi.id;
        }

        public static PackageItem GetItemByURL(string url)
        {
            if (url == null)
                return null;

            int pos1 = url.IndexOf("//");
            if (pos1 == -1)
                return null;

            int pos2 = url.IndexOf('/', pos1 + 2);
            if (pos2 == -1)
            {
                if (url.Length > 13)
                {
                    string pkgId = url.Substring(5, 8);
                    UIPackage pkg = GetById(pkgId);
                    if (pkg != null)
                    {
                        string srcId = url.Substring(13);
                        return pkg.GetItem(srcId);
                    }
                }
            }
            else
            {
                string pkgName = url.Substring(pos1 + 2, pos2 - pos1 - 2);
                UIPackage pkg = GetByName(pkgName);
                if (pkg != null)
                {
                    string srcName = url.Substring(pos2 + 1);
                    return pkg.GetItemByName(srcName);
                }
            }

            return null;
        }

        /// <summary>
        /// 将'ui://包名/组件名'转换为以内部id表达的url格式。如果传入的url本身就是内部id格式，则直接返回。
        /// 同时这个方法还带格式检测，如果传入不正确的url，会返回null。
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string NormalizeURL(string url)
        {
            if (url == null)
                return null;

            int pos1 = url.IndexOf("//");
            if (pos1 == -1)
                return null;

            int pos2 = url.IndexOf('/', pos1 + 2);
            if (pos2 == -1)
                return url;
            else
            {
                string pkgName = url.Substring(pos1 + 2, pos2 - pos1 - 2);
                string srcName = url.Substring(pos2 + 1);
                return GetItemURL(pkgName, srcName);
            }
        }

        /// <summary>
        /// Set strings source.
        /// </summary>
        /// <param name="source"></param>
        public static void SetStringsSource(XML source)
        {
            TranslationHelper.LoadFromXML(source);
        }

        /// <summary>
        /// 
        /// </summary>
        public string assetPath
        {
            get { return _assetPath; }
        }

        /// <summary>
        /// Set a custom id for package, then you can use it in GetById.
        /// </summary>
        public string customId
        {
            get { return _customId; }
            set
            {
                if (_customId != null)
                    _packageInstById.Remove(_customId);
                _customId = value;
                if (_customId != null)
                    _packageInstById[_customId] = this;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public AssetBundle resBundle
        {
            get { return _resBundle; }
        }

        /// <summary>
        /// 获得本包依赖的包的id列表
        /// </summary>
        public Dictionary<string, string>[] dependencies
        {
            get { return _dependencies; }
        }

        bool LoadPackage(ByteBuffer buffer, string assetNamePrefix)
        {
            if (buffer.ReadUint() != 0x46475549)
            {
                if (Application.isPlaying)
                    throw new Exception("FairyGUI: old package format found in '" + assetNamePrefix + "'");
                else
                {
                    Debug.LogWarning("FairyGUI: old package format found in '" + assetNamePrefix + "'");
                    return false;
                }
            }

            buffer.version = buffer.ReadInt();
            bool ver2 = buffer.version >= 2;
            buffer.ReadBool(); //compressed
            id = buffer.ReadString();
            name = buffer.ReadString();

            UIPackage existingPkg;
            if (_packageInstById.TryGetValue(id, out existingPkg))
            {
                if (name != existingPkg.name)
                    Debug.LogWarning("FairyGUI: Package conflicts, '" + name + "' and '" + existingPkg.name + "'");

#if UNITY_EDITOR
                //maybe multiple pkgs in different folder, pefer the one in resources
                if (Application.isEditor)
                {
                    if (existingPkg._loadFunc == _loadFromAssetsPath) //old one is outside resources path
                        existingPkg.Dispose(); //replace the existing
                    else if (existingPkg._loadFunc == _loadFromResourcesPath && _loadFunc == _loadFromResourcesPath
                                                                             && _assetPath.Length <
                                                                             existingPkg._assetPath
                                                                                 .Length) //both in resources path, pefer short path
                        existingPkg.Dispose(); //replace the existing
                    else //keep the existing
                        return false;
                }
#endif
            }

            buffer.Skip(20);
            int indexTablePos = buffer.position;
            int cnt;

            buffer.Seek(indexTablePos, 4);

            cnt = buffer.ReadInt();
            string[] stringTable = new string[cnt];
            for (int i = 0; i < cnt; i++)
                stringTable[i] = buffer.ReadString();
            buffer.stringTable = stringTable;

            if (buffer.Seek(indexTablePos, 5))
            {
                cnt = buffer.ReadInt();
                for (int i = 0; i < cnt; i++)
                {
                    int index = buffer.ReadUshort();
                    int len = buffer.ReadInt();
                    stringTable[index] = buffer.ReadString(len);
                }
            }

            buffer.Seek(indexTablePos, 0);

            cnt = buffer.ReadShort();
            _dependencies = new Dictionary<string, string>[cnt];
            for (int i = 0; i < cnt; i++)
            {
                Dictionary<string, string> kv = new Dictionary<string, string>();
                kv.Add("id", buffer.ReadS());
                kv.Add("name", buffer.ReadS());
                _dependencies[i] = kv;
            }

            bool branchIncluded = false;
            if (ver2)
            {
                cnt = buffer.ReadShort();
                if (cnt > 0)
                {
                    _branches = buffer.ReadSArray(cnt);
                    if (!string.IsNullOrEmpty(_branch))
                        _branchIndex = Array.IndexOf(_branches, _branch);
                }

                branchIncluded = cnt > 0;
            }

            buffer.Seek(indexTablePos, 1);

            PackageItem pi;
            string assetPath;
            if (assetNamePrefix.Length > 0)
            {
                assetPath = Path.GetDirectoryName(assetNamePrefix);
                if (assetPath.Length > 0)
                    assetPath += "/";
                assetNamePrefix = assetNamePrefix + "_";
            }
            else
                assetPath = string.Empty;

            cnt = buffer.ReadShort();
            for (int i = 0; i < cnt; i++)
            {
                int nextPos = buffer.ReadInt();
                nextPos += buffer.position;

                pi = new PackageItem();
                pi.owner = this;
                pi.type = (PackageItemType)buffer.ReadByte();
                pi.id = buffer.ReadS();
                pi.name = buffer.ReadS();
                buffer.ReadS(); //path
                pi.file = buffer.ReadS();
                pi.exported = buffer.ReadBool();
                pi.width = buffer.ReadInt();
                pi.height = buffer.ReadInt();

                switch (pi.type)
                {
                    case PackageItemType.Image:
                    {
                        pi.objectType = ObjectType.Image;
                        int scaleOption = buffer.ReadByte();
                        if (scaleOption == 1)
                        {
                            Rect rect = new Rect();
                            rect.x = buffer.ReadInt();
                            rect.y = buffer.ReadInt();
                            rect.width = buffer.ReadInt();
                            rect.height = buffer.ReadInt();
                            pi.scale9Grid = rect;

                            pi.tileGridIndice = buffer.ReadInt();
                        }
                        else if (scaleOption == 2)
                            pi.scaleByTile = true;

                        buffer.ReadBool(); //smoothing
                        break;
                    }

                    case PackageItemType.MovieClip:
                    {
                        buffer.ReadBool(); //smoothing
                        pi.objectType = ObjectType.MovieClip;
                        pi.rawData = buffer.ReadBuffer();
                        break;
                    }

                    case PackageItemType.Font:
                    {
                        pi.rawData = buffer.ReadBuffer();
                        break;
                    }

                    case PackageItemType.Component:
                    {
                        int extension = buffer.ReadByte();
                        if (extension > 0)
                            pi.objectType = (ObjectType)extension;
                        else
                            pi.objectType = ObjectType.Component;
                        pi.rawData = buffer.ReadBuffer();

                        UIObjectFactory.ResolvePackageItemExtension(pi);
                        break;
                    }

                    case PackageItemType.Atlas:
                    case PackageItemType.Sound:
                    case PackageItemType.Misc:
                    {
                        pi.file = assetNamePrefix + pi.file;
                        break;
                    }

                    case PackageItemType.Spine:
                    case PackageItemType.DragoneBones:
                    {
                        pi.file = assetPath + pi.file;
                        pi.skeletonAnchor.x = buffer.ReadFloat();
                        pi.skeletonAnchor.y = buffer.ReadFloat();
                        break;
                    }
                }

                if (ver2)
                {
                    string str = buffer.ReadS(); //branch
                    if (str != null)
                        pi.name = str + "/" + pi.name;

                    int branchCnt = buffer.ReadByte();
                    if (branchCnt > 0)
                    {
                        if (branchIncluded)
                            pi.branches = buffer.ReadSArray(branchCnt);
                        else
                            _itemsById[buffer.ReadS()] = pi;
                    }

                    int highResCnt = buffer.ReadByte();
                    if (highResCnt > 0)
                        pi.highResolution = buffer.ReadSArray(highResCnt);
                }

                _items.Add(pi);
                _itemsById[pi.id] = pi;
                if (pi.name != null)
                    _itemsByName[pi.name] = pi;

                buffer.position = nextPos;
            }

            buffer.Seek(indexTablePos, 2);

            cnt = buffer.ReadShort();
            for (int i = 0; i < cnt; i++)
            {
                int nextPos = buffer.ReadUshort();
                nextPos += buffer.position;

                string itemId = buffer.ReadS();
                pi = _itemsById[buffer.ReadS()];

                AtlasSprite sprite = new AtlasSprite();
                sprite.atlas = pi;
                sprite.rect.x = buffer.ReadInt();
                sprite.rect.y = buffer.ReadInt();
                sprite.rect.width = buffer.ReadInt();
                sprite.rect.height = buffer.ReadInt();
                sprite.rotated = buffer.ReadBool();
                if (ver2 && buffer.ReadBool())
                {
                    sprite.offset.x = buffer.ReadInt();
                    sprite.offset.y = buffer.ReadInt();
                    sprite.originalSize.x = buffer.ReadInt();
                    sprite.originalSize.y = buffer.ReadInt();
                }
                else if (sprite.rotated)
                {
                    sprite.originalSize.x = sprite.rect.height;
                    sprite.originalSize.y = sprite.rect.width;
                }
                else
                {
                    sprite.originalSize.x = sprite.rect.width;
                    sprite.originalSize.y = sprite.rect.height;
                }

                _sprites[itemId] = sprite;

                buffer.position = nextPos;
            }

            if (buffer.Seek(indexTablePos, 3))
            {
                cnt = buffer.ReadShort();
                for (int i = 0; i < cnt; i++)
                {
                    int nextPos = buffer.ReadInt();
                    nextPos += buffer.position;

                    if (_itemsById.TryGetValue(buffer.ReadS(), out pi))
                    {
                        if (pi.type == PackageItemType.Image)
                        {
                            pi.pixelHitTestData = new PixelHitTestData();
                            pi.pixelHitTestData.Load(buffer);
                        }
                    }

                    buffer.position = nextPos;
                }
            }

            if (!Application.isPlaying)
                _items.Sort(ComparePackageItem);

            return true;
        }

        static int ComparePackageItem(PackageItem p1, PackageItem p2)
        {
            if (p1.name != null && p2.name != null)
                return p1.name.CompareTo(p2.name);
            else
                return 0;
        }

        /// <summary>
        /// 
        /// </summary>
        public void LoadAllAssets()
        {
            int cnt = _items.Count;
            for (int i = 0; i < cnt; i++)
                GetItemAsset(_items[i]);
        }

        /// <summary>
        /// 
        /// </summary>
        public void UnloadAssets()
        {
            int cnt = _items.Count;
            for (int i = 0; i < cnt; i++)
            {
                PackageItem pi = _items[i];
                if (pi.type == PackageItemType.Atlas)
                {
                    if (pi.texture != null)
                        pi.texture.Unload();
                }
                else if (pi.type == PackageItemType.Sound)
                {
                    if (pi.audioClip != null)
                        pi.audioClip.Unload();
                }
            }

            if (unloadBundleByFGUI &&
                _resBundle != null)
            {
                _resBundle.Unload(true);
                _resBundle = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void ReloadAssets()
        {
            if (_fromBundle)
                throw new Exception("FairyGUI: new bundle must be passed to this function");

            ReloadAssets(null);
        }

        /// <summary>
        /// 
        /// </summary>
        public void ReloadAssets(AssetBundle resBundle)
        {
            _resBundle = resBundle;
            _fromBundle = _resBundle != null;

            int cnt = _items.Count;
            for (int i = 0; i < cnt; i++)
            {
                PackageItem pi = _items[i];
                if (pi.type == PackageItemType.Atlas)
                {
                    if (pi.texture != null && pi.texture.nativeTexture == null)
                        LoadAtlas(pi);
                }
                else if (pi.type == PackageItemType.Sound)
                {
                    if (pi.audioClip != null && pi.audioClip.nativeClip == null)
                        LoadSound(pi);
                }
            }
        }

        void Dispose()
        {
            int cnt = _items.Count;
            for (int i = 0; i < cnt; i++)
            {
                PackageItem pi = _items[i];
                if (pi.type == PackageItemType.Atlas)
                {
                    if (pi.texture != null)
                    {
                        pi.texture.Dispose();
                        pi.texture = null;
                    }
                }
                else if (pi.type == PackageItemType.Sound)
                {
                    if (pi.audioClip != null)
                    {
                        pi.audioClip.Unload();
                        pi.audioClip = null;
                    }
                }
            }

            _items.Clear();

            if (unloadBundleByFGUI &&
                _resBundle != null)
            {
                _resBundle.Unload(true);
                _resBundle = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resName"></param>
        /// <returns></returns>
        public GObject CreateObject(string resName)
        {
            PackageItem pi;
            if (!_itemsByName.TryGetValue(resName, out pi))
            {
                Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
                return null;
            }

            return CreateObject(pi, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resName"></param>
        /// <param name="userClass"></param>
        /// <returns></returns>
        public GObject CreateObject(string resName, System.Type userClass)
        {
            PackageItem pi;
            if (!_itemsByName.TryGetValue(resName, out pi))
            {
                Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
                return null;
            }

            return CreateObject(pi, userClass);
        }

        public void CreateObjectAsync(string resName, CreateObjectCallback callback)
        {
            PackageItem pi;
            if (!_itemsByName.TryGetValue(resName, out pi))
            {
                Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
                return;
            }

            AsyncCreationHelper.CreateObject(pi, callback);
        }

        GObject CreateObject(PackageItem item, System.Type userClass)
        {
            Stats.LatestObjectCreation = 0;
            Stats.LatestGraphicsCreation = 0;

            GetItemAsset(item);

            GObject g = UIObjectFactory.NewObject(item, userClass);
            if (g == null)
                return null;

            _constructing++;
            g.ConstructFromResource();
            _constructing--;

            return g;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="resName"></param>
        /// <returns></returns>
        public object GetItemAsset(string resName)
        {
            PackageItem pi;
            if (!_itemsByName.TryGetValue(resName, out pi))
            {
                Debug.LogError("FairyGUI: Resource not found - " + resName + " in " + this.name);
                return null;
            }

            return GetItemAsset(pi);
        }

        public List<PackageItem> GetItems()
        {
            return _items;
        }

        public PackageItem GetItem(string itemId)
        {
            PackageItem pi;
            if (_itemsById.TryGetValue(itemId, out pi))
                return pi;
            else
                return null;
        }

        public PackageItem GetItemByName(string itemName)
        {
            PackageItem pi;
            if (_itemsByName.TryGetValue(itemName, out pi))
                return pi;
            else
                return null;
        }

        public object GetItemAsset(PackageItem item)
        {
            switch (item.type)
            {
                case PackageItemType.Image:
                    if (item.texture == null)
                        LoadImage(item);
                    return item.texture;

                case PackageItemType.Atlas:
                    if (item.texture == null)
                        LoadAtlas(item);
                    return item.texture;

                case PackageItemType.Sound:
                    if (item.audioClip == null)
                        LoadSound(item);
                    return item.audioClip;

                case PackageItemType.Font:
                    if (item.bitmapFont == null)
                        LoadFont(item);

                    return item.bitmapFont;

                case PackageItemType.MovieClip:
                    if (item.frames == null)
                        LoadMovieClip(item);

                    return item.frames;

                case PackageItemType.Component:
                    return item.rawData;

                case PackageItemType.Misc:
                    return LoadBinary(item);

                case PackageItemType.Spine:
                    if (item.skeletonAsset == null)
                        LoadSpine(item);
                    return item.skeletonAsset;

                case PackageItemType.DragoneBones:
                    if (item.skeletonAsset == null)
                        LoadDragonBones(item);
                    return item.skeletonAsset;

                default:
                    return null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="asset"></param>
        /// <param name="destroyMethod"></param>
        public void SetItemAsset(PackageItem item, object asset, DestroyMethod destroyMethod)
        {
            switch (item.type)
            {
                case PackageItemType.Atlas:
                    if (item.texture == null)
                        item.texture = new NTexture(null, new Rect(0, 0, item.width, item.height));
                    item.texture.Reload((Texture)asset, null);
                    item.texture.destroyMethod = destroyMethod;
                    break;

                case PackageItemType.Sound:
                    if (item.audioClip == null)
                        item.audioClip = new NAudioClip(null);
                    item.audioClip.Reload((AudioClip)asset);
                    item.audioClip.destroyMethod = destroyMethod;
                    break;

                case PackageItemType.Spine:
#if FAIRYGUI_SPINE
                    item.skeletonAsset = (Spine.Unity.SkeletonDataAsset)asset;
#endif
                    break;

                case PackageItemType.DragoneBones:
#if FAIRYGUI_DRAGONBONES
                    item.skeletonAsset = (DragonBones.UnityDragonBonesData)asset;
#endif
                    break;
            }
        }

        void LoadAtlas(PackageItem item)
        {
            string ext = Path.GetExtension(item.file);
            string fileName = item.file.Substring(0, item.file.Length - ext.Length);

            if (_loadAsyncFunc != null)
            {
                _loadAsyncFunc(fileName, ext, typeof(Texture), item);
                if (item.texture == null)
                    item.texture = new NTexture(null, new Rect(0, 0, item.width, item.height));
                item.texture.destroyMethod = DestroyMethod.None;
            }
            else
            {
                Texture tex = null;
                Texture alphaTex = null;
                DestroyMethod dm;

                if (_fromBundle)
                {
                    if (_resBundle != null)
                        tex = _resBundle.LoadAsset<Texture>(fileName);
                    else
                        Debug.LogWarning("FairyGUI: bundle already unloaded.");

                    dm = DestroyMethod.None;
                }
                else
                {
                    tex = (Texture)_loadFunc(fileName, ext, typeof(Texture), out dm);
                }

                if (tex == null)
                    Debug.LogWarning("FairyGUI: texture '" + item.file + "' not found in " + this.name);

                else if (!(tex is Texture2D))
                {
                    Debug.LogWarning("FairyGUI: settings for '" + item.file +
                                     "' is wrong! Correct values are: (Texture Type=Default, Texture Shape=2D)");
                    tex = null;
                }
                else
                {
                    if (((Texture2D)tex).mipmapCount > 1)
                        Debug.LogWarning("FairyGUI: settings for '" + item.file +
                                         "' is wrong! Correct values are: (Generate Mip Maps=unchecked)");
                }

                if (tex != null)
                {
                    fileName = fileName + "!a";
                    if (_fromBundle)
                    {
                        if (_resBundle != null)
                            alphaTex = _resBundle.LoadAsset<Texture2D>(fileName);
                    }
                    else
                        alphaTex = (Texture2D)_loadFunc(fileName, ext, typeof(Texture2D), out dm);
                }

                if (tex == null)
                {
                    tex = NTexture.CreateEmptyTexture();
                    dm = DestroyMethod.Destroy;
                }

                if (item.texture == null)
                {
                    item.texture = new NTexture(tex, alphaTex, (float)tex.width / item.width,
                        (float)tex.height / item.height);
                    item.texture.onRelease += (NTexture t) =>
                    {
                        if (onReleaseResource != null)
                            onReleaseResource(item);
                    };
                }
                else
                    item.texture.Reload(tex, alphaTex);

                item.texture.destroyMethod = dm;
            }
        }

        void LoadImage(PackageItem item)
        {
            AtlasSprite sprite;
            if (_sprites.TryGetValue(item.id, out sprite))
            {
                NTexture atlas = (NTexture)GetItemAsset(sprite.atlas);
                if (atlas.width == sprite.rect.width && atlas.height == sprite.rect.height)
                    item.texture = atlas;
                else
                    item.texture = new NTexture(atlas, sprite.rect, sprite.rotated, sprite.originalSize, sprite.offset);
            }
            else
                item.texture = NTexture.Empty;
        }

        void LoadSound(PackageItem item)
        {
            string ext = Path.GetExtension(item.file);
            string fileName = item.file.Substring(0, item.file.Length - ext.Length);

            if (_loadAsyncFunc != null)
            {
                _loadAsyncFunc(fileName, ext, typeof(AudioClip), item);
                if (item.audioClip == null)
                    item.audioClip = new NAudioClip(null);
                item.audioClip.destroyMethod = DestroyMethod.None;
            }
            else
            {
                AudioClip audioClip = null;
                DestroyMethod dm;

                if (_fromBundle)
                {
                    if (_resBundle != null)
                        audioClip = _resBundle.LoadAsset<AudioClip>(fileName);
                    dm = DestroyMethod.None;
                }
                else
                {
                    audioClip = (AudioClip)_loadFunc(fileName, ext, typeof(AudioClip), out dm);
                }

                if (item.audioClip == null)
                    item.audioClip = new NAudioClip(audioClip);
                else
                    item.audioClip.Reload(audioClip);
                item.audioClip.destroyMethod = dm;
            }
        }

        byte[] LoadBinary(PackageItem item)
        {
            string ext = Path.GetExtension(item.file);
            string fileName = item.file.Substring(0, item.file.Length - ext.Length);

            TextAsset ta;
            if (_resBundle != null)
            {
                ta = _resBundle.LoadAsset<TextAsset>(fileName);
                if (ta != null)
                    return ta.bytes;
                else
                    return null;
            }
            else
            {
                DestroyMethod dm;
                object ret = _loadFunc(fileName, ext, typeof(TextAsset), out dm);
                if (ret == null)
                    return null;
                if (ret is byte[])
                    return (byte[])ret;
                else
                    return ((TextAsset)ret).bytes;
            }
        }

        void LoadMovieClip(PackageItem item)
        {
            ByteBuffer buffer = item.rawData;

            buffer.Seek(0, 0);

            item.interval = buffer.ReadInt() / 1000f;
            item.swing = buffer.ReadBool();
            item.repeatDelay = buffer.ReadInt() / 1000f;

            buffer.Seek(0, 1);

            int frameCount = buffer.ReadShort();
            item.frames = new MovieClip.Frame[frameCount];

            string spriteId;
            MovieClip.Frame frame;
            AtlasSprite sprite;
            Rect frameRect = new Rect();

            for (int i = 0; i < frameCount; i++)
            {
                int nextPos = buffer.ReadUshort();
                nextPos += buffer.position;

                frame = new MovieClip.Frame();
                frameRect.x = buffer.ReadInt();
                frameRect.y = buffer.ReadInt();
                frameRect.width = buffer.ReadInt();
                frameRect.height = buffer.ReadInt();
                frame.addDelay = buffer.ReadInt() / 1000f;
                spriteId = buffer.ReadS();

                if (spriteId != null && _sprites.TryGetValue(spriteId, out sprite))
                {
                    frame.texture = new NTexture((NTexture)GetItemAsset(sprite.atlas), sprite.rect, sprite.rotated,
                        new Vector2(item.width, item.height), frameRect.position);
                }

                item.frames[i] = frame;

                buffer.position = nextPos;
            }
        }

        void LoadFont(PackageItem item)
        {
            BitmapFont font = new BitmapFont();
            font.name = URL_PREFIX + this.id + item.id;
            item.bitmapFont = font;
            ByteBuffer buffer = item.rawData;

            buffer.Seek(0, 0);

            bool ttf = buffer.ReadBool();
            font.canTint = buffer.ReadBool();
            font.resizable = buffer.ReadBool();
            font.hasChannel = buffer.ReadBool();
            int fontSize = buffer.ReadInt();
            int xadvance = buffer.ReadInt();
            int lineHeight = buffer.ReadInt();

            float texScaleX = 1;
            float texScaleY = 1;
            int bgX;
            int bgY;
            int bgWidth;
            int bgHeight;

            NTexture mainTexture = null;
            AtlasSprite mainSprite = null;
            if (ttf && _sprites.TryGetValue(item.id, out mainSprite))
            {
                mainTexture = (NTexture)GetItemAsset(mainSprite.atlas);
                texScaleX = mainTexture.root.uvRect.width / mainTexture.width;
                texScaleY = mainTexture.root.uvRect.height / mainTexture.height;
            }

            buffer.Seek(0, 1);

            BitmapFont.BMGlyph bg;
            int cnt = buffer.ReadInt();
            for (int i = 0; i < cnt; i++)
            {
                int nextPos = buffer.ReadUshort();
                nextPos += buffer.position;

                bg = new BitmapFont.BMGlyph();
                char ch = buffer.ReadChar();
                font.AddChar(ch, bg);

                string img = buffer.ReadS();
                int bx = buffer.ReadInt();
                int by = buffer.ReadInt();
                bgX = buffer.ReadInt();
                bgY = buffer.ReadInt();
                bgWidth = buffer.ReadInt();
                bgHeight = buffer.ReadInt();
                bg.advance = buffer.ReadInt();
                bg.channel = buffer.ReadByte();
                //The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha, 15-all).
                if (bg.channel == 1)
                    bg.channel = 2;
                else if (bg.channel == 2)
                    bg.channel = 1;
                else if (bg.channel == 4)
                    bg.channel = 0;
                else if (bg.channel == 8)
                    bg.channel = 3;

                if (ttf)
                {
                    if (mainSprite.rotated)
                    {
                        bg.uv[0] = new Vector2((float)(by + bgHeight + mainSprite.rect.x) * texScaleX,
                            1 - (float)(mainSprite.rect.yMax - bx) * texScaleY);
                        bg.uv[1] = new Vector2(bg.uv[0].x - (float)bgHeight * texScaleX, bg.uv[0].y);
                        bg.uv[2] = new Vector2(bg.uv[1].x, bg.uv[0].y + (float)bgWidth * texScaleY);
                        bg.uv[3] = new Vector2(bg.uv[0].x, bg.uv[2].y);
                    }
                    else
                    {
                        bg.uv[0] = new Vector2((float)(bx + mainSprite.rect.x) * texScaleX,
                            1 - (float)(by + bgHeight + mainSprite.rect.y) * texScaleY);
                        bg.uv[1] = new Vector2(bg.uv[0].x, bg.uv[0].y + (float)bgHeight * texScaleY);
                        bg.uv[2] = new Vector2(bg.uv[0].x + (float)bgWidth * texScaleX, bg.uv[1].y);
                        bg.uv[3] = new Vector2(bg.uv[2].x, bg.uv[0].y);
                    }

                    bg.lineHeight = lineHeight;
                    bg.x = bgX;
                    bg.y = bgY;
                    bg.width = bgWidth;
                    bg.height = bgHeight;
                }
                else
                {
                    PackageItem charImg;
                    if (_itemsById.TryGetValue(img, out charImg))
                    {
                        charImg = charImg.getBranch();
                        bgWidth = charImg.width;
                        bgHeight = charImg.height;
                        charImg = charImg.getHighResolution();
                        GetItemAsset(charImg);
                        charImg.texture.GetUV(bg.uv);

                        texScaleX = (float)bgWidth / charImg.width;
                        texScaleY = (float)bgHeight / charImg.height;

                        bg.x = bgX + charImg.texture.offset.x * texScaleX;
                        bg.y = bgY + charImg.texture.offset.y * texScaleY;
                        bg.width = charImg.texture.width * texScaleX;
                        bg.height = charImg.texture.height * texScaleY;

                        if (mainTexture == null)
                            mainTexture = charImg.texture.root;
                    }

                    if (fontSize == 0)
                        fontSize = bgHeight;

                    if (bg.advance == 0)
                    {
                        if (xadvance == 0)
                            bg.advance = bgX + bgWidth;
                        else
                            bg.advance = xadvance;
                    }

                    bg.lineHeight = bgY < 0 ? bgHeight : (bgY + bgHeight);
                    if (bg.lineHeight < fontSize)
                        bg.lineHeight = fontSize;
                }

                buffer.position = nextPos;
            }

            font.size = fontSize;
            font.mainTexture = mainTexture;
            if (!font.hasChannel)
                font.shader = ShaderConfig.imageShader;
        }

        void LoadSpine(PackageItem item)
        {
#if FAIRYGUI_SPINE
            string ext = Path.GetExtension(item.file);
            string fileName = item.file.Substring(0, item.file.Length - ext.Length);
            int index = fileName.LastIndexOf(".skel");
            if (index > 0)
                fileName = fileName.Substring(0, index);

            Spine.Unity.SkeletonDataAsset asset;
            if (_resBundle != null)
                asset = _resBundle.LoadAsset<Spine.Unity.SkeletonDataAsset>(fileName + "_SkeletonData");
            else
            {
                DestroyMethod dm;
                asset =
 (Spine.Unity.SkeletonDataAsset)_loadFunc(fileName + "_SkeletonData", ".asset", typeof(Spine.Unity.SkeletonDataAsset), out dm);
            }
            if (asset == null)
                Debug.LogWarning("FairyGUI: Failed to load " + fileName);
            item.skeletonAsset = asset;
#else
            Debug.LogWarning("To enable Spine support, add script define symbol: FAIRYGUI_SPINE");
#endif
        }

        void LoadDragonBones(PackageItem item)
        {
#if FAIRYGUI_DRAGONBONES
            string ext = Path.GetExtension(item.file);
            string fileName = item.file.Substring(0, item.file.Length - ext.Length);
            int index = fileName.LastIndexOf("_ske");
            if (index > 0)
                fileName = fileName.Substring(0, index);
            index = fileName.LastIndexOf(".dbbin");
            if (index > 0)
                fileName = fileName.Substring(0, index);

            DragonBones.UnityDragonBonesData asset;
            if (_resBundle != null)
                asset = _resBundle.LoadAsset<DragonBones.UnityDragonBonesData>(fileName + "_Data");
            else
            {
                DestroyMethod dm;
                asset =
 (DragonBones.UnityDragonBonesData)_loadFunc(fileName + "_Data", ".asset", typeof(DragonBones.UnityDragonBonesData), out dm);
            }
            if (asset != null)
            {
                foreach (var atlas in asset.textureAtlas)
                {
                    if (atlas.material == null)
                    {
                        atlas.material = new Material(ShaderConfig.GetShader(ShaderConfig.imageShader));
                        atlas.material.mainTexture = atlas.texture;
                    }
                }
                item.skeletonAsset = DragonBones.UnityFactory.factory.LoadData(asset);
            }
            else
                Debug.LogWarning("FairyGUI: Failed to load " + fileName);
#else
            Debug.LogWarning("To enable DragonBones support, add script define symbol: FAIRYGUI_DRAGONBONES");
#endif
        }
    }
}