using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;

namespace WinSCP
{
    [Guid("70253534-C5DC-4EF3-9C98-65C57D79C324")]
    [ClassInterface(Constants.ClassInterface)]
    [ComVisible(true)]
    public sealed class RemotePath : IReflect
    {
        public static string EscapeFileMask(string fileMask)
        {
            if (fileMask == null)
            {
                throw new ArgumentNullException(nameof(fileMask));
            }
            int lastSlash = fileMask.LastIndexOf('/');
            string path = lastSlash > 0 ? fileMask.Substring(0, lastSlash + 1) : string.Empty;
            string mask = lastSlash > 0 ? fileMask.Substring(lastSlash + 1) : fileMask;
            // Keep in sync with EscapeFileMask in GenerateUrl.cpp
            mask = mask.Replace("[", "[[]").Replace("*", "[*]").Replace("?", "[?]").Replace("<", "<<").Replace(">", ">>");
            return path + mask;
        }

        public static string EscapeOperationMask(string operationMask)
        {
            if (operationMask == null)
            {
                throw new ArgumentNullException(nameof(operationMask));
            }
            int lastSlash = operationMask.LastIndexOf('/');
            string path = lastSlash > 0 ? operationMask.Substring(0, lastSlash + 1) : string.Empty;
            string mask = lastSlash > 0 ? operationMask.Substring(lastSlash + 1) : operationMask;
            mask = mask.Replace("\\", "\\\\").Replace("*", "\\*").Replace("?", "\\?");
            return path + mask;
        }

        [Obsolete("Use RemotePath.Combine method")]
        public static string CombinePaths(string path1, string path2)
        {
            return Combine(path1, path2);
        }

        public static string Combine(string path1, string path2)
        {
            if (path1 == null)
            {
                throw new ArgumentNullException(nameof(path1));
            }

            if (path2 == null)
            {
                throw new ArgumentNullException(nameof(path2));
            }

            string result;

            if (path2.StartsWith("/", StringComparison.Ordinal))
            {
                result = path2;
            }
            else
            {
                result =
                    path1 +
                    ((path1.Length == 0) || (path2.Length == 0) || path1.EndsWith("/", StringComparison.Ordinal) ? string.Empty : "/") +
                    path2;
            }
            return result;
        }

        public static string TranslateRemotePathToLocal(string remotePath, string remoteRoot, string localRoot)
        {
            if (remotePath == null)
            {
                throw new ArgumentNullException(nameof(remotePath));
            }

            if (remoteRoot == null)
            {
                throw new ArgumentNullException(nameof(remoteRoot));
            }

            if (localRoot == null)
            {
                throw new ArgumentNullException(nameof(localRoot));
            }

            localRoot = AddSeparator(localRoot, @"\");
            remoteRoot = AddSeparator(remoteRoot, "/");

            string localPath;
            // special case
            if (AddSeparator(remotePath, "/") == remoteRoot)
            {
                localPath = localRoot;
            }
            else
            {
                if (!remotePath.StartsWith(remoteRoot, StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "{0} does not start with {1}", remotePath, remoteRoot));
                }

                string subPath = remotePath.Substring(remoteRoot.Length);
                // can happen only when remoteRoot is empty
                if (subPath.StartsWith("/", StringComparison.Ordinal))
                {
                    subPath = subPath.Substring(1);
                }
                subPath = subPath.Replace('/', '\\');
                localPath = localRoot + subPath;
            }
            return localPath;
        }

        private static string AddSeparator(string path, string separator)
        {
            // not adding to empty root paths, because the path may not even start with slash
            if ((path.Length > 0) && !path.EndsWith(separator, StringComparison.Ordinal))
            {
                path += separator;
            }
            return path;
        }

        public static string TranslateLocalPathToRemote(string localPath, string localRoot, string remoteRoot)
        {
            if (localPath == null)
            {
                throw new ArgumentNullException(nameof(localPath));
            }

            if (localRoot == null)
            {
                throw new ArgumentNullException(nameof(localRoot));
            }

            if (remoteRoot == null)
            {
                throw new ArgumentNullException(nameof(remoteRoot));
            }

            localRoot = AddSeparator(localRoot, @"\");
            remoteRoot = AddSeparator(remoteRoot, "/");

            string remotePath;
            // special case
            if (AddSeparator(localPath, @"\") == localRoot)
            {
                remotePath = remoteRoot;
            }
            else
            {
                if (!localPath.StartsWith(localRoot, StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "{0} does not start with {1}", localPath, localRoot));
                }

                string subPath = localPath.Substring(localRoot.Length);
                // can happen only when localRoot is empty
                if (subPath.StartsWith("\\", StringComparison.Ordinal))
                {
                    subPath = subPath.Substring(1);
                }
                subPath = subPath.Replace('\\', '/');
                remotePath = remoteRoot + subPath;
            }
            return remotePath;
        }

        public static string GetDirectoryName(string path)
        {
            string result;
            if (path == null)
            {
                result = null;
            }
            else if (path.Length == 0)
            {
                throw new ArgumentException("Path cannot be empty", nameof(path));
            }
            else
            {
                int i = path.LastIndexOf('/');
                if (i < 0)
                {
                    result = string.Empty;
                }
                else if (i == 0)
                {
                    if (path.Length == 1)
                    {
                        result = null;
                    }
                    else
                    {
                        result = "/";
                    }
                }
                else
                {
                    result = path.Substring(0, i);
                }
            }
            return result;
        }

        public static string AddDirectorySeparator(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException("Path cannot be empty", nameof(path));
            }

            if (!path.EndsWith("/", StringComparison.Ordinal))
            {
                path += "/";
            }

            return path;
        }

        public static string GetFileName(string path)
        {
            string result;
            if (string.IsNullOrEmpty(path))
            {
                result = null;
            }
            else
            {
                int i = path.LastIndexOf('/');
                if (i >= 0)
                {
                    result = path.Substring(i + 1);
                }
                else
                {
                    result = path;
                }
            }
            return result;
        }

        FieldInfo IReflect.GetField(string name, BindingFlags bindingAttr)
        {
            return GetType().GetField(name, bindingAttr);
        }

        FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr)
        {
            return GetType().GetFields(bindingAttr);
        }

        MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr)
        {
            return GetType().GetMember(name, bindingAttr);
        }

        MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr)
        {
            return GetType().GetMembers(bindingAttr);
        }

        MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr)
        {
            return GetType().GetMethod(name, bindingAttr);
        }

        MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
        {
            return GetType().GetMethod(name, bindingAttr, binder, types, modifiers);
        }

        MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr)
        {
            return GetType().GetMethods(bindingAttr);
        }

        PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr)
        {
            return GetType().GetProperties(bindingAttr);
        }

        PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
        {
            return GetType().GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
        }

        PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr)
        {
            return GetType().GetProperty(name, bindingAttr);
        }

        object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }

            Type type = target.GetType();

            // This trivial implementation allows calling static methods over COM
            return type.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
        }

        Type IReflect.UnderlyingSystemType
        {
            get { return GetType(); }
        }
    }
}
