#region Copyright(c) Travis Robinson
// Copyright (c) 2012 Travis Robinson <libusbdotnet@gmail.com>
// All rights reserved.
// 
// TestParameters.cs
// 
// Created:      03.05.2012
// Last Updated: 03.05.2012
// 
// 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.
// 	  
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 TRAVIS LEE ROBINSON 
// 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.
#endregion


using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Win32;


namespace libusbK.Examples
{
    internal abstract class TestParameters
    {
        protected TestParameters(int vid,
                                 int pid,
                                 int altInterfaceId,
                                 int pipeId,
                                 int maxTransfersTotal,
                                 string logFilename)
        {
            MI = -1;
            ;
            Vid = vid;
            Pid = pid;
            PipeId = pipeId;
            AltInterfaceId = altInterfaceId;
            MaxTransfersTotal = maxTransfersTotal;
            LogFilename = logFilename;
        }


        #region Public Members
        public int AltInterfaceId;

        public bool ConfigureDevice(out WINUSB_PIPE_INFORMATION_EX pipeInfo,
                                    out UsbK usb,
                                    out USB_INTERFACE_DESCRIPTOR interfaceDescriptor)
        {
            bool success;
            Console.WriteLine("Finding usb device by VID/PID.. ({0:X4}h / {1:X4}h)",
                              Vid,
                              Pid);

            // Use a patttern match to include only matching devices.
            // NOTE: You can use the '*' and '?' chars as wildcards for all chars or a single char (respectively). 
            KLST_PATTERN_MATCH patternMatch = new KLST_PATTERN_MATCH();
            if (MI != -1)
                patternMatch.DeviceID = String.Format("USB\\VID_{0:X4}&PID_{1:X4}&MI_{2:X2}*",
                                                        Vid,
                                                        Pid,
                                                        MI);
            else
                patternMatch.DeviceID = String.Format("USB\\VID_{0:X4}&PID_{1:X4}*",
                                                        Vid,
                                                        Pid);

            LstK deviceList = new LstK(KLST_FLAG.NONE,
                                       ref patternMatch);
            KLST_DEVINFO_HANDLE deviceInfo;
            interfaceDescriptor = new USB_INTERFACE_DESCRIPTOR();
            pipeInfo = new WINUSB_PIPE_INFORMATION_EX();
            usb = null;

            // Iterate the devices looking for a matching alt-interface and endpoint id.
            while (deviceList.MoveNext(out deviceInfo))
            {
                // libusbK class contructors can throw exceptions; For instance, if the device is
                // using the WinUsb driver and already in-use by another application.
                Console.WriteLine("Opening usb device..");
                usb = new UsbK(deviceInfo);

                Console.WriteLine("Finding interface and endpoint by PipeId..");
                success = FindPipeAndInterface(usb,
                                               out interfaceDescriptor,
                                               out pipeInfo);
                if (success) break;

                Console.WriteLine("PipeId {0:X2}h not found on this device.",
                                  PipeId);
                usb.Free();
                usb = null;
            }
            if (ReferenceEquals(usb,
                                null))
            {
                Console.WriteLine("Usb device not found: {0}",
                                  patternMatch.DeviceID);
                success = false;
                goto Done;
            }

            MI = interfaceDescriptor.bInterfaceNumber;
            AltInterfaceId = interfaceDescriptor.bAlternateSetting;
            PipeId = pipeInfo.PipeId;

            // Set interface alt setting.
            Console.WriteLine("Setting interface #{0} to bAlternateSetting #{1}..",
                              interfaceDescriptor.bInterfaceNumber,
                              interfaceDescriptor.bAlternateSetting);
            success = usb.SetAltInterface(interfaceDescriptor.bInterfaceNumber,
                                          false,
                                          interfaceDescriptor.bAlternateSetting);
            if (!success)
            {
                Console.WriteLine("SetAltInterface failed. ErrorCode={0:X8}h",
                                  Marshal.GetLastWin32Error());
                Console.WriteLine(interfaceDescriptor.ToString());
            }

            Done:
            deviceList.Free();

            return success;
        }

