namespace Nwc.XmlRpc
{
    using System;
    using System.Collections;
    using System.Reflection;

    /// <summary> XML-RPC System object implementation of extended specifications.</summary>
    [XmlRpcExposed]
    public class XmlRpcSystemObject
    {
        private XmlRpcServer _server;
        static private IDictionary _methodHelp = new Hashtable();

        /// <summary>Static <c>IDictionary</c> to hold mappings of method name to associated documentation String</summary>
        static public IDictionary MethodHelp
        {
            get { return _methodHelp; }
        }

        /// <summary>Constructor.</summary>
        /// <param name="server"><c>XmlRpcServer</c> server to be the system object for.</param>
        public XmlRpcSystemObject(XmlRpcServer server)
        {
            _server = server;
            server.Add("system", this);
            _methodHelp.Add(this.GetType().FullName + ".methodHelp", "Return a string description.");
        }

        /// <summary>Invoke a method on a given object.</summary>
        /// <remarks>Using reflection, and respecting the <c>XmlRpcExposed</c> attribute,
        /// invoke the <paramref>methodName</paramref> method on the <paramref>target</paramref>
        /// instance with the <paramref>parameters</paramref> provided. All this packages other <c>Invoke</c> methods 
        /// end up calling this.</remarks>
        /// <returns><c>Object</c> the value the invoked method returns.</returns>
        /// <exception cref="XmlRpcException">If method does not exist, is not exposed, parameters invalid, or invocation
        /// results in an exception. Note, the <c>XmlRpcException.Code</c> will indicate cause.</exception>
        static public Object Invoke(Object target, String methodName, IList parameters)
        {
            if (target == null)
                throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD,
                              XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Invalid target object.");

            Type type = target.GetType();
            MethodInfo method = type.GetMethod(methodName);

            try
            {
                if (!XmlRpcExposedAttribute.ExposedMethod(target, methodName))
                    throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD,
                              XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Method " + methodName + " is not exposed.");
            }
            catch (MissingMethodException me)
            {
                throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD,
                              XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": " + me.Message);
            }

            Object[] args = new Object[parameters.Count];

            int index = 0;
            foreach (Object arg in parameters)
            {
                args[index] = arg;
                index++;
            }

            try
            {
                Object retValue = method.Invoke(target, args);
                if (retValue == null)
                    throw new XmlRpcException(XmlRpcErrorCodes.APPLICATION_ERROR,
                              XmlRpcErrorCodes.APPLICATION_ERROR_MSG + ": Method returned NULL.");
                return retValue;
            }
            catch (XmlRpcException e)
            {
                throw e;
            }
            catch (ArgumentException ae)
            {
                Logger.WriteEntry(XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": " + ae.Message,
                          LogLevel.Information);
                String call = methodName + "( ";
                foreach (Object o in args)
                {
                    call += o.GetType().Name;
                    call += " ";
                }
                call += ")";
                throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_PARAMS,
                              XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": Arguement type mismatch invoking " + call);
            }
            catch (TargetParameterCountException tpce)
            {
                Logger.WriteEntry(XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": " + tpce.Message,
                          LogLevel.Information);
                throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_PARAMS,
                              XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": Arguement count mismatch invoking " + methodName);
            }
            catch (TargetInvocationException tie)
            {
                throw new XmlRpcException(XmlRpcErrorCodes.APPLICATION_ERROR,
                              XmlRpcErrorCodes.APPLICATION_ERROR_MSG + " Invoked method " + methodName + ": " + tie.Message);
            }
        }

        /// <summary>List methods available on all handlers of this server.</summary>
        /// <returns><c>IList</c> An array of <c>Strings</c>, each <c>String</c> will have form "object.method".</returns>
        [XmlRpcExposed]
        public IList listMethods()
        {
            IList methods = new ArrayList();
            Boolean considerExposure;

            foreach (DictionaryEntry handlerEntry in _server)
            {
                considerExposure = XmlRpcExposedAttribute.IsExposed(handlerEntry.Value.GetType());

                foreach (MemberInfo mi in handlerEntry.Value.GetType().GetMembers())
                {
                    if (mi.MemberType != MemberTypes.Method)
                        continue;

                    if (!((MethodInfo)mi).IsPublic)
                        continue;

                    if (considerExposure && !XmlRpcExposedAttribute.IsExposed(mi))
                        continue;

                    methods.Add(handlerEntry.Key + "." + mi.Name);
                }
            }

            return methods;
        }

        /// <summary>Given a method name return the possible signatures for it.</summary>
        /// <param name="name"><c>String</c> The object.method name to look up.</param>
        /// <returns><c>IList</c> Of arrays of signatures.</returns>
        [XmlRpcExposed]
        public IList methodSignature(String name)
        {
            IList signatures = new ArrayList();
            int index = name.IndexOf('.');

            if (index < 0)
                return signatures;

            String oName = name.Substring(0, index);
            Object obj = _server[oName];

            if (obj == null)
                return signatures;

            MemberInfo[] mi = obj.GetType().GetMember(name.Substring(index + 1));

            if (mi == null || mi.Length != 1) // for now we want a single signature
                return signatures;

            MethodInfo method;

            try
            {
                method = (MethodInfo)mi[0];
            }
            catch (Exception e)
            {
                Logger.WriteEntry("Attempted methodSignature call on " + mi[0] + " caused: " + e,
                          LogLevel.Information);
                return signatures;
            }

            if (!method.IsPublic)
                return signatures;

            IList signature = new ArrayList();
            signature.Add(method.ReturnType.Name);

            foreach (ParameterInfo param in method.GetParameters())
            {
                signature.Add(param.ParameterType.Name);
            }


            signatures.Add(signature);

            return signatures;
        }

        /// <summary>Help for given method signature. Not implemented yet.</summary>
        /// <param name="name"><c>String</c> The object.method name to look up.</param>
        /// <returns><c>String</c> help text. Rich HTML text.</returns>
        [XmlRpcExposed]
        public String methodHelp(String name)
        {
            String help = null;

            try
            {
                help = (String)_methodHelp[_server.MethodName(name)];
            }
            catch (XmlRpcException e)
            {
                throw e;
            }
            catch (Exception) { /* ignored */ };

            if (help == null)
                help = "No help available for: " + name;

            return help;
        }

        /// <summary>Boxcarring support method.</summary>
        /// <param name="calls"><c>IList</c> of calls</param>
        /// <returns><c>ArrayList</c> of results/faults.</returns>
        [XmlRpcExposed]
        public IList multiCall(IList calls)
        {
            IList responses = new ArrayList();
            XmlRpcResponse fault = new XmlRpcResponse();

            foreach (IDictionary call in calls)
            {
                try
                {
                    XmlRpcRequest req = new XmlRpcRequest((String)call[XmlRpcXmlTokens.METHOD_NAME],
                                          (ArrayList)call[XmlRpcXmlTokens.PARAMS]);
                    Object results = _server.Invoke(req);
                    IList response = new ArrayList();
                    response.Add(results);
                    responses.Add(response);
                }
                catch (XmlRpcException e)
                {
                    fault.SetFault(e.FaultCode, e.FaultString);
                    responses.Add(fault.Value);
                }
                catch (Exception e2)
                {
                    fault.SetFault(XmlRpcErrorCodes.APPLICATION_ERROR,
                               XmlRpcErrorCodes.APPLICATION_ERROR_MSG + ": " + e2.Message);
                    responses.Add(fault.Value);
                }
            }

            return responses;
        }

    }
}