/* Copyright (C) 2014-2018 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
 * 
 * You can redistribute this program and/or modify it under the terms of
 * the GNU Lesser Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 */
using System;
using System.Collections.Generic;
using System.Text;
using Utilities;

namespace SMBLibrary.Services
{
    /// <summary>
    /// [MS-SRVS]
    /// </summary>
    public class ServerService : RemoteService
    {
        public const string ServicePipeName = @"srvsvc";
        public static readonly Guid ServiceInterfaceGuid = new Guid("4B324FC8-1670-01D3-1278-5A47BF6EE188");
        public const int ServiceVersion = 3;

        public const int MaxPreferredLength = -1; // MAX_PREFERRED_LENGTH

        private PlatformName m_platformID;
        private string m_serverName;
        private uint m_verMajor;
        private uint m_verMinor;
        private ServerType m_serverType;

        private List<string> m_shares;

        public ServerService(string serverName, List<string> shares)
        {
            m_platformID = PlatformName.NT;
            m_serverName = serverName;
            m_verMajor = 5;
            m_verMinor = 2;
            m_serverType = ServerType.Workstation | ServerType.Server | ServerType.WindowsNT | ServerType.ServerNT | ServerType.MasterBrowser;

            m_shares = shares;
        }

        public override byte[] GetResponseBytes(ushort opNum, byte[] requestBytes)
        {
            switch ((ServerServiceOpName)opNum)
            {
                case ServerServiceOpName.NetrShareEnum:
                    {
                        NetrShareEnumRequest request = new NetrShareEnumRequest(requestBytes);
                        NetrShareEnumResponse response = GetNetrShareEnumResponse(request);
                        return response.GetBytes();
                    }
                case ServerServiceOpName.NetrShareGetInfo:
                    {
                        NetrShareGetInfoRequest request = new NetrShareGetInfoRequest(requestBytes);
                        NetrShareGetInfoResponse response = GetNetrShareGetInfoResponse(request);
                        return response.GetBytes();
                    }
                case ServerServiceOpName.NetrServerGetInfo:
                    {
                        NetrServerGetInfoRequest request = new NetrServerGetInfoRequest(requestBytes);
                        NetrServerGetInfoResponse response = GetNetrWkstaGetInfoResponse(request);
                        return response.GetBytes();
                    }
                default:
                    throw new UnsupportedOpNumException();
            }
        }

        public NetrShareEnumResponse GetNetrShareEnumResponse(NetrShareEnumRequest request)
        {
            NetrShareEnumResponse response = new NetrShareEnumResponse();
            switch (request.InfoStruct.Level)
            {
                case 0:
                    {
                        // We ignore request.PreferedMaximumLength
                        ShareInfo0Container info = new ShareInfo0Container();
                        foreach (string shareName in m_shares)
                        {
                            info.Add(new ShareInfo0Entry(shareName));
                        }
                        response.InfoStruct = new ShareEnum(info);
                        response.TotalEntries = (uint)m_shares.Count;
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 1:
                    {
                        // We ignore request.PreferedMaximumLength
                        ShareInfo1Container info = new ShareInfo1Container();
                        foreach (string shareName in m_shares)
                        {
                            info.Add(new ShareInfo1Entry(shareName, new ShareTypeExtended(ShareType.DiskDrive)));
                        }
                        response.InfoStruct = new ShareEnum(info);
                        response.TotalEntries = (uint)m_shares.Count;
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 2:
                case 501:
                case 502:
                case 503:
                    {
                        response.InfoStruct = new ShareEnum(request.InfoStruct.Level);
                        response.Result = Win32Error.ERROR_NOT_SUPPORTED;
                        return response;
                    }
                default:
                    {
                        response.InfoStruct = new ShareEnum(request.InfoStruct.Level);
                        response.Result = Win32Error.ERROR_INVALID_LEVEL;
                        return response;
                    }
            }
        }

        public NetrShareGetInfoResponse GetNetrShareGetInfoResponse(NetrShareGetInfoRequest request)
        {
            int shareIndex = IndexOfShare(request.NetName);
            
            NetrShareGetInfoResponse response = new NetrShareGetInfoResponse();
            if (shareIndex == -1)
            {
                response.InfoStruct = new ShareInfo(request.Level);
                response.Result = Win32Error.NERR_NetNameNotFound;
                return response;
            }

            switch (request.Level)
            {
                case 0:
                    {
                        ShareInfo0Entry info = new ShareInfo0Entry(m_shares[shareIndex]);
                        response.InfoStruct = new ShareInfo(info);
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 1:
                    {
                        ShareInfo1Entry info = new ShareInfo1Entry(m_shares[shareIndex], new ShareTypeExtended(ShareType.DiskDrive));
                        response.InfoStruct = new ShareInfo(info);
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 2:
                    {
                        ShareInfo2Entry info = new ShareInfo2Entry(m_shares[shareIndex], new ShareTypeExtended(ShareType.DiskDrive));
                        response.InfoStruct = new ShareInfo(info);
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 501:
                case 502:
                case 503:
                case 1005:
                    {
                        response.InfoStruct = new ShareInfo(request.Level);
                        response.Result = Win32Error.ERROR_NOT_SUPPORTED;
                        return response;
                    }
                default:
                    {
                        response.InfoStruct = new ShareInfo(request.Level);
                        response.Result = Win32Error.ERROR_INVALID_LEVEL;
                        return response;
                    }
            }
        }

        public NetrServerGetInfoResponse GetNetrWkstaGetInfoResponse(NetrServerGetInfoRequest request)
        {
            NetrServerGetInfoResponse response = new NetrServerGetInfoResponse();
            switch (request.Level)
            {
                case 100:
                    {
                        ServerInfo100 info = new ServerInfo100();
                        info.PlatformID = m_platformID;
                        info.ServerName.Value = m_serverName;
                        response.InfoStruct = new ServerInfo(info);
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 101:
                    {
                        ServerInfo101 info = new ServerInfo101();
                        info.PlatformID = m_platformID;
                        info.ServerName.Value = m_serverName;
                        info.VerMajor = m_verMajor;
                        info.VerMinor = m_verMinor;
                        info.Type = m_serverType;
                        info.Comment.Value = String.Empty;
                        response.InfoStruct = new ServerInfo(info);
                        response.Result = Win32Error.ERROR_SUCCESS;
                        return response;
                    }
                case 102:
                case 103:
                case 502:
                case 503:
                    {
                        response.InfoStruct = new ServerInfo(request.Level);
                        response.Result = Win32Error.ERROR_NOT_SUPPORTED;
                        return response;
                    }
                default:
                    {
                        response.InfoStruct = new ServerInfo(request.Level);
                        response.Result = Win32Error.ERROR_INVALID_LEVEL;
                        return response;
                    }
            }
        }

        private int IndexOfShare(string shareName)
        {
            for (int index = 0; index < m_shares.Count; index++)
            {
                if (m_shares[index].Equals(shareName, StringComparison.OrdinalIgnoreCase))
                {
                    return index;
                }
            }

            return -1;
        }

        public override Guid InterfaceGuid
        {
            get
            {
                return ServiceInterfaceGuid;
            }
        }

        public override string PipeName
        {
            get
            {
                return ServicePipeName;
            }
        }
    }
}