        public HiPerfTimer Dcs = new HiPerfTimer();

        public void FillFromCommandLine(string commandLine)
        {
            commandLine = Regex.Replace(commandLine,
                                        "^(\".+?\"|\\S+)\\s*",
                                        "");
            string exp = String.Empty;
            exp += makeArgExpression("Vid",
                                     NumberStyles.HexNumber) + "|";
            exp += makeArgExpression("Pid",
                                     NumberStyles.HexNumber) + "|";
            exp += makeArgExpression("PipeId",
                                     NumberStyles.HexNumber) + "|";
            exp += makeArgExpression("AltId",
                                     NumberStyles.Integer) + "|";
            exp += makeArgExpression("MI",
                                     NumberStyles.Integer) + "|";
            exp += makeArgExpression("Log",
                                     NumberStyles.None);

            m_CommandArguments = Regex.Matches(commandLine,
                                               exp,
                                               RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase |
                                               RegexOptions.IgnorePatternWhitespace);

            foreach (Match m in m_CommandArguments)
            {
                Group gVid = m.Groups["Vid"];
                Group gPid = m.Groups["Pid"];
                Group gMI = m.Groups["MI"];
                Group gPipeId = m.Groups["PipeId"];
                Group gAltId = m.Groups["AltId"];
                Group gLog = m.Groups["Log"];
                Group gIsHex = m.Groups["IsHex"];

                if (gVid.Success)
                    Vid = gIsHex.Success
                              ? UInt16.Parse(gVid.Value,
                                             NumberStyles.HexNumber)
                              : UInt16.Parse(gVid.Value);
                else if (gPid.Success)
                    Pid = gIsHex.Success
                              ? UInt16.Parse(gPid.Value,
                                             NumberStyles.HexNumber)
                              : UInt16.Parse(gPid.Value);
                else if (gMI.Success)
                    MI = gIsHex.Success
                             ? UInt16.Parse(gMI.Value,
                                            NumberStyles.HexNumber)
                             : UInt16.Parse(gMI.Value);
                else if (gPipeId.Success)
                    PipeId = gIsHex.Success
                                 ? Byte.Parse(gPipeId.Value,
                                              NumberStyles.HexNumber)
                                 : Byte.Parse(gPipeId.Value);
                else if (gAltId.Success)
                    AltInterfaceId = gIsHex.Success
                                         ? Byte.Parse(gAltId.Value,
                                                      NumberStyles.HexNumber)
                                         : Byte.Parse(gAltId.Value);
                else if (gLog.Success)
                    LogFilename = gLog.Value;
                else
                {
                    throw new Exception("Invalid argument:" + m.Value);
                }
            }
        }

        public bool FindPipeAndInterface(UsbK usb,
                                         out USB_INTERFACE_DESCRIPTOR interfaceDescriptor,
                                         out WINUSB_PIPE_INFORMATION_EX pipeInfo)
        {
            if (FindPipeAndInterface(usb,
                                     out interfaceDescriptor,
                                     out pipeInfo,
                                     AltInterfaceId,
                                     PipeId))
            {
                AltInterfaceId = interfaceDescriptor.bAlternateSetting;
                PipeId = pipeInfo.PipeId;

                return true;
            }
            return false;
        }

