/////////////////////////////////////
/////// Thread3DClientTool.cs
/////////////////////////////////////
/// 
/// Project „Thread3D“ 
/// 
/// 
/// $Author: franke, keller $: 
///
/// Copyright 2017, Physikalisch-Technische Bundesanstalt, Braunschweig & Berlin


using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Linq;
using tracimPTB_Thread3D;

using System.Text;
using System.Net;

namespace Thread3DClientTool
{
    /// <summary>
    /// This is an example class to mock a customers software under test. It should help to understand the usage of
    /// the TraCIM API.
    /// </summary>
    class ClientTool
    {

        private string dataDir = ".\\tracimTestClientData\\"; // "..\\..\\data/";
        private string BaseURL;
        public string TraCIMServer { get; set; }
        public string OrderKey { get; set; }
        public string ProcessKey { get; set; }
        public bool success = false;

        public static string proxyServer { get; set; }
        public static string proxyUser { get; set; }
        public static string proxyPassword { get; set; }

        public bool loadFreeData { get; set; }

        // version, vendor and customer information on tested software
        public string softwareName { get; set; }
        public string softwareVendor { get; set; }
        public string softwareVersion { get; set; }
        public string softwareRevision { get; set; }

        public decimal mpeLength { get; set; }
        public decimal mpeAngle { get; set; }
        public decimal mpeTaperOfPitchCone { get; set; }

        public string tracimTestMessageAsString;
        public tracimMessage tracimTestMessage;

        private tracimPTB_Thread3D.thread3DResultPackage TestResult;

        public string tracimResultAsXMLString;

        public string tracimValidationMessageAsString;
        public tracimMessage tracimValidationMessage;

        private DefaultResults DefaultResultsExampleData;


        public ClientTool()
        {
            // set values related to the used tracim-server and the API 
            BaseURL = "/tracim/api/";
            TraCIMServer = "https://tracim.ptb.de";
            

            OrderKey = ""; //[Enter your order ID here]";
            proxyServer = "";
            proxyUser = "";
            proxyPassword = "";

            // set values related to the software under test
            softwareName = ""; 
            softwareVendor = ""; 
            softwareVersion = ""; 
            softwareRevision = ""; 
            softwareRevision = ""; 

            mpeLength = 0.001m;
            mpeAngle = 0.01m;
            mpeTaperOfPitchCone = 0.01m;

            tracimTestMessageAsString = " ";

            // Test results for the free public test
            // This is needed to try the communication with the server, ect. without need of a Thread3D caculation software

            DefaultResultsExampleData = new DefaultResults();

            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
        }



        /// <summary>
        /// Load test data from xml file
        /// </summary>
        /// <returns>none</returns>
        //  public void loadTest()
        public void loadTest()
        {
            
            success = false;

            string testDataXMLString = " ";

            FileStream fs = null;
            Stream myStream = null;
            OpenFileDialog openFileDialog = new OpenFileDialog();

            openFileDialog.InitialDirectory = System.Windows.Forms.Application.StartupPath + dataDir;
            openFileDialog.Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 1;
            //openFileDialog1.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    using (myStream)
                    {
                        fs = File.OpenRead(openFileDialog.FileName);
                        testDataXMLString = File.ReadAllText(openFileDialog.FileName);
                    }
                }
                catch (Exception ex)
                {
                    tracimTestMessageAsString = "Error: Could not read file from disk. Original error: " + ex.Message;
                    return;
                }
            }
            else
            {
                success = false;
                return;
            }

            try
            {

                XmlSerializer serializer = new XmlSerializer(typeof(tracimMessage));
                this.tracimTestMessage = (tracimMessage)serializer.Deserialize(fs);
                success = true;

                
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("thread3d", "http://tracim.ptb.de/thread3d/test");
                ns.Add("tracim", "http://tracim.ptb.de/tracim");
                ns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");

                MemoryStream ms = new MemoryStream();
                XmlTextWriter xmlTextWriter = new XmlTextWriter(ms, Encoding.UTF8);
                xmlTextWriter.Formatting = Formatting.Indented;
                serializer.Serialize(xmlTextWriter, this.tracimTestMessage, ns);

                ms.Position = 0;

                // copy tracim message to string (e.g. for display on GUI)
                tracimTestMessageAsString = Encoding.UTF8.GetString(ms.ToArray());

                writeTestDataToFile(tracimTestMessage);                  

                            }

