using System.Linq;
using System.Threading;
using Aaru.Console;
using Aaru.Decoders.CD;
using Aaru.Decoders.SCSI;
using Aaru.Devices;
using Aaru.Helpers;

namespace Aaru.Tests.Devices
{
    internal static partial class ScsiMmc
    {
        static void ReadLeadOutUsingTrapDisc(string devPath, Device dev)
        {
            string strDev;
            int    item;
            bool   tocIsNotBcd = false;
            bool   sense;
            byte[] buffer;
            byte[] senseBuffer;
            int    retries;

            start:
            System.Console.Clear();

            AaruConsole.WriteLine("Ejecting disc...");

            dev.AllowMediumRemoval(out _, dev.Timeout, out _);
            dev.EjectTray(out _, dev.Timeout, out _);

            AaruConsole.WriteLine("Please insert a data only disc inside...");
            AaruConsole.WriteLine("Press any key to continue...");
            System.Console.ReadLine();

            AaruConsole.WriteLine("Sending READ FULL TOC to the device...");

            retries = 0;

            do
            {
                retries++;
                sense = dev.ScsiTestUnitReady(out senseBuffer, dev.Timeout, out _);

                if(!sense)
                    break;

                DecodedSense? decodedSense = Sense.Decode(senseBuffer);

                if(decodedSense.Value.ASC != 0x04)
                    break;

                if(decodedSense.Value.ASCQ != 0x01)
                    break;

                Thread.Sleep(2000);
            } while(retries < 25);

            sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);

