using Li.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace GameBuilder.Atrac3
{
    public class Atrac3ToolEncoder : IAtracEncoderBase
    {
        [DllImport("libc")]
        private static extern int setenv(string name, string value, bool overwrite);

        private static Random rng = new Random();
        private static string TOOLS_DIRECTORY = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tools");

        private static string AT3TOOL_WIN = Path.Combine(TOOLS_DIRECTORY, "at3tool.exe");
        private static string AT3TOOL_LINUX = Path.Combine(TOOLS_DIRECTORY, "at3tool.elf");
        
        private static string TEMP_DIRECTORY = Path.Combine(Path.GetTempPath(), "at3tool_tmp");
        private static string LD_LIBRARY_PATH = "LD_LIBRARY_PATH";

        // random name so that can generate multiple at once if wanted ..
        private string TEMP_WAV;
        private string TEMP_AT3;
        public Atrac3ToolEncoder()
        {
            string rdmPart = rng.Next().ToString("X");

            TEMP_WAV = Path.Combine(TEMP_DIRECTORY, rdmPart + "_tmp.wav");
            TEMP_AT3 = Path.Combine(TEMP_DIRECTORY, rdmPart + "_tmp.at3");

        }
         
        private static string AT3TOOL_LOCATION
        {
            get
            {
                if (OperatingSystem.IsWindows())
                    return AT3TOOL_WIN;
                else if (OperatingSystem.IsLinux())
                    return AT3TOOL_LINUX;
                else
                    throw new PlatformNotSupportedException("No at3tool binary for your platform");
            }
        }

        private string setupLibaryPath()
        {
            string? libaryPath = Environment.GetEnvironmentVariable(LD_LIBRARY_PATH);
            if (libaryPath is null) libaryPath = TOOLS_DIRECTORY;
            else libaryPath += ";" + TOOLS_DIRECTORY;

            Environment.SetEnvironmentVariable(libaryPath, libaryPath);
            setenv(LD_LIBRARY_PATH, libaryPath, true);

            return libaryPath;
        }
        private void runAtrac3Tool()
        {
            using(Process proc = new Process())
            {
                if (OperatingSystem.IsLinux())
                    proc.StartInfo.Environment.Add(LD_LIBRARY_PATH, setupLibaryPath());

                proc.StartInfo.FileName = AT3TOOL_LOCATION;
                proc.StartInfo.Arguments = "-br 132 -e \"" + TEMP_WAV + "\" \"" + TEMP_AT3 + "\"";

                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.CreateNoWindow = true;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardInput = true;

                proc.Start();
                proc.WaitForExit();

                string stdout = proc.StandardOutput.ReadToEnd();
                if (!stdout.Contains("Total Encoded Bytes"))
                    throw new Exception(stdout);
            }
        }

        private byte[] stripAtracHeader()
        {
            using(FileStream at3Stream = File.OpenRead(TEMP_AT3))
            {
                StreamUtil at3Util = new StreamUtil(at3Stream);
                at3Stream.Seek(0x4C, SeekOrigin.Begin);
                int at3Len = at3Util.ReadInt32();
                return at3Util.ReadBytes(at3Len);
            }
        }

        private void makeWav(byte[] pcmData)
        {
            using (FileStream wavStream = File.Open(TEMP_WAV, FileMode.Create))
            {
                // CD-AUDIO standard settings
                int fileSize = pcmData.Length;
                int samplerate = 44100;
                short channels = 2; // channels
                short format = 16; // signed, 16 bit PCM

                StreamUtil wavUtil = new StreamUtil(wavStream);

                wavUtil.WriteStr("RIFF");
                wavUtil.WriteInt32((fileSize + (0x2C - 8)));

                wavUtil.WriteStr("WAVE");
                wavUtil.WriteStr("fmt ");
                wavUtil.WriteInt32(format); 

                wavUtil.WriteInt16(1);
                wavUtil.WriteInt16(channels); 
                wavUtil.WriteInt32(samplerate); 
                wavUtil.WriteInt32((samplerate * format * channels) / 8);
                wavUtil.WriteInt16(Convert.ToInt16(format * channels));
                wavUtil.WriteInt16(format);

                wavUtil.WriteStr("data");
                wavUtil.WriteInt32(fileSize);
                wavUtil.WriteBytes(pcmData);
            }
        }

        private void ensureFilesAvailable()
        {
            if (!Directory.Exists(TEMP_DIRECTORY))
                Directory.CreateDirectory(TEMP_DIRECTORY);

            if (!Directory.Exists(TOOLS_DIRECTORY))
                Directory.CreateDirectory(TOOLS_DIRECTORY);

            if (OperatingSystem.IsWindows())
            {
                if (!File.Exists(AT3TOOL_WIN))
                {
                    throw new FileNotFoundException("Cannot find at3tool at " + AT3TOOL_WIN);
                }
            }
            else if(OperatingSystem.IsLinux())
            {
                if (!File.Exists(AT3TOOL_LINUX))
                {
                    throw new FileNotFoundException("Cannot find at3tool at " + AT3TOOL_LINUX);
                }
            }
        }

        private void cleanup()
        {
            if (File.Exists(TEMP_WAV)) File.Delete(TEMP_WAV);
            if (File.Exists(TEMP_AT3)) File.Delete(TEMP_AT3);
        }

        public byte[] EncodeToAtrac(byte[] pcmData)
        {
            ensureFilesAvailable();

            makeWav(pcmData);
            runAtrac3Tool();
            byte[] rawAtracData = stripAtracHeader();
            
            cleanup();

            return rawAtracData;
        }
    }
}
