/*
 * 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 OpenSim 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;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Xml;
using System.Drawing;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications;
using OpenSim.Framework.Communications.Cache;
using OpenMetaverse;
using OpenMetaverse.Imaging;
using Nini.Config;

namespace OpenSim.ApplicationPlugins.Rest.Inventory
{

    public class RestAppearanceServices : IRest
    {

        private static readonly int PARM_USERID = 0;

        // private static readonly int PARM_PATH   = 1;

        private bool       enabled = false;
        private string     qPrefix = "appearance";

        /// <summary>
        /// The constructor makes sure that the service prefix is absolute
        /// and the registers the service handler and the allocator.
        /// </summary>

        public RestAppearanceServices()
        {
            Rest.Log.InfoFormat("{0} User appearance services initializing", MsgId);
            Rest.Log.InfoFormat("{0} Using REST Implementation Version {1}", MsgId, Rest.Version);

            // This is better than a null reference.

            if (Rest.AvatarServices == null)
                throw new Exception(String.Format("{0} OpenSim inventory services are not available",
                                                  MsgId));

            if (Rest.UserServices == null)
                throw new Exception(String.Format("{0} OpenSim user profile services are not available",
                                                  MsgId));

            // If a relative path was specified for the handler's domain,
            // add the standard prefix to make it absolute, e.g. /admin

            if (!qPrefix.StartsWith(Rest.UrlPathSeparator))
            {
                Rest.Log.InfoFormat("{0} Domain is relative, adding absolute prefix", MsgId);
                qPrefix = String.Format("{0}{1}{2}", Rest.Prefix, Rest.UrlPathSeparator, qPrefix);
                Rest.Log.InfoFormat("{0} Domain is now <{1}>", MsgId, qPrefix);
            }

            // Register interface using the absolute URI.

            Rest.Plugin.AddPathHandler(DoAppearance,qPrefix,Allocate);

            // Activate if everything went OK

            enabled = true;

            Rest.Log.InfoFormat("{0} User appearance services initialization complete", MsgId);
        }

        /// <summary>
        /// Post-construction, pre-enabled initialization opportunity
        /// Not currently exploited.
        /// </summary>

        public void Initialize()
        {
        }

        /// <summary>
        /// Called by the plug-in to halt service processing. Local processing is
        /// disabled.
        /// </summary>

        public void Close()
        {
            enabled = false;
            Rest.Log.InfoFormat("{0} User appearance services closing down", MsgId);
        }

        /// <summary>
        /// This property is declared locally because it is used a lot and
        /// brevity is nice.
        /// </summary>

        internal string MsgId
        {
            get { return Rest.MsgId; }
        }

        #region Interface

        /// <summary>
        /// The plugin (RestHandler) calls this method to allocate the request
        /// state carrier for a new request. It is destroyed when the request
        /// completes. All request-instance specific state is kept here. This
        /// is registered when this service provider is registered.
        /// </summary>
        /// <param name=request>Inbound HTTP request information</param>
        /// <param name=response>Outbound HTTP request information</param>
        /// <param name=qPrefix>REST service domain prefix</param>
        /// <returns>A RequestData instance suitable for this service</returns>

        private RequestData Allocate(OSHttpRequest request, OSHttpResponse response, string prefix)
        {
            return (RequestData) new AppearanceRequestData(request, response, prefix);
        }

        /// <summary>
        /// This method is registered with the handler when this service provider
        /// is initialized. It is called whenever the plug-in identifies this service
        /// provider as the best match for a given request.
        /// It handles all aspects of inventory REST processing, i.e. /admin/inventory
        /// </summary>
        /// <param name=hdata>A consolidated HTTP request work area</param>

        private void DoAppearance(RequestData hdata)
        {

            AppearanceRequestData rdata = (AppearanceRequestData) hdata;

            Rest.Log.DebugFormat("{0} DoAppearance ENTRY", MsgId);

            // If we're disabled, do nothing.

            if (!enabled)
            {
                return;
            }

            // Now that we know this is a serious attempt to
            // access inventory data, we should find out who
            // is asking, and make sure they are authorized
            // to do so. We need to validate the caller's
            // identity before revealing anything about the
            // status quo. Authenticate throws an exception
            // via Fail if no identity information is present.
            //
            // With the present HTTP server we can't use the
            // builtin authentication mechanisms because they
            // would be enforced for all in-bound requests.
            // Instead we look at the headers ourselves and
            // handle authentication directly.

            try
            {
                if (!rdata.IsAuthenticated)
                {
                    rdata.Fail(Rest.HttpStatusCodeNotAuthorized,String.Format("user \"{0}\" could not be authenticated", rdata.userName));
                }
            }
            catch (RestException e)
            {
                if (e.statusCode == Rest.HttpStatusCodeNotAuthorized)
                {
                    Rest.Log.WarnFormat("{0} User not authenticated", MsgId);
                    Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
                }
                else
                {
                    Rest.Log.ErrorFormat("{0} User authentication failed", MsgId);
                    Rest.Log.DebugFormat("{0} Authorization header: {1}", MsgId, rdata.request.Headers.Get("Authorization"));
                }
                throw (e);
            }

            Rest.Log.DebugFormat("{0} Authenticated {1}", MsgId, rdata.userName);

            // We can only get here if we are authorized
            //
            // The requestor may have specified an UUID or
            // a conjoined FirstName LastName string. We'll
            // try both. If we fail with the first, UUID,
            // attempt, we try the other. As an example, the
            // URI for a valid inventory request might be:
            //
            // http://<host>:<port>/admin/inventory/Arthur Dent
            //
            // Indicating that this is an inventory request for
            // an avatar named Arthur Dent. This is ALL that is
            // required to designate a GET for an entire
            // inventory.
            //

            // Do we have at least a user agent name?

            if (rdata.Parameters.Length < 1)
            {
                Rest.Log.WarnFormat("{0} Appearance: No user agent identifier specified", MsgId);
                rdata.Fail(Rest.HttpStatusCodeBadRequest, "no user identity specified");
            }

            // The first parameter MUST be the agent identification, either an UUID
            // or a space-separated First-name Last-Name specification. We check for
            // an UUID first, if anyone names their character using a valid UUID
            // that identifies another existing avatar will cause this a problem...

            try
            {
                rdata.uuid = new UUID(rdata.Parameters[PARM_USERID]);
                Rest.Log.DebugFormat("{0} UUID supplied", MsgId);
                rdata.userProfile = Rest.UserServices.GetUserProfile(rdata.uuid);
            }
            catch
            {
                string[] names = rdata.Parameters[PARM_USERID].Split(Rest.CA_SPACE);
                if (names.Length == 2)
                {
                    Rest.Log.DebugFormat("{0} Agent Name supplied [2]", MsgId);
                    rdata.userProfile = Rest.UserServices.GetUserProfile(names[0],names[1]);
                }
                else
                {
                    Rest.Log.WarnFormat("{0} A Valid UUID or both first and last names must be specified", MsgId);
                    rdata.Fail(Rest.HttpStatusCodeBadRequest, "invalid user identity");
                }
            }

            // If the user profile is null then either the server is broken, or the
            // user is not known. We always assume the latter case.

            if (rdata.userProfile != null)
            {
                Rest.Log.DebugFormat("{0} User profile obtained for agent {1} {2}",
                                     MsgId, rdata.userProfile.FirstName, rdata.userProfile.SurName);
            }
            else
            {
                Rest.Log.WarnFormat("{0} No user profile for {1}", MsgId, rdata.path);
                rdata.Fail(Rest.HttpStatusCodeNotFound, "unrecognized user identity");
            }

            // If we get to here, then we have effectively validated the user's

            switch (rdata.method)
            {
                case Rest.HEAD   : // Do the processing, set the status code, suppress entity
                    DoGet(rdata);
                    rdata.buffer = null;
                    break;

                case Rest.GET    : // Do the processing, set the status code, return entity
                    DoGet(rdata);
                    break;

                case Rest.PUT    : // Update named element
                    DoUpdate(rdata);
                    break;

                case Rest.POST   : // Add new information to identified context.
                    DoExtend(rdata);
                    break;

                case Rest.DELETE : // Delete information
                    DoDelete(rdata);
                    break;

                default :
                    Rest.Log.WarnFormat("{0} Method {1} not supported for {2}",
                                         MsgId, rdata.method, rdata.path);
                    rdata.Fail(Rest.HttpStatusCodeMethodNotAllowed, 
                               String.Format("{0} not supported", rdata.method));
                    break;
            }
        }

        #endregion Interface

        #region method-specific processing

        /// <summary>
        /// This method implements GET processing for user's appearance.
        /// </summary>
        /// <param name=rdata>HTTP service request work area</param>

        private void DoGet(AppearanceRequestData rdata)
        {

            rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);

            if (rdata.userAppearance == null)
            {
                rdata.Fail(Rest.HttpStatusCodeNoContent,
                    String.Format("appearance data not found for user {0} {1}", 
                      rdata.userProfile.FirstName, rdata.userProfile.SurName));
            }

            rdata.initXmlWriter();

            FormatUserAppearance(rdata);

            // Indicate a successful request

            rdata.Complete();

            // Send the response to the user. The body will be implicitly
            // constructed from the result of the XML writer.

            rdata.Respond(String.Format("Appearance {0} Normal completion", rdata.method));
        }

        /// <summary>
        /// POST adds NEW information to the user profile database.
        /// This effectively resets the appearance before applying those
        /// characteristics supplied in the request.
        /// </summary>

        private void DoExtend(AppearanceRequestData rdata)
        {

            bool  created  = false;
            bool  modified = false;
            string newnode = String.Empty;

            Rest.Log.DebugFormat("{0} POST ENTRY", MsgId);

            //AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);

            rdata.userAppearance = new AvatarAppearance();

            //  Although the following behavior is admitted by HTTP I am becoming 
            //  increasingly doubtful that it is appropriate for REST. If I attempt to
            //  add a new record, and it already exists, then it seems to me that the
            //  attempt should fail, rather than update the existing record.

            if (GetUserAppearance(rdata))
            {
                modified = rdata.userAppearance != null;
                created  = !modified;
                Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
            //    Rest.UserServices.UpdateUserProfile(rdata.userProfile);
            }
            else
            {
                created  = true;
                Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
             //   Rest.UserServices.UpdateUserProfile(rdata.userProfile);
            }

            if (created)
            {
                newnode = String.Format("{0} {1}", rdata.userProfile.FirstName,
                                   rdata.userProfile.SurName);
                // Must include a location header with a URI that identifies the new resource.

                rdata.AddHeader(Rest.HttpHeaderLocation,String.Format("http://{0}{1}:{2}{3}{4}",
                         rdata.hostname,rdata.port,rdata.path,Rest.UrlPathSeparator, newnode));
                rdata.Complete(Rest.HttpStatusCodeCreated);

            }
            else
            {
                if (modified)
                {
                    rdata.Complete(Rest.HttpStatusCodeOK);
                }
                else
                {
                    rdata.Complete(Rest.HttpStatusCodeNoContent);
                }
            }

            rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));
            
        }

        /// <summary>
        /// This updates the user's appearance. not all aspects need to be provided,
        /// only those supplied will be changed.
        /// </summary>

        private void DoUpdate(AppearanceRequestData rdata)
        {

            bool  created  = false;
            bool  modified = false;


            rdata.userAppearance = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);

            // If the user exists then this is considered a modification regardless
            // of what may, or may not be, specified in the payload.

            if (rdata.userAppearance != null)
            {
                modified = true;
                Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);
                Rest.UserServices.UpdateUserProfile(rdata.userProfile);
            }

            if (created)
            {
                rdata.Complete(Rest.HttpStatusCodeCreated);
            }
            else
            {
                if (modified)
                {
                    rdata.Complete(Rest.HttpStatusCodeOK);
                }
                else
                {
                    rdata.Complete(Rest.HttpStatusCodeNoContent);
                }
            }

            rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));

        }

        /// <summary>
        /// Delete the specified user's appearance. This actually performs a reset
        /// to the default avatar appearance, if the info is already there. 
        /// Existing ownership is preserved. All prior updates are lost and can not
        /// be recovered.
        /// </summary>

        private void DoDelete(AppearanceRequestData rdata)
        {

            AvatarAppearance old = Rest.AvatarServices.GetUserAppearance(rdata.userProfile.ID);

            if (old != null)
            {
                rdata.userAppearance = new AvatarAppearance();

                rdata.userAppearance.Owner = old.Owner;
                
                Rest.AvatarServices.UpdateUserAppearance(rdata.userProfile.ID, rdata.userAppearance);

                rdata.Complete();
            }
            else
            {
                rdata.Complete(Rest.HttpStatusCodeNoContent);
            }

            rdata.Respond(String.Format("Appearance {0} : Normal completion", rdata.method));

        }

#endregion method-specific processing

        private bool GetUserAppearance(AppearanceRequestData rdata)
        {

            XmlReader xml;
            bool indata = false;

            rdata.initXmlReader();
            xml     = rdata.reader;

            while (xml.Read())
            {
                switch (xml.NodeType)
                {
                    case XmlNodeType.Element :
                        switch (xml.Name)
                        {
                            case "Appearance" :
                                if (xml.MoveToAttribute("Height"))
                                {
                                    rdata.userAppearance.AvatarHeight = (float) Convert.ToDouble(xml.Value);
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Owner"))
                                {
                                    rdata.userAppearance.Owner = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Serial"))
                                {
                                    rdata.userAppearance.Serial = Convert.ToInt32(xml.Value);
                                    indata = true;
                                }
                            break;
                            case "Body" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.BodyItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.BodyAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Skin" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.SkinItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.SkinAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Hair" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.HairItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.HairAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Eyes" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.EyesItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.EyesAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Shirt" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.ShirtItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.ShirtAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Pants" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.PantsItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.PantsAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Shoes" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.ShoesItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.ShoesAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Socks" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.SocksItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.SocksAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Jacket" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.JacketItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.JacketAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Gloves" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.GlovesItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.GlovesAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "UnderShirt" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.UnderShirtItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.UnderShirtAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "UnderPants" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.UnderPantsItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.UnderPantsAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Skirt" :
                                if (xml.MoveToAttribute("Item"))
                                {
                                    rdata.userAppearance.SkirtItem = (UUID)xml.Value;
                                    indata = true;
                                }
                                if (xml.MoveToAttribute("Asset"))
                                {
                                    rdata.userAppearance.SkirtAsset = (UUID)xml.Value;
                                    indata = true;
                                }
                            break;
                            case "Attachment" :
                                {

                                    int  ap;
                                    UUID asset;
                                    UUID item;

                                    if (xml.MoveToAttribute("AtPoint"))
                                    {
                                        ap = Convert.ToInt32(xml.Value);
                                        if (xml.MoveToAttribute("Asset"))
                                        {
                                            asset = new UUID(xml.Value);
                                            if (xml.MoveToAttribute("Asset"))
                                            {
                                                item = new UUID(xml.Value);
                                                rdata.userAppearance.SetAttachment(ap, item, asset);
                                                indata = true;
                                            }
                                        }
                                    }
                                }
                            break;
                            case "Texture" :
                                if (xml.MoveToAttribute("Default"))
                                {
                                    rdata.userAppearance.Texture = new Primitive.TextureEntry(new UUID(xml.Value));
                                    indata = true;
                                }
                            break;
                            case "Face" :
                                {
                                    uint index;
                                    if (xml.MoveToAttribute("Index"))
                                    {
                                        index = Convert.ToUInt32(xml.Value);
                                        if (xml.MoveToAttribute("Id"))
                                        {
                                            rdata.userAppearance.Texture.CreateFace(index).TextureID = new UUID(xml.Value);
                                            indata = true;
                                        }
                                    }
                                }
                            break;
                            case "VisualParameters" :
                                {
                                    xml.ReadContentAsBase64(rdata.userAppearance.VisualParams,
                                                            0, rdata.userAppearance.VisualParams.Length);
                                    indata = true;
                                }
                            break;
                        } 
                    break;
                }
            }

            return indata;

        }

        private void FormatPart(AppearanceRequestData rdata, string part, UUID item, UUID asset)
        {
            if (item != UUID.Zero || asset != UUID.Zero)
            {
                rdata.writer.WriteStartElement(part);
                if (item  != UUID.Zero)
                {
                    rdata.writer.WriteAttributeString("Item",item.ToString());
                }

                if (asset != UUID.Zero)
                {
                    rdata.writer.WriteAttributeString("Asset",asset.ToString());
                }
                rdata.writer.WriteEndElement();
            }
        }

        private void FormatUserAppearance(AppearanceRequestData rdata)
        {

            Rest.Log.DebugFormat("{0} FormatUserAppearance", MsgId);

            if (rdata.userAppearance != null)
            {

                Rest.Log.DebugFormat("{0} FormatUserAppearance: appearance object exists", MsgId);
                rdata.writer.WriteStartElement("Appearance");

                rdata.writer.WriteAttributeString("Height", rdata.userAppearance.AvatarHeight.ToString());
                if (rdata.userAppearance.Owner != UUID.Zero)
                    rdata.writer.WriteAttributeString("Owner", rdata.userAppearance.Owner.ToString());
                rdata.writer.WriteAttributeString("Serial", rdata.userAppearance.Serial.ToString());

                FormatPart(rdata, "Body", rdata.userAppearance.BodyItem, rdata.userAppearance.BodyAsset);
                FormatPart(rdata, "Skin", rdata.userAppearance.SkinItem, rdata.userAppearance.SkinAsset);
                FormatPart(rdata, "Hair", rdata.userAppearance.HairItem, rdata.userAppearance.HairAsset);
                FormatPart(rdata, "Eyes", rdata.userAppearance.EyesItem, rdata.userAppearance.EyesAsset);

                FormatPart(rdata, "Shirt", rdata.userAppearance.ShirtItem, rdata.userAppearance.ShirtAsset);
                FormatPart(rdata, "Pants", rdata.userAppearance.PantsItem, rdata.userAppearance.PantsAsset);
                FormatPart(rdata, "Skirt", rdata.userAppearance.SkirtItem, rdata.userAppearance.SkirtAsset);
                FormatPart(rdata, "Shoes", rdata.userAppearance.ShoesItem, rdata.userAppearance.ShoesAsset);
                FormatPart(rdata, "Socks", rdata.userAppearance.SocksItem, rdata.userAppearance.SocksAsset);

                FormatPart(rdata, "Jacket", rdata.userAppearance.JacketItem, rdata.userAppearance.JacketAsset);
                FormatPart(rdata, "Gloves", rdata.userAppearance.GlovesItem, rdata.userAppearance.GlovesAsset);

                FormatPart(rdata, "UnderShirt", rdata.userAppearance.UnderShirtItem, rdata.userAppearance.UnderShirtAsset);
                FormatPart(rdata, "UnderPants", rdata.userAppearance.UnderPantsItem, rdata.userAppearance.UnderPantsAsset);

                Hashtable attachments = rdata.userAppearance.GetAttachments();

                if (attachments != null)
                {

                    Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting attachments", MsgId);

                    rdata.writer.WriteStartElement("Attachments");
                    for (int i = 0; i < attachments.Count; i++)
                    {
                        Hashtable attachment = attachments[i] as Hashtable;
                        rdata.writer.WriteStartElement("Attachment");
                        rdata.writer.WriteAttributeString("AtPoint", i.ToString());
                        rdata.writer.WriteAttributeString("Item", (string) attachment["item"]);
                        rdata.writer.WriteAttributeString("Asset", (string) attachment["asset"]);
                        rdata.writer.WriteEndElement();
                    }
                    rdata.writer.WriteEndElement();
                }

                Primitive.TextureEntry texture = rdata.userAppearance.Texture;

                if (texture != null && (texture.DefaultTexture != null || texture.FaceTextures != null))
                {
                    Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting textures", MsgId);

                    rdata.writer.WriteStartElement("Texture");

                    if (texture.DefaultTexture != null)
                    {
                        Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting default texture", MsgId);
                        rdata.writer.WriteAttributeString("Default",
                            texture.DefaultTexture.TextureID.ToString());
                    }

                    if (texture.FaceTextures != null)
                    {

                        Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting face textures", MsgId);

                        for (int i=0; i<texture.FaceTextures.Length;i++)
                        {
                            if (texture.FaceTextures[i] != null)
                            {
                                rdata.writer.WriteStartElement("Face");
                                rdata.writer.WriteAttributeString("Index", i.ToString());
                                rdata.writer.WriteAttributeString("Id",
                                        texture.FaceTextures[i].TextureID.ToString());
                                rdata.writer.WriteEndElement();
                            }
                        }
                    }

                    rdata.writer.WriteEndElement();
                }

                Rest.Log.DebugFormat("{0} FormatUserAppearance: Formatting visual parameters", MsgId);

                rdata.writer.WriteStartElement("VisualParameters");
                rdata.writer.WriteBase64(rdata.userAppearance.VisualParams,0,
                            rdata.userAppearance.VisualParams.Length);
                rdata.writer.WriteEndElement();
                rdata.writer.WriteFullEndElement();
            }

            Rest.Log.DebugFormat("{0} FormatUserAppearance: completed", MsgId);

            return;
        }

        #region appearance RequestData extension

        internal class AppearanceRequestData : RequestData
        {

            /// <summary>
            /// These are the inventory specific request/response state
            /// extensions.
            /// </summary>

            internal UUID                       uuid = UUID.Zero;
            internal UserProfileData     userProfile = null;
            internal AvatarAppearance userAppearance = null;

            internal AppearanceRequestData(OSHttpRequest request, OSHttpResponse response, string prefix)
                : base(request, response, prefix)
            {
            }

        }

        #endregion Appearance RequestData extension

    }
}