/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using log4net;
using OpenMetaverse;

namespace OpenSim.Framework
{
    public static class SLUtil
    {
//        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Asset types used only in OpenSim.
        /// To avoid clashing with the code numbers used in Second Life, use only negative numbers here.
        /// </summary>
        public enum OpenSimAssetType : sbyte
        {
            Material = -2
        }

        
        #region SL / file extension / content-type conversions

        /// <summary>
        /// Returns the Enum entry corresponding to the given code, regardless of whether it belongs
        /// to the AssetType or OpenSimAssetType enums.
        /// </summary>
        public static object AssetTypeFromCode(sbyte assetType)
        {
            if (Enum.IsDefined(typeof(OpenMetaverse.AssetType), assetType))
                return (OpenMetaverse.AssetType)assetType;
            else if (Enum.IsDefined(typeof(OpenSimAssetType), assetType))
                return (OpenSimAssetType)assetType;
            else
                return OpenMetaverse.AssetType.Unknown;
        }

        private class TypeMapping
        {
            private sbyte assetType;
            private InventoryType inventoryType;
            private string contentType;
            private string contentType2;
            private string extension;

            public sbyte AssetTypeCode
            {
                get { return assetType; }
            }

            public object AssetType
            {
                get { return AssetTypeFromCode(assetType); }
            }

            public InventoryType InventoryType
            {
                get { return inventoryType; }
            }

            public string ContentType
            {
                get { return contentType; }
            }

            public string ContentType2
            {
                get { return contentType2; }
            }

            public string Extension
            {
                get { return extension; }
            }

            private TypeMapping(sbyte assetType, InventoryType inventoryType, string contentType, string contentType2, string extension)
            {
                this.assetType = assetType;
                this.inventoryType = inventoryType;
                this.contentType = contentType;
                this.contentType2 = contentType2;
                this.extension = extension;
            }

            public TypeMapping(AssetType assetType, InventoryType inventoryType, string contentType, string contentType2, string extension)
                : this((sbyte)assetType, inventoryType, contentType, contentType2, extension)
            {
            }

            public TypeMapping(AssetType assetType, InventoryType inventoryType, string contentType, string extension)
                : this((sbyte)assetType, inventoryType, contentType, null, extension)
            {
            }

            public TypeMapping(OpenSimAssetType assetType, InventoryType inventoryType, string contentType, string extension)
                : this((sbyte)assetType, inventoryType, contentType, null, extension)
            {
            }
        }

        /// <summary>
        /// Maps between AssetType, InventoryType and Content-Type.
        /// Where more than one possibility exists, the first one takes precedence. E.g.:
        ///   AssetType "AssetType.Texture" -> Content-Type "image-xj2c"
        ///   Content-Type "image/x-j2c" -> InventoryType "InventoryType.Texture"
        /// </summary>
        private static TypeMapping[] MAPPINGS = new TypeMapping[] {
            new TypeMapping(AssetType.Unknown, InventoryType.Unknown, "application/octet-stream", "bin"),
            new TypeMapping(AssetType.Texture, InventoryType.Texture, "image/x-j2c", "image/jp2", "j2c"),
            new TypeMapping(AssetType.Texture, InventoryType.Snapshot, "image/x-j2c", "image/jp2", "j2c"),
            new TypeMapping(AssetType.TextureTGA, InventoryType.Texture, "image/tga", "tga"),
            new TypeMapping(AssetType.ImageTGA, InventoryType.Texture, "image/tga", "tga"),
            new TypeMapping(AssetType.ImageJPEG, InventoryType.Texture, "image/jpeg", "jpg"),
            new TypeMapping(AssetType.Sound, InventoryType.Sound, "audio/ogg", "application/ogg", "ogg"),
            new TypeMapping(AssetType.SoundWAV, InventoryType.Sound, "audio/x-wav", "wav"),
            new TypeMapping(AssetType.CallingCard, InventoryType.CallingCard, "application/vnd.ll.callingcard", "application/x-metaverse-callingcard", "callingcard"),
            new TypeMapping(AssetType.Landmark, InventoryType.Landmark, "application/vnd.ll.landmark", "application/x-metaverse-landmark", "landmark"),
            new TypeMapping(AssetType.Clothing, InventoryType.Wearable, "application/vnd.ll.clothing", "application/x-metaverse-clothing", "clothing"),
            new TypeMapping(AssetType.Object, InventoryType.Object, "application/vnd.ll.primitive", "application/x-metaverse-primitive", "primitive"),
            new TypeMapping(AssetType.Object, InventoryType.Attachment, "application/vnd.ll.primitive", "application/x-metaverse-primitive", "primitive"),
            new TypeMapping(AssetType.Notecard, InventoryType.Notecard, "application/vnd.ll.notecard", "application/x-metaverse-notecard", "notecard"),
            new TypeMapping(AssetType.Folder, InventoryType.Folder, "application/vnd.ll.folder", "folder"),
            new TypeMapping(AssetType.RootFolder, InventoryType.RootCategory, "application/vnd.ll.rootfolder", "rootfolder"),
            new TypeMapping(AssetType.LSLText, InventoryType.LSL, "application/vnd.ll.lsltext", "application/x-metaverse-lsl", "lsl"),
            new TypeMapping(AssetType.LSLBytecode, InventoryType.LSL, "application/vnd.ll.lslbyte", "application/x-metaverse-lso", "lso"),
            new TypeMapping(AssetType.Bodypart, InventoryType.Wearable, "application/vnd.ll.bodypart", "application/x-metaverse-bodypart", "bodypart"),
            new TypeMapping(AssetType.TrashFolder, InventoryType.Folder, "application/vnd.ll.trashfolder", "trashfolder"),
            new TypeMapping(AssetType.SnapshotFolder, InventoryType.Folder, "application/vnd.ll.snapshotfolder", "snapshotfolder"),
            new TypeMapping(AssetType.LostAndFoundFolder, InventoryType.Folder, "application/vnd.ll.lostandfoundfolder", "lostandfoundfolder"),
            new TypeMapping(AssetType.Animation, InventoryType.Animation, "application/vnd.ll.animation", "application/x-metaverse-animation", "animation"),
            new TypeMapping(AssetType.Gesture, InventoryType.Gesture, "application/vnd.ll.gesture", "application/x-metaverse-gesture", "gesture"),
            new TypeMapping(AssetType.Simstate, InventoryType.Snapshot, "application/x-metaverse-simstate", "simstate"),
            new TypeMapping(AssetType.FavoriteFolder, InventoryType.Unknown, "application/vnd.ll.favoritefolder", "favoritefolder"),
            new TypeMapping(AssetType.Link, InventoryType.Unknown, "application/vnd.ll.link", "link"),
            new TypeMapping(AssetType.LinkFolder, InventoryType.Unknown, "application/vnd.ll.linkfolder", "linkfolder"),
            new TypeMapping(AssetType.CurrentOutfitFolder, InventoryType.Unknown, "application/vnd.ll.currentoutfitfolder", "currentoutfitfolder"),
            new TypeMapping(AssetType.OutfitFolder, InventoryType.Unknown, "application/vnd.ll.outfitfolder", "outfitfolder"),
            new TypeMapping(AssetType.MyOutfitsFolder, InventoryType.Unknown, "application/vnd.ll.myoutfitsfolder", "myoutfitsfolder"),
            new TypeMapping(AssetType.Mesh, InventoryType.Mesh, "application/vnd.ll.mesh", "llm"),
            
            new TypeMapping(OpenSimAssetType.Material, InventoryType.Unknown, "application/llsd+xml", "material")
        };

        private static Dictionary<sbyte, string> asset2Content;
        private static Dictionary<sbyte, string> asset2Extension;
        private static Dictionary<InventoryType, string> inventory2Content;
        private static Dictionary<string, sbyte> content2Asset;
        private static Dictionary<string, InventoryType> content2Inventory;

        static SLUtil()
        {
            asset2Content = new Dictionary<sbyte, string>();
            asset2Extension = new Dictionary<sbyte, string>();
            inventory2Content = new Dictionary<InventoryType, string>();
            content2Asset = new Dictionary<string, sbyte>();
            content2Inventory = new Dictionary<string, InventoryType>();
            
            foreach (TypeMapping mapping in MAPPINGS)
            {
                sbyte assetType = mapping.AssetTypeCode;
                if (!asset2Content.ContainsKey(assetType))
                    asset2Content.Add(assetType, mapping.ContentType);
                if (!asset2Extension.ContainsKey(assetType))
                    asset2Extension.Add(assetType, mapping.Extension);
                if (!inventory2Content.ContainsKey(mapping.InventoryType))
                    inventory2Content.Add(mapping.InventoryType, mapping.ContentType);
                if (!content2Asset.ContainsKey(mapping.ContentType))
                    content2Asset.Add(mapping.ContentType, assetType);
                if (!content2Inventory.ContainsKey(mapping.ContentType))
                    content2Inventory.Add(mapping.ContentType, mapping.InventoryType);

                if (mapping.ContentType2 != null)
                {
                    if (!content2Asset.ContainsKey(mapping.ContentType2))
                        content2Asset.Add(mapping.ContentType2, assetType);
                    if (!content2Inventory.ContainsKey(mapping.ContentType2))
                        content2Inventory.Add(mapping.ContentType2, mapping.InventoryType);
                }
            }
        }
        
        public static string SLAssetTypeToContentType(int assetType)
        {
            string contentType;
            if (!asset2Content.TryGetValue((sbyte)assetType, out contentType))
                contentType = asset2Content[(sbyte)AssetType.Unknown];
            return contentType;
        }

        public static string SLInvTypeToContentType(int invType)
        {
            string contentType;
            if (!inventory2Content.TryGetValue((InventoryType)invType, out contentType))
                contentType = inventory2Content[InventoryType.Unknown];
            return contentType;
        }

        public static sbyte ContentTypeToSLAssetType(string contentType)
        {
            sbyte assetType;
            if (!content2Asset.TryGetValue(contentType, out assetType))
                assetType = (sbyte)AssetType.Unknown;
            return (sbyte)assetType;
        }

        public static sbyte ContentTypeToSLInvType(string contentType)
        {
            InventoryType invType;
            if (!content2Inventory.TryGetValue(contentType, out invType))
                invType = InventoryType.Unknown;
            return (sbyte)invType;
        }

        public static string SLAssetTypeToExtension(int assetType)
        {
            string extension;
            if (!asset2Extension.TryGetValue((sbyte)assetType, out extension))
                extension = asset2Extension[(sbyte)AssetType.Unknown];
            return extension;
        }

        #endregion SL / file extension / content-type conversions

        /// <summary>
        /// Parse a notecard in Linden format to a string of ordinary text.
        /// </summary>
        /// <param name="rawInput"></param>
        /// <returns></returns>
        public static string ParseNotecardToString(string rawInput)
        {
            string[] output = ParseNotecardToList(rawInput).ToArray();

//            foreach (string line in output)
//                m_log.DebugFormat("[PARSE NOTECARD]: ParseNotecardToString got line {0}", line);
            
            return string.Join("\n", output);
        }
                
        /// <summary>
        /// Parse a notecard in Linden format to a list of ordinary lines.
        /// </summary>
        /// <param name="rawInput"></param>
        /// <returns></returns>
        public static List<string> ParseNotecardToList(string rawInput)
        {
            string[] input;
            int idx = 0;
            int level = 0;
            List<string> output = new List<string>();
            string[] words;

            //The Linden format always ends with a } after the input data.
            //Strip off trailing } so there is nothing after the input data.
            int i = rawInput.LastIndexOf("}");
            rawInput = rawInput.Remove(i, rawInput.Length-i);
            input = rawInput.Replace("\r", "").Split('\n');

            while (idx < input.Length)
            {
                if (input[idx] == "{")
                {
                    level++;
                    idx++;
                    continue;
                }

                if (input[idx]== "}")
                {
                    level--;
                    idx++;
                    continue;
                }

                switch (level)
                {
                case 0:
                    words = input[idx].Split(' '); // Linden text ver
                    // Notecards are created *really* empty. Treat that as "no text" (just like after saving an empty notecard)
                    if (words.Length < 3)
                        return output;

                    int version = int.Parse(words[3]);
                    if (version != 2)
                        return output;
                    break;
                case 1:
                    words = input[idx].Split(' ');
                    if (words[0] == "LLEmbeddedItems")
                        break;
                    if (words[0] == "Text")
                    {
                        idx++;  //Now points to first line of notecard text

                        //Number of lines in notecard.
                        int lines = input.Length - idx;
                        int line = 0;

                        while (line < lines)
                        {
//                            m_log.DebugFormat("[PARSE NOTECARD]: Adding line {0}", input[idx]);
                            output.Add(input[idx]);
                            idx++;
                            line++;
                        }

                        return output;
                    }
                    break;
                case 2:
                    words = input[idx].Split(' '); // count
                    if (words[0] == "count")
                    {
                        int c = int.Parse(words[1]);
                        if (c > 0)
                            return output;
                        break;
                    }
                    break;
                }
                idx++;
            }
            
            return output;
        }
    }
}