            if(sense)
            {
                AaruConsole.WriteLine("READ FULL TOC failed...");
                AaruConsole.WriteLine("{0}", Sense.PrettifySense(senseBuffer));
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            FullTOC.CDFullTOC? decodedToc = FullTOC.Decode(buffer);

            if(decodedToc is null)
            {
                AaruConsole.WriteLine("Could not decode TOC...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            FullTOC.CDFullTOC toc = decodedToc.Value;

            FullTOC.TrackDataDescriptor leadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);

            if(leadOutTrack.POINT != 0xA2)
            {
                AaruConsole.WriteLine("Cannot find lead-out...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            int min   = ((leadOutTrack.PMIN   >> 4) * 10) + (leadOutTrack.PMIN   & 0x0F);
            int sec   = ((leadOutTrack.PSEC   >> 4) * 10) + (leadOutTrack.PSEC   & 0x0F);
            int frame = ((leadOutTrack.PFRAME >> 4) * 10) + (leadOutTrack.PFRAME & 0x0F);

            int sectors = (min * 60 * 75) + (sec * 75) + frame - 150;

            AaruConsole.WriteLine("Data disc shows {0} sectors...", sectors);

            AaruConsole.WriteLine("Ejecting disc...");

            dev.AllowMediumRemoval(out _, dev.Timeout, out _);
            dev.EjectTray(out _, dev.Timeout, out _);

            AaruConsole.WriteLine("Please insert the trap disc inside...");
            AaruConsole.WriteLine("Press any key to continue...");
            System.Console.ReadLine();

            AaruConsole.WriteLine("Sending READ FULL TOC to the device...");

            retries = 0;

            do
            {
                retries++;
                sense = dev.ScsiTestUnitReady(out senseBuffer, dev.Timeout, out _);

                if(!sense)
                    break;

                DecodedSense? decodedSense = Sense.Decode(senseBuffer);

                if(decodedSense.Value.ASC != 0x04)
                    break;

                if(decodedSense.Value.ASCQ != 0x01)
                    break;

                Thread.Sleep(2000);
            } while(retries < 25);

            sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);

            if(sense)
            {
                AaruConsole.WriteLine("READ FULL TOC failed...");
                AaruConsole.WriteLine("{0}", Sense.PrettifySense(senseBuffer));
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            decodedToc = FullTOC.Decode(buffer);

            if(decodedToc is null)
            {
                AaruConsole.WriteLine("Could not decode TOC...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            toc = decodedToc.Value;

            leadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);

            if(leadOutTrack.POINT != 0xA2)
            {
                AaruConsole.WriteLine("Cannot find lead-out...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            min = 0;

            if(leadOutTrack.PMIN == 122)
                tocIsNotBcd = true;

            if(leadOutTrack.PMIN >= 0xA0 &&
               !tocIsNotBcd)
            {
                min               += 90;
                leadOutTrack.PMIN -= 0x90;
            }

            if(tocIsNotBcd)
            {
                min   = leadOutTrack.PMIN;
                sec   = leadOutTrack.PSEC;
                frame = leadOutTrack.PFRAME;
            }
            else
            {
                min   += ((leadOutTrack.PMIN   >> 4) * 10) + (leadOutTrack.PMIN   & 0x0F);
                sec   =  ((leadOutTrack.PSEC   >> 4) * 10) + (leadOutTrack.PSEC   & 0x0F);
                frame =  ((leadOutTrack.PFRAME >> 4) * 10) + (leadOutTrack.PFRAME & 0x0F);
            }

            int trapSectors = (min * 60 * 75) + (sec * 75) + frame - 150;

            AaruConsole.WriteLine("Trap disc shows {0} sectors...", trapSectors);

            if(trapSectors < sectors + 100)
            {
                AaruConsole.WriteLine("Trap disc doesn't have enough sectors...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            AaruConsole.WriteLine("Stopping motor...");

            dev.StopUnit(out _, dev.Timeout, out _);

            AaruConsole.WriteLine("Please MANUALLY get the trap disc out and put the data disc back inside...");
            AaruConsole.WriteLine("Press any key to continue...");
            System.Console.ReadLine();

            AaruConsole.WriteLine("Waiting 5 seconds...");
            Thread.Sleep(5000);

            AaruConsole.WriteLine("Sending READ FULL TOC to the device...");

            retries = 0;

            do
            {
                retries++;
                sense = dev.ReadRawToc(out buffer, out senseBuffer, 1, dev.Timeout, out _);

                if(!sense)
                    break;

                DecodedSense? decodedSense = Sense.Decode(senseBuffer);

                if(decodedSense.Value.ASC != 0x04)
                    break;

                if(decodedSense.Value.ASCQ != 0x01)
                    break;
            } while(retries < 25);

            if(sense)
            {
                AaruConsole.WriteLine("READ FULL TOC failed...");
                AaruConsole.WriteLine("{0}", Sense.PrettifySense(senseBuffer));
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            decodedToc = FullTOC.Decode(buffer);

            if(decodedToc is null)
            {
                AaruConsole.WriteLine("Could not decode TOC...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            toc = decodedToc.Value;

            FullTOC.TrackDataDescriptor newLeadOutTrack = toc.TrackDescriptors.FirstOrDefault(t => t.POINT == 0xA2);

            if(newLeadOutTrack.POINT != 0xA2)
            {
                AaruConsole.WriteLine("Cannot find lead-out...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            if(newLeadOutTrack.PMIN >= 0xA0 &&
               !tocIsNotBcd)
                newLeadOutTrack.PMIN -= 0x90;

            if(newLeadOutTrack.PMIN   != leadOutTrack.PMIN ||
               newLeadOutTrack.PSEC   != leadOutTrack.PSEC ||
               newLeadOutTrack.PFRAME != leadOutTrack.PFRAME)
            {
                AaruConsole.WriteLine("Lead-out has changed, this drive does not support hot swapping discs...");
                AaruConsole.WriteLine("Press any key to continue...");
                System.Console.ReadLine();

                return;
            }

            AaruConsole.Write("Reading LBA {0}... ", sectors + 5);

            bool dataResult = dev.ReadCd(out byte[] dataBuffer, out byte[] dataSense, (uint)(sectors + 5), 2352, 1,
                                         MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true,
                                         true, MmcErrorField.None, MmcSubchannel.None, dev.Timeout, out _);

            AaruConsole.WriteLine(dataResult ? "FAIL!" : "Success!");

            AaruConsole.Write("Reading LBA {0} as audio (scrambled)... ", sectors + 5);

            bool scrambledResult = dev.ReadCd(out byte[] scrambledBuffer, out byte[] scrambledSense,
                                              (uint)(sectors + 5), 2352, 1, MmcSectorTypes.Cdda, false, false, false,
                                              MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.None,
                                              dev.Timeout, out _);

            AaruConsole.WriteLine(scrambledResult ? "FAIL!" : "Success!");

            AaruConsole.Write("Reading LBA {0}'s PQ subchannel... ", sectors + 5);

            bool pqResult = dev.ReadCd(out byte[] pqBuffer, out byte[] pqSense, (uint)(sectors + 5), 16, 1,
                                       MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, false, false,
                                       MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout, out _);

            if(pqResult)
                pqResult = dev.ReadCd(out pqBuffer, out pqSense, (uint)(sectors + 5), 16, 1, MmcSectorTypes.AllTypes,
                                      false, false, false, MmcHeaderCodes.None, false, false, MmcErrorField.None,
                                      MmcSubchannel.Q16, dev.Timeout, out _);

            AaruConsole.WriteLine(pqResult ? "FAIL!" : "Success!");

            AaruConsole.Write("Reading LBA {0}'s PQ subchannel... ", sectors + 5);

            bool rwResult = dev.ReadCd(out byte[] rwBuffer, out byte[] rwSense, (uint)(sectors + 5), 16, 1,
                                       MmcSectorTypes.AllTypes, false, false, false, MmcHeaderCodes.None, false, false,
                                       MmcErrorField.None, MmcSubchannel.Rw, dev.Timeout, out _);

            if(rwResult)
                rwResult = dev.ReadCd(out rwBuffer, out rwSense, (uint)(sectors + 5), 16, 1, MmcSectorTypes.Cdda, false,
                                      false, false, MmcHeaderCodes.None, false, false, MmcErrorField.None,
                                      MmcSubchannel.Rw, dev.Timeout, out _);

            AaruConsole.WriteLine(pqResult ? "FAIL!" : "Success!");

            menu:
            System.Console.Clear();
            AaruConsole.WriteLine("Device: {0}", devPath);
            AaruConsole.WriteLine("Device {0} read Lead-Out.", dataResult && scrambledResult ? "cannot" : "can");

            AaruConsole.WriteLine("LBA {0} sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5, dataResult,
                                  dataBuffer is null
                                      ? "null"
                                      : ArrayHelpers.ArrayIsNullOrEmpty(dataBuffer)
                                          ? "empty"
                                          : $"{dataBuffer.Length} bytes", dataSense is null
                                                                              ? "null"
                                                                              : ArrayHelpers.
                                                                                  ArrayIsNullOrEmpty(dataSense)
                                                                                  ? "empty"
                                                                                  : $"{dataSense.Length}");

            AaruConsole.WriteLine("LBA {0} (scrambled) sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
                                  scrambledResult, scrambledBuffer is null
                                                       ? "null"
                                                       : ArrayHelpers.ArrayIsNullOrEmpty(scrambledBuffer)
                                                           ? "empty"
                                                           : $"{scrambledBuffer.Length} bytes", scrambledSense is null
                                      ? "null"
                                      : ArrayHelpers.ArrayIsNullOrEmpty(scrambledSense)
                                          ? "empty"
                                          : $"{scrambledSense.Length}");

            AaruConsole.WriteLine("LBA {0}'s PQ sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
                                  pqResult, pqBuffer is null
                                                ? "null"
                                                : ArrayHelpers.ArrayIsNullOrEmpty(pqBuffer)
                                                    ? "empty"
                                                    : $"{pqBuffer.Length} bytes", pqSense is null
                                      ? "null"
                                      : ArrayHelpers.ArrayIsNullOrEmpty(pqSense)
                                          ? "empty"
                                          : $"{pqSense.Length}");

            AaruConsole.WriteLine("LBA {0}'s RW sense is {1}, buffer is {2}, sense buffer is {3}.", sectors + 5,
                                  rwResult, rwBuffer is null
                                                ? "null"
                                                : ArrayHelpers.ArrayIsNullOrEmpty(rwBuffer)
                                                    ? "empty"
                                                    : $"{rwBuffer.Length} bytes", rwSense is null
                                      ? "null"
                                      : ArrayHelpers.ArrayIsNullOrEmpty(rwSense)
                                          ? "empty"
                                          : $"{rwSense.Length}");

            AaruConsole.WriteLine();
            AaruConsole.WriteLine("Choose what to do:");
            AaruConsole.WriteLine("1.- Print LBA {0} buffer.", sectors                    + 5);
            AaruConsole.WriteLine("2.- Print LBA {0} sense buffer.", sectors              + 5);
            AaruConsole.WriteLine("3.- Decode LBA {0} sense buffer.", sectors             + 5);
            AaruConsole.WriteLine("4.- Print LBA {0} (scrambled) buffer.", sectors        + 5);
            AaruConsole.WriteLine("5.- Print LBA {0} (scrambled) sense buffer.", sectors  + 5);
            AaruConsole.WriteLine("6.- Decode LBA {0} (scrambled) sense buffer.", sectors + 5);
            AaruConsole.WriteLine("7.- Print LBA {0}'s PQ buffer.", sectors               + 5);
            AaruConsole.WriteLine("8.- Print LBA {0}'s PQ sense buffer.", sectors         + 5);
            AaruConsole.WriteLine("9.- Decode LBA {0}'s PQ sense buffer.", sectors        + 5);
            AaruConsole.WriteLine("10.- Print LBA {0}'s RW buffer.", sectors              + 5);
            AaruConsole.WriteLine("11.- Print LBA {0}'s RW sense buffer.", sectors        + 5);
            AaruConsole.WriteLine("12.- Decode LBA {0}'s RW sense buffer.", sectors       + 5);
            AaruConsole.WriteLine("13.- Send command again.");
            AaruConsole.WriteLine("0.- Return to special SCSI MultiMedia Commands menu.");
            AaruConsole.Write("Choose: ");

            strDev = System.Console.ReadLine();

            if(!int.TryParse(strDev, out item))
            {
                AaruConsole.WriteLine("Not a number. Press any key to continue...");
                System.Console.ReadKey();
                System.Console.Clear();

                goto menu;
            }

            switch(item)
            {
                case 0:
                    AaruConsole.WriteLine("Returning to special SCSI MultiMedia Commands menu...");

                    return;
                case 1:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} response:", sectors + 5);

                    if(buffer != null)
                        PrintHex.PrintHexArray(dataBuffer, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 2:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} sense:", sectors + 5);

                    if(senseBuffer != null)
                        PrintHex.PrintHexArray(dataSense, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 3:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} decoded sense:", sectors + 5);
                    AaruConsole.Write("{0}", Sense.PrettifySense(dataSense));
                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 4:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} (scrambled) response:", sectors + 5);

                    if(buffer != null)
                        PrintHex.PrintHexArray(scrambledBuffer, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 5:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} (scrambled) sense:", sectors + 5);

                    if(senseBuffer != null)
                        PrintHex.PrintHexArray(scrambledSense, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 6:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA {0} (scrambled) decoded sense:", sectors + 5);
                    AaruConsole.Write("{0}", Sense.PrettifySense(scrambledSense));
                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 7:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's PQ {0} response:", sectors + 5);

                    if(buffer != null)
                        PrintHex.PrintHexArray(pqBuffer, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 8:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's PQ {0} sense:", sectors + 5);

                    if(senseBuffer != null)
                        PrintHex.PrintHexArray(pqSense, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 9:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's PQ {0} decoded sense:", sectors + 5);
                    AaruConsole.Write("{0}", Sense.PrettifySense(pqSense));
                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 10:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's RW {0} response:", sectors + 5);

                    if(buffer != null)
                        PrintHex.PrintHexArray(rwBuffer, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 11:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's RW {0} sense:", sectors + 5);

                    if(senseBuffer != null)
                        PrintHex.PrintHexArray(rwSense, 64);

                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 12:
                    System.Console.Clear();
                    AaruConsole.WriteLine("Device: {0}", devPath);
                    AaruConsole.WriteLine("LBA's RW {0} decoded sense:", sectors + 5);
                    AaruConsole.Write("{0}", Sense.PrettifySense(rwSense));
                    AaruConsole.WriteLine("Press any key to continue...");
                    System.Console.ReadKey();

                    goto menu;
                case 13: goto start;
                default:
                    AaruConsole.WriteLine("Incorrect option. Press any key to continue...");
                    System.Console.ReadKey();
                    System.Console.Clear();

                    goto menu;
            }
        }
    }
}