        public static bool FindPipeAndInterface(UsbK usb,
                                                out USB_INTERFACE_DESCRIPTOR interfaceDescriptor,
                                                out WINUSB_PIPE_INFORMATION_EX pipeInfo,
                                                int altInterfaceId,
                                                int pipeId)
        {
            byte interfaceIndex = 0;
            interfaceDescriptor = new USB_INTERFACE_DESCRIPTOR();
            pipeInfo = new WINUSB_PIPE_INFORMATION_EX();
            while (usb.SelectInterface(interfaceIndex,
                                       true))
            {
                byte altSettingNumber = 0;
                while (usb.QueryInterfaceSettings(altSettingNumber,
                                                  out interfaceDescriptor))
                {
                    if (altInterfaceId == -1 || altInterfaceId == altSettingNumber)
                    {
                        byte pipeIndex = 0;
                        while (usb.QueryPipeEx(altSettingNumber,
                                             pipeIndex++,
                                             out pipeInfo))
                        {
                            if ((pipeInfo.MaximumPacketSize > 0) &&
                                pipeId == -1 || pipeInfo.PipeId == pipeId ||
                                ((pipeId & 0xF) == 0 && ((pipeId & 0x80) == (pipeInfo.PipeId & 0x80))))
                            {
                                goto FindInterfaceDone;
                            }
                            pipeInfo.PipeId = 0;
                        }
                    }
                    altSettingNumber++;
                }
                interfaceIndex++;
            }

            FindInterfaceDone:
            return pipeInfo.PipeId != 0;
        }

        public void Free()
        {
            if (fileLoggingEnabled && logStream != null)
            {
                logStream.Flush();
                logStream.Close();
                logStream = null;
            }
        }

        public void Init()
        {
            if (!String.IsNullOrEmpty(LogFilename))
            {
                logStream = new StreamWriter(LogFilename);
                fileLoggingEnabled = true;
            }
        }

        public void Log(String text)
        {
            if (fileLoggingEnabled) logStream.Write(text);
            else Console.Write(text);
        }

        public void Log(String format,
                        params object[] args)
        {
            if (fileLoggingEnabled)
                logStream.Write(format,
                                args);
            else
                Console.Write(format,
                              args);
        }

        public string LogFilename;

        public void LogLn(String text)
        {
            if (fileLoggingEnabled) logStream.WriteLine(text);
            else Console.WriteLine(text);
        }

        public void LogLn(String format,
                          params object[] args)
        {
            if (fileLoggingEnabled)
                logStream.WriteLine(format,
                                    args);
            else
                Console.WriteLine(format,
                                  args);
        }

        public int MI;
        public int MaxTransfersTotal;
        public int Pid;
        public int PipeId;

        public bool ShowTestReady()
        {
            while (Console.KeyAvailable)
                Console.ReadKey(true);

            Console.WriteLine("Test ready:");
            Console.Write(ToString());

            if (!String.IsNullOrEmpty(LogFilename))
                Console.WriteLine("Logging output to file: {0}",
                                  LogFilename);

            Console.WriteLine("Press 'Q' to abort or any other key to continue..");
            ConsoleKeyInfo key = Console.ReadKey(true);
            if (key.KeyChar == 'q' || key.KeyChar == 'Q') return false;

            Console.WriteLine();
            Console.WriteLine("Starting test..");
            Console.WriteLine();

            return true;
        }

        public override string ToString()
        {
            return
                String.Format(
                    "Vid: {0:X4}h\nPid: {1:X4}h\nMI: {2:X2}h\nPipeId: {3:X2}h\nMaxTransfersTotal: {4}\n\nLogFilename: {5}\n",
                    Vid,
                    Pid,
                    MI,
                    PipeId,
                    MaxTransfersTotal,
                    LogFilename);
        }

        public int Vid;
        #endregion


        #region Private Members
        private bool fileLoggingEnabled;

        private StreamWriter logStream;
        private MatchCollection m_CommandArguments;

        private string makeArgExpression(string argName,
                                         NumberStyles argType)
        {
            const string argsep = @"\W\s*";

            switch (argType)
            {
                case NumberStyles.None:
                    return String.Format(@"(({0}{1}(?<{0}>{2}))\s*)",
                                         argName,
                                         argsep,
                                         "(\".+?\"|\\S+)");
                case NumberStyles.Integer:
                case NumberStyles.HexNumber:
                    return String.Format(@"(({0}{1}(?<IsHex>0x)?(?<{0}>[0-9A-F]+))(?<IsHex>h)?\s*)",
                                         argName,
                                         argsep);
                default:
                    throw new Exception();
            }
        }
        #endregion
    }