            catch (Exception e)
            {
                tracimTestMessageAsString = "<br /><br /> " + "Failed to read test data from file <br /><br /> " + e.ToString();
                System.Diagnostics.Debug.WriteLine(tracimTestMessageAsString);
                success = false;
            }
        }



        /// <summary>
        /// Perform the http request POST to get test data from the server
        /// </summary>
        /// <returns>none</returns>
        public async Task getTest()
        {

            success = false;
            try
            {

                if (OrderKey == null || OrderKey == "")
                    return;
                

                Directory.CreateDirectory(dataDir);
                TraCIMServer = TraCIMServer.TrimEnd('/');
                HttpResponseMessage res = postRequest((TraCIMServer + BaseURL + "order/" + OrderKey + "/test"), "");
                MemoryStream ms = new MemoryStream();
                if (res.IsSuccessStatusCode)
                {
                    using (var httpStream = await res.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    {
                        httpStream.CopyTo(ms);
                        ms.Position = 0;
                    }

                    XmlSerializer serializer = new XmlSerializer(typeof(tracimMessage));
                    this.tracimTestMessage = (tracimMessage)serializer.Deserialize(ms);
                    success = true;

                    // copy tracim message to string (e.g. for display on GUI)
                    tracimTestMessageAsString = Encoding.UTF8.GetString(ms.ToArray());

                    string subDir = tracimTestMessage.process.key + "\\";
                    Directory.CreateDirectory(dataDir + subDir);

                    File.WriteAllText(dataDir + subDir + "testData.xml", FormatXml(tracimTestMessageAsString));

                    writeTestDataToFile(tracimTestMessage);           
                }

                else
                {
                    using (var httpStream = await res.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    {
                        httpStream.CopyTo(ms);
                        ms.Position = 0;
                        // copy tracim message to string (e.g. for display on GUI)
                        tracimTestMessageAsString = Encoding.UTF8.GetString(ms.ToArray());
                        if (this.tracimResultAsXMLString == null)
                            this.tracimResultAsXMLString = "Failed to download test data";
                        File.WriteAllText(dataDir + "error_getTest.xml", FormatXml(tracimTestMessageAsString));
                    }
                }

            }
            catch (Exception e)
            {
                tracimTestMessageAsString = "<br /><br /> " + "Failed to collect test data from server <br /><br /> " + e.ToString();
                success = false;
            }
        }



        /// <summary>
        /// Evaluate the test package
        /// </summary>
        /// <remarks>This method iterates through the requested test package and evaluates the references parameters for each contained geometric element.</remarks>
        /// <returns>true if all evaluations have be successful, otherwise false</returns>
        public bool EvaluateResults()
        {
            try
            {
                int nElement = 10;

                // allocate the storage for the test results
                TestResult = new tracimPTB_Thread3D.thread3DResultPackage();
                TestResult.thread3DResultData = new tracimPTB_Thread3D.thread3DResultData[nElement];

                // iterate through all elements
                int elementCount = 0;
                for (int k = 1; k <= nElement; k++)
                {

                    string basicID = String.Format("T{0:00}", k);

                    tracimPTB_Thread3D.thread3DResultData ElementResult;

                    // Calculate or load result:
                    if (loadFreeData)
                        ElementResult = getFreethread3DResult(basicID);
                    else
                        ElementResult = getthread3DResult(basicID);

                    TestResult.thread3DResultData[elementCount] = ElementResult;
                    elementCount++;
                }


                XmlSerializer serializer = new XmlSerializer(typeof(tracimPTB_Thread3D.thread3DResultPackage));

                TestResult.processKey = ProcessKey; // tracimTestMessage.process.key;
                TestResult.softwareName = softwareName;
                TestResult.softwareVendor = softwareVendor;
                TestResult.softwareVersion = softwareVersion;
                TestResult.softwareRev = softwareRevision;

                // mpe in mm:
                TestResult.mpe_length= Convert.ToDouble(mpeLength / 1.0e3m);
                TestResult.mpe_angle = Convert.ToDouble(mpeAngle / 1.0e6m);
                TestResult.mpe_taper_of_pitch_cone = Convert.ToDouble(mpeTaperOfPitchCone / 1.0e6m);
                //  Serialize the object to Xml with UTF8 encoding
                
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();                
                ns.Add("thread3d", "http://tracim.ptb.de/thread3d/result");
                ns.Add("tracim", "http://tracim.ptb.de/tracim");
                ns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
                

                MemoryStream ms = new MemoryStream();
                XmlTextWriter xmlTextWriter = new XmlTextWriter(ms, Encoding.UTF8);
                xmlTextWriter.Formatting = Formatting.Indented;
                //serializer.Serialize(xmlTextWriter, TestResult, ns);
                serializer.Serialize(xmlTextWriter, TestResult, ns);

                // copy calculated results to xml file (only needed for debugging)
                string subDir = ProcessKey + "\\";
                Directory.CreateDirectory(dataDir + subDir);

                ms.Position = 0;
                FileStream dbgFile = File.Create(dataDir + subDir + "calculated_result.xml");
                ms.CopyTo(dbgFile);
                dbgFile.Close();
                // copy result message to string (e.g. for display on GUI)
                tracimResultAsXMLString = Encoding.UTF8.GetString(ms.ToArray());
            }
            catch (Exception e)
            {
                tracimResultAsXMLString = "Failed to import test results <br /><br />" + e.ToString();
                return false;
            }

            return true;
        }

        /// <summary>
        ///  Perform the http request POST to send the evaluated reference parameters to the server
        /// </summary>
        /// <returns>none</returns>
        public async Task postResults()
        {

            success = false;
            this.tracimValidationMessage = new tracimMessage();
            tracimValidationMessageAsString = "";

            string subDir = ProcessKey + "\\";
            Directory.CreateDirectory(dataDir + subDir);

            TraCIMServer = TraCIMServer.TrimEnd('/');

            try
            {

                TestResult.processKey = ProcessKey; // tracimTestMessage.process.key;
                TestResult.softwareName = softwareName;
                TestResult.softwareVendor = softwareVendor;
                TestResult.softwareVersion = softwareVersion;
                TestResult.softwareRev = softwareRevision;

                // mpe in mm:
                TestResult.mpe_length = Convert.ToDouble(mpeLength / 1.0e3m);
                TestResult.mpe_angle = Convert.ToDouble(mpeAngle / 1.0e6m);
                TestResult.mpe_taper_of_pitch_cone = Convert.ToDouble(mpeTaperOfPitchCone / 1.0e6m);

                XmlSerializer resultserializer = new XmlSerializer(typeof(tracimPTB_Thread3D.thread3DResultPackage));

                MemoryStream ms1 = new MemoryStream();
                
                XmlTextWriter xmlTextWriter = new XmlTextWriter(ms1, Encoding.UTF8);
                xmlTextWriter.Formatting = Formatting.Indented;
                //serializer.Serialize(xmlTextWriter, TestResult, ns);
                resultserializer.Serialize(xmlTextWriter, TestResult);
                
                ms1.Position = 0;
                tracimResultAsXMLString = Encoding.UTF8.GetString(ms1.ToArray());

                MemoryStream ms = new MemoryStream();
                XmlSerializer serializer = new XmlSerializer(typeof(tracimMessage));

                HttpResponseMessage res = postRequest((TraCIMServer + BaseURL + "test/" + ProcessKey), tracimResultAsXMLString);

                if (res.IsSuccessStatusCode)
                {
                    using (var httpStream = await res.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    {
                        httpStream.CopyTo(ms);
                        ms.Position = 0;
                        tracimValidationMessageAsString = Encoding.UTF8.GetString(ms.ToArray());
                        File.WriteAllText(dataDir + subDir + "validation_result.xml", FormatXml(Encoding.UTF8.GetString(ms.ToArray())));
                        this.tracimValidationMessage = serializer.Deserialize(ms) as tracimMessage;

                        success = true;
                    }
                }
                else
                {
                    using (var httpStream = await res.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    {
                        httpStream.CopyTo(ms);
                        ms.Position = 0;
                        tracimValidationMessageAsString = Encoding.UTF8.GetString(ms.ToArray());
                        //tracimValidationMessageAsString = "<br />" + tracimValidationMessageAsString + "<br />";                       
                        File.WriteAllText(dataDir + subDir + "validation_error.xml", FormatXml(Encoding.UTF8.GetString(ms.ToArray())));
                        this.tracimValidationMessage = serializer.Deserialize(ms) as tracimMessage;
                        
                        try
                        {
                            if (tracimValidationMessage.error != null)
                                tracimValidationMessageAsString = tracimValidationMessage.error.description.Replace("\n","<br />");
                        }
                        catch (Exception ex)
                        {
                            tracimValidationMessageAsString = tracimValidationMessageAsString + "<br /><br />" + ex.ToString();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                tracimValidationMessageAsString = "<br />" + tracimValidationMessageAsString + "<br /><br />" + e.ToString();

                System.Diagnostics.Debug.WriteLine(tracimValidationMessageAsString);
                success = false;
            }

            if (success)
                try
                {
                    string PDFFileName = "Report_" + this.ProcessKey + ".pdf";
                    FileStream pdfStream = new FileStream(dataDir + subDir + PDFFileName, FileMode.Create, FileAccess.Write);
                    byte[] myReportPdf = Convert.FromBase64String(this.tracimValidationMessage.validation.reportPDF);
                    pdfStream.Write(myReportPdf, 0, myReportPdf.Length);
                    pdfStream.Close();

                    System.Diagnostics.Process.Start(dataDir + subDir + PDFFileName);
                }
                catch (Exception e)
                {
                    tracimValidationMessageAsString = tracimValidationMessageAsString + "<br /><br />" + e.ToString();
                }
        }

        /// <summary>
        /// helper function for posting http requests
        /// </summary>
        /// <param name="url">Full URL of the requested resource one the server</param>
        /// <param name="data">Data to post (can be empty string)</param>
        /// <returns>Response of the server </returns>
        static HttpResponseMessage postRequest(string url, string data)
        {

            ICredentials credentials = new NetworkCredential(proxyUser.Trim(), proxyPassword.Trim());

            HttpClient cli;

            if (proxyServer.Trim().Equals(""))
                cli = new HttpClient();
            else
            {
                var httpClientHandler = new HttpClientHandler
                {
                    Proxy = new WebProxy(proxyServer.Trim(), true, null, credentials),
                    UseProxy = true
                };

                cli = new HttpClient(httpClientHandler);
            }

           
            cli.BaseAddress = new Uri(url);

                var req = new HttpRequestMessage(HttpMethod.Post, "");

                req.Content = new StringContent(data);
                req.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/xml");
                var res = cli.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
                res.Wait();
                return res.Result;

                throw new Exception("Failed loading test date");
        }



        /// <summary>
        /// get build-in results for the free test date
        public tracimPTB_Thread3D.thread3DResultData getFreethread3DResult(string basicID)
        {

            int resultCount = 0;
            foreach (tracimPTB_Thread3D.thread3DResultData exampleResult in DefaultResultsExampleData.DefaultExampleResults.thread3DResultData)
            {
                if (exampleResult.basicID.Equals(basicID))
                {
                    return exampleResult;
                }
                resultCount++;
            }

            throw new Exception("Build-in results for free test not found");

        }


        /// <summary>
        /// read results from txt-files
        public tracimPTB_Thread3D.thread3DResultData getthread3DResult(string basicID)
        {
            string subDir = ProcessKey + "\\";

            thread3DResultData result = new thread3DResultData();

            result.basicID = basicID;

            var bla = File.ReadLines(dataDir + subDir + basicID + "_resultData.txt");

            char[] delimiterChars = { ' ', ',', '\t' };

            var allRows = File
              .ReadLines(dataDir + subDir + basicID + "_resultData.txt")
              .Where(line => !string.IsNullOrWhiteSpace(line))
              .Select(line => line.Split(delimiterChars, StringSplitOptions.RemoveEmptyEntries))
              .ToList();
            
            //String type = allRows.Where(row => row[0].Equals("type")).ToList()[0][1];
            //double reference_plane = allRows.Where(row => row[0].Equals("reference_plane")).Select( col => col[1] ).Select(double.Parse).ToList()[0];
            double lead_1 = allRows.Where(row => row[0].Equals("lead_1")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double flank_angle_1 = allRows.Where(row => row[0].Equals("flank_angle_1")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double lead_2 = allRows.Where(row => row[0].Equals("lead_2")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double flank_angle_2 = allRows.Where(row => row[0].Equals("flank_angle_2")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double taper_of_pitch_cone = allRows.Where(row => row[0].Equals("taper_of_pitch_cone")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double pitch_diameter = allRows.Where(row => row[0].Equals("pitch_diameter")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double lead_of_helix = allRows.Where(row => row[0].Equals("lead_of_helix")).Select(col => col[1]).Select(double.Parse).ToList()[0];
            double cumulative_lead_deviation = allRows.Where(row => row[0].Equals("cumulative_lead_deviation")).Select(col => col[1]).Select(double.Parse).ToList()[0];

            //result.reference_plane = reference_plane;
            result.lead_1 = lead_1;
            result.flank_angle_1 = flank_angle_1;
            result.lead_2 = lead_2;
            result.flank_angle_2 = flank_angle_2;
            result.taper_of_pitch_cone = taper_of_pitch_cone;
            result.pitch_diameter = pitch_diameter;
            result.lead_of_helix = lead_of_helix;
            result.cumulative_lead_deviation = cumulative_lead_deviation;

            return result;
        }
        
        
        private void writeTestDataToFile(tracimMessage msg)
        {
            string info = "Order key:       " + msg.order.key + "\n";
            info = info + "Process key:     " + msg.process.key + "\n";
            info = info + "Creation Date:   " + msg.order.creationDate.ToLongDateString() + " " + msg.order.creationDate.ToLongTimeString() + "\n";
            info = info + "Expiration Date: " + msg.order.expirationDate.ToLongDateString() + " " + msg.order.expirationDate.ToLongTimeString() + "\n";


            string subDir = msg.process.key + "\\";
            Directory.CreateDirectory(dataDir + subDir);

            File.WriteAllText(dataDir + subDir + "processInfo.txt", info);
            tracimPTB_Thread3D.thread3DTestPackage myTest = (tracimPTB_Thread3D.thread3DTestPackage)msg.test;


            int nElement = myTest.thread3DTestData.Length;
            for (int k = 0; k < nElement; k++)
            {

                tracimPTB_Thread3D.nominal_values nominal_values = myTest.thread3DTestData[k].nominal_values;
                tracimPTB_Thread3D.inspection_values inspection_values = myTest.thread3DTestData[k].inspection_values;

                var sb = new System.Text.StringBuilder();

                sb.Append(String.Format("{0,-29}{1,-20}\n", "Basic ID", myTest.thread3DTestData[k].basicID));
                sb.Append(String.Format("{0,-29}{1,-20}\n", "type", nominal_values.type));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "nominal diameter", nominal_values.nominal_diameter));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "lead", nominal_values.lead));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "pitch diameter", nominal_values.pitch_diameter));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "flank angle 1", nominal_values.flank_angle_1));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "flank angle 2", nominal_values.flank_angle_2));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n\n", "taper of pitch cone", nominal_values.taper_of_pitch_cone));

                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "stylus diameter", inspection_values.stylus_diameter));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n", "intersection point", inspection_values.intersection_point));
                sb.Append(String.Format("{0,-29}{1,-20:N16}\n\n", "reference plane", inspection_values.reference_plane));

                var pointesetList = inspection_values.thread_pointset;

                foreach (var pointeset in pointesetList)
                {
                    sb.Append(String.Format("{0,-8}{1,-4}{2,-8} {3,-4}\n", "flank", pointeset.flankNumber, "track", pointeset.trackNumber));

                    foreach (var xyz in pointeset.p3D_set)
                    {

                        sb.Append(String.Format("{0,-20:N16}{1,-20:N16}{2,-20:N16}\n", xyz.x, xyz.y, xyz.z));
                    }
                    sb.Append("\n");
                }

                File.WriteAllText(dataDir + subDir + myTest.thread3DTestData[k].basicID + "_testData.txt", sb.ToString());
            }            
        }
        


        /// <summary>
        /// Function to format xml strings (used to add linebreaks)
        static string FormatXml(string xml)
        {
            try
            {
                XDocument doc = XDocument.Parse(xml);
                return doc.ToString();
            }
            catch (Exception)
            {
                return xml;
            }
        }

        /*         
        /// <summary>
        /// Another function to format xml strings (used to add linebreaks)
        static string PrettyXml(string xml)
        {
            var stringBuilder = new StringBuilder();

            var element = XElement.Parse(xml);

            var settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;
            settings.Indent = true;
            settings.NewLineOnAttributes = true;

            using (var xmlWriter = XmlWriter.Create(stringBuilder, settings))
            {
                element.Save(xmlWriter);
            }

            return stringBuilder.ToString();
        }
        */

    }
}






