//******************************************************************************************************
//  AdapterInitializer.cs - Gbtc
//
//  Copyright © 2026, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
//  file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  01/15/2026 - J. Ritchie Carroll
//       Generated original version of source code.
//
//******************************************************************************************************
// ReSharper disable AccessToDisposedClosure

using System.Diagnostics;

namespace DNP3ConfigLoader;

/// <summary>
/// Initializes DNP3 adapters by executing commands through openPDCConsole.exe.
/// </summary>
public static class AdapterInitializer
{
    /// <summary>
    /// Initializes adapters through the openPDC console.
    /// </summary>
    public static void InitializeAdapters(ProcessParameters processParams)
    {
        if (string.IsNullOrWhiteSpace(processParams.Settings.OpenPDCConsolePath) || !File.Exists(processParams.Settings.OpenPDCConsolePath))
        {
            Console.WriteLine("     WARNING: openPDCConsole.exe not found. Skipping adapter initialization.");
            return;
        }

        IEnumerable<string> acronyms = processParams.ProcessedAcronyms;

        Console.WriteLine();
        Console.WriteLine("Initializing adapters...");

        ProcessStartInfo startInfo = new()
        {
            FileName = processParams.Settings.OpenPDCConsolePath,
            UseShellExecute = false,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            WorkingDirectory = Path.GetDirectoryName(processParams.Settings.OpenPDCConsolePath) ?? Environment.CurrentDirectory
        };

        try
        {
            using Process? process = Process.Start(startInfo);

            if (process is null)
            {
                Console.WriteLine("       ERROR: Failed to start openPDCConsole.exe, check the path and permissions.");
                return;
            }

            bool connected = false;
            object ioLock = new();

            // Start async output reading
            Thread stdoutThread = new(() => ReadOutput(process.StandardOutput, Console.Out, ref connected, ioLock)) { IsBackground = true };
            Thread stderrThread = new(() => ReadOutput(process.StandardError, Console.Error, ref connected, ioLock)) { IsBackground = true };

            stdoutThread.Start();
            stderrThread.Start();

            // Wait for connection to be established
            Console.WriteLine("Waiting for connection to openPDC...");

            int sleepCount = 0;

            while (!connected && sleepCount < 50)
            {
                Thread.Sleep(100);
                sleepCount++;
            }

            int commandDelay = processParams.Settings.CommandDelayMilliseconds;

            if (connected)
            {
                int initCount = 0;

                foreach (string deviceAcronym in acronyms)
                {
                    Console.WriteLine();
                    Console.WriteLine($"    Initializing adapter for device: {deviceAcronym}...");
                    Console.WriteLine();

                    try
                    {
                        lock (ioLock)
                        {
                            process.StandardInput.WriteLine($"init {deviceAcronym}");
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"    WARNING: Failed to send command: {ex.Message}");
                        break;
                    }

                    initCount++;

                    // Flush every 20 commands
                    if (initCount % 20 == 0)
                    {
                        FlushCommands(process, ioLock);
                        Thread.Sleep(commandDelay);
                    }
                }

                // Send final flush
                if (initCount % 20 != 0)
                {
                    FlushCommands(process, ioLock);
                    Thread.Sleep(commandDelay);
                }

                Console.WriteLine();
                Console.WriteLine($"Initialized {acronyms.Count():N0} adapters.");
            }
            else
            {
                Console.WriteLine("WARNING: Never detected connection to openPDC, adapter initializations may not have succeeded.");
            }

            // Close stdin and terminate
            try
            {
                lock (ioLock) 
                    process.StandardInput.Close();
            }
            catch
            {
                // stdin might already be closed
            }

            process.Kill();
            process.WaitForExit(processParams.Settings.ProcessTimeoutMilliseconds);

            stdoutThread.Join(1000);
            stderrThread.Join(1000);

            process.StandardOutput.Close();
            process.StandardError.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"       ERROR: Failed to initialize adapters: {ex.Message}");
        }

        Console.WriteLine();
    }

    private static void ReadOutput(StreamReader pipe, TextWriter output, ref bool connected, object ioLock)
    {
        try
        {
            while (pipe.ReadLine() is { } line)
            {
                if (connected)
                    continue;

                string detail = line.Trim().ToLowerInvariant();

                // Only show relevant lines from console output
                if (detail.Contains("dnp3") || detail.Contains("connect"))
                {
                    lock (ioLock)
                    {
                        output.WriteLine($"        {line}");
                        output.Flush();
                    }
                }

                if (detail.Contains("connected"))
                    connected = true;
            }
        }
        catch
        {
            // Pipe closed
        }
    }

    private static void FlushCommands(Process process, object ioLock)
    {
        try
        {
            lock (ioLock) 
                process.StandardInput.Flush();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"    WARNING: Failed to flush stdin: {ex.Message}");
        }
    }
}