    internal class IsoTestParameters : TestParameters
    {
        #region Public Members
        public IsoTestParameters(int vid,
                                 int pid,
                                 int altInterfaceId,
                                 int pipeId,
                                 int maxOutstandingTransfers,
                                 int maxTransfersTotal,
                                 string logFilename,
                                 uint isoPacketsPerTransfer)
            : base(vid,
                   pid,
                   altInterfaceId,
                   pipeId,
                   maxTransfersTotal,
                   logFilename)
        {
            IsoPacketsPerTransfer = isoPacketsPerTransfer;
            MaxOutstandingTransfers = maxOutstandingTransfers;

            FillFromCommandLine(Environment.CommandLine);
            Init();
        }

        public uint IsoPacketsPerTransfer;
        public int MaxOutstandingTransfers;

        public override string ToString()
        {
            string fmt = String.Empty;
            fmt += "IsoPacketsPerTransfer: {0}\n";
            fmt += "MaxOutstandingTransfers: {1}\n";

            object[] args = new object[]
                                {
                                    IsoPacketsPerTransfer, MaxOutstandingTransfers
                                };
            return base.ToString() + string.Format(fmt,
                                                   args);
        }
        #endregion
    }

    internal class StmTestParameters : TestParameters
    {
        #region Public Members
        public StmTestParameters(int vid,
                                 int pid,
                                 int altInterfaceId,
                                 byte pipeId,
                                 int maxTransfersTotal,
                                 string logFilename,
                                 int transferBufferSize,
                                 int maxPendingIO,
                                 int maxPendingTransfers)
            : base(vid,
                   pid,
                   altInterfaceId,
                   pipeId,
                   maxTransfersTotal,
                   logFilename)
        {
            TransferBufferSize = transferBufferSize;
            MaxPendingIO = maxPendingIO;
            MaxPendingTransfers = maxPendingTransfers;

            FillFromCommandLine(Environment.CommandLine);
            Init();
        }

        public int MaxPendingIO;
        public int MaxPendingTransfers;

        public override string ToString()
        {
            string fmt = String.Empty;
            fmt += "TransferBufferSize: {0}\n";
            fmt += "MaxPendingIO: {1}\n";
            fmt += "MaxPendingTransfers: {2}\n";

            object[] args = new object[]
                                {
                                    TransferBufferSize, MaxPendingIO, MaxPendingTransfers
                                };
            return base.ToString() + string.Format(fmt,
                                                   args);
        }

        public int TransferBufferSize;
        #endregion
    }

    internal class AsyncTestParameters : TestParameters
    {
        #region Public Members
        public AsyncTestParameters(int vid,
                                 int pid,
                                 int altInterfaceId,
                                 byte pipeId,
                                 int maxTransfersTotal,
                                 string logFilename,
                                 int transferBufferSize,
                                 int maxPendingIO)
            : base(vid,
                   pid,
                   altInterfaceId,
                   pipeId,
                   maxTransfersTotal,
                   logFilename)
        {
            TransferBufferSize = transferBufferSize;
            MaxPendingIO = maxPendingIO;

            FillFromCommandLine(Environment.CommandLine);
            Init();
        }

        public int MaxPendingIO;

        public override string ToString()
        {
            string fmt = String.Empty;
            fmt += "TransferBufferSize: {0}\n";
            fmt += "MaxPendingIO: {1}\n";

            object[] args = new object[]
                                {
                                    TransferBufferSize, MaxPendingIO
                                };
            return base.ToString() + string.Format(fmt,
                                                   args);
        }

        public int TransferBufferSize;
        #endregion
    }
}