using System.Diagnostics;
using System.Net.WebSockets;
using System.Text;
using NAudio.CoreAudioApi;
using NAudio.Wave;

namespace _2dxAutoClip;

class Program
{
    private static readonly string WebsocketAddress = "localhost";
    private static readonly int WebsocketPort = 10573;
    private static Process? _ffmpegProcess;
    private static string _ffmpegFolderPath = GetFolderPath();
    private static WasapiCapture? _waveSource;
    private static WaveFileWriter _writer = null!;
    private static string _audioFilePath = null!;
    private static string _videoFilePath = null!;

    private static async Task Main(string[] args)
    {
        var spiceProcesses = Process.GetProcessesByName("spice64");

        if (spiceProcesses.Length > 0)
        {
            Console.WriteLine("Found spice64, Attempting connection to TickerHookWS...");
            await TryConnectWebSocket();
        }
        else
        {
            Console.WriteLine("Unable to find Spice64. Are you sure Beatmania IIDX is running and TickerHook is enabled?");
        }
    }


    private static string GetFolderPath()
    {
        const string filePath = "prop.txt";
        if (File.Exists(filePath))
        {
            var fileContent = File.ReadAllText(filePath);
            var lines = fileContent.Split('\n');
            foreach (var line in lines)
            {
                if (line.StartsWith("path:"))
                {
                    var path = line["path:".Length..].Trim();
                    if (Directory.Exists(path))
                    {
                        return path;
                    }
                    Console.WriteLine($"The path specified in {filePath} does not exist.");
                }
            }
        }
        else
        {
            Console.WriteLine($"The file {filePath} does not exist.");
        }

        return GetDefaultVideosFolderPath();
    }

    private static string GetDefaultVideosFolderPath()
    {
        var userName = Environment.UserName;
        var defaultPath = Path.Combine($@"C:\Users\{userName}", "Videos");
        return defaultPath;
    }
    
private static async Task TryConnectWebSocket()
{
    const int maxRetries = 5;
    int attempt = 0;
    
    while (attempt < maxRetries)
    {
        Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect...");
        
        bool success = await ConnectWebSocket();
        
        if (success)
        {
            break;  // Exit the loop if connection was successful
        }
        
        attempt++;
        
        if (attempt < maxRetries)
        {
            Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining.");
            await Task.Delay(10000);  // Wait for 10 seconds before retrying
        }
    }

    if (attempt == maxRetries)
    {
        Console.WriteLine("Failed to connect after 5 attempts.");
    }
}

private static async Task<bool> ConnectWebSocket()
{
    var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}");
    var reconnecting = false;

    var lastMessage = string.Empty;
    var consecutiveMessageCount = 0;
    var isRecording = false;
    var currentSongName = string.Empty;
    
    var shouldCheckForMusicSelect = false;

    using var clientWebSocket = new ClientWebSocket();
    try
    {
        await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None);
        Console.WriteLine("Connected to TickerHook WebSocket.");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
        return false; // Indicate connection failure
    }

    var buffer = new byte[1024];

    while (clientWebSocket.State == WebSocketState.Open)
    {
        WebSocketReceiveResult result;
        try
        {
            result = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error receiving message: {ex.Message}");
            reconnecting = true;
            break;
        }

        var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper();
        Console.WriteLine($"Received message: {message}");

        if (message == lastMessage && !message.Contains("SELECT FROM"))
        {
            consecutiveMessageCount++;
        }
        else
        {
            consecutiveMessageCount = 1;
            lastMessage = message;
        }

        if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording)
        {
            currentSongName = message;
            Console.WriteLine("Starting recording...");
            StartRecording(currentSongName);
            isRecording = true;
        }

        if (isRecording)
        {
            if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!"))
            {
                Console.WriteLine("Stopping recording...");
                StopRecording(currentSongName);
                isRecording = false;
                shouldCheckForMusicSelect = false;
            }
            else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED.."))
            { 
                shouldCheckForMusicSelect = true;
            }
        }
    }

    return !reconnecting;
}

    private static void StartRecording(string songName)
    {
        Task.Run(() => StartAudioRecording(songName));
        StartFfmpegRecording(songName); 
    }

    private static void StartAudioRecording(string songName)
    {
        try
        {
            var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
            _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
            var outputFolder = Path.GetDirectoryName(_audioFilePath)!;
            Directory.CreateDirectory(outputFolder);
            _waveSource = new WasapiLoopbackCapture();
            _writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
            _waveSource.DataAvailable += (sender, args) =>
            {
                _writer.Write(args.Buffer, 0, args.BytesRecorded);
            };
            _waveSource.RecordingStopped += (sender, args) =>
            {
                _writer.Dispose();
                _writer = null!;
                _waveSource.Dispose();
                _waveSource = null;
            };

            _waveSource.StartRecording();
            Console.WriteLine("WASAPI Audio recording started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error starting audio recording: {ex.Message}");
        }
    }



    private static void StartFfmpegRecording(string songName)
    {
        var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
        _videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";

        var ffmpegArguments = $"-framerate 60 " +
                              $"-filter_complex \"ddagrab=0,hwdownload,format=bgra\" " +
                              $"-c:v libx264 -movflags +faststart -crf 20 -y \"{_videoFilePath}\"";

        _ffmpegProcess = new Process();
        _ffmpegProcess.StartInfo.FileName = "ffmpeg";
        _ffmpegProcess.StartInfo.Arguments = ffmpegArguments;
        _ffmpegProcess.StartInfo.UseShellExecute = false;
        _ffmpegProcess.StartInfo.RedirectStandardOutput = false;
        _ffmpegProcess.StartInfo.RedirectStandardError = true;
        _ffmpegProcess.StartInfo.CreateNoWindow = true;

        _ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);

        _ffmpegProcess.Start();
        _ffmpegProcess.BeginErrorReadLine();
    }

    private static void StopRecording(string songName)
    {
        StopFfmpegRecording();
        StopAudioRecording();
        CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
    }

    private static void StopAudioRecording()
    {
        if (_waveSource == null) return;

        _waveSource.StopRecording();
        _waveSource.Dispose();
        _waveSource = null;
        if (_writer != null)
        {
            _writer.Dispose();
            _writer = null!;
        }

        Console.WriteLine("Audio recording stopped.");
    }


    private static void StopFfmpegRecording()
    {
        if (_ffmpegProcess != null && _ffmpegProcess.HasExited) return;
        _ffmpegProcess?.Kill();
        _ffmpegProcess?.WaitForExit();
        _ffmpegProcess?.Dispose();
        _ffmpegProcess = null;
        Console.WriteLine("FFMPEG process stopped.");
    }

    private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName)
    {
        var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4";
        var ffmpegArgs =
            $"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental \"{combinedOutputFilePath}\"";

        var processInfo = new ProcessStartInfo
        {
            FileName = "ffmpeg",
            Arguments = ffmpegArgs,
            RedirectStandardOutput = false,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        using var process = Process.Start(processInfo);
        if (process == null)
        {
            Console.WriteLine("FFmpeg failed to start.");
            return;
        }

        var error = process.StandardError.ReadToEnd();

        process.WaitForExit();

        Console.WriteLine("FFmpeg Error: " + error);

        Console.WriteLine(File.Exists(combinedOutputFilePath)
            ? "Audio and video have been successfully combined."
            : "Failed to combine audio and video. Check the logs for errors.");
        File.Delete(videoFilePath);
        File.Delete(audioFilePath);
    }
}