// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename       : Pregap.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : CompactDisc dumping.
//
// --[ Description ] ----------------------------------------------------------
//
//     Calculates CompactDisc track pregaps.
//
// --[ License ] --------------------------------------------------------------
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as
//     published by the Free Software Foundation, either version 3 of the
//     License, or (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2023 Natalia Portillo
// ****************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Aaru.Checksums;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
using Aaru.Console;
using Aaru.Core.Logging;
using Aaru.Devices;

// ReSharper disable JoinDeclarationAndInitializer
// ReSharper disable InlineOutVariableDeclaration
// ReSharper disable TooWideLocalVariableScope

namespace Aaru.Core.Devices.Dumping
{
    partial class Dump
    {
        // TODO: Fix offset
        /// <summary>Reads the first track pregap from a CompactDisc</summary>
        /// <param name="blockSize">Block size in bytes</param>
        /// <param name="currentSpeed">Current speed</param>
        /// <param name="mediaTags">List of media tags</param>
        /// <param name="supportedSubchannel">Subchannel the drive can read</param>
        /// <param name="totalDuration">Total time spent sending commands to a drive</param>
        void ReadCdFirstTrackPregap(uint blockSize, ref double currentSpeed, Dictionary<MediaTagType, byte[]> mediaTags,
                                    MmcSubchannel supportedSubchannel, ref double totalDuration)
        {
            bool     sense;                           // Sense indicator
            byte[]   cmdBuf;                          // Data buffer
            double   cmdDuration;                     // Command execution time
            DateTime timeSpeedStart;                  // Time of start for speed calculation
            ulong    sectorSpeedStart            = 0; // Used to calculate correct speed
            bool     gotFirstTrackPregap         = false;
            int      firstTrackPregapSectorsGood = 0;
            var      firstTrackPregapMs          = new MemoryStream();

            _dumpLog.WriteLine("Reading first track pregap");
            UpdateStatus?.Invoke("Reading first track pregap");
            InitProgress?.Invoke();
            timeSpeedStart = DateTime.UtcNow;

            for(int firstTrackPregapBlock = -150; firstTrackPregapBlock < 0 && _resume.NextBlock == 0;
                firstTrackPregapBlock++)
            {
                if(_aborted)
                {
                    _dumpLog.WriteLine("Aborted!");
                    UpdateStatus?.Invoke("Aborted!");

                    break;
                }

                PulseProgress?.
                    Invoke($"Trying to read first track pregap sector {firstTrackPregapBlock} ({currentSpeed:F3} MiB/sec.)");

                // ReSharper disable IntVariableOverflowInUncheckedContext
                sense = _dev.ReadCd(out cmdBuf, out _, (uint)firstTrackPregapBlock, blockSize, 1,
                                    MmcSectorTypes.AllTypes, false, false, true, MmcHeaderCodes.AllHeaders, true, true,
                                    MmcErrorField.None, supportedSubchannel, _dev.Timeout, out cmdDuration);

                // ReSharper restore IntVariableOverflowInUncheckedContext

                if(!sense &&
                   !_dev.Error)
                {
                    firstTrackPregapMs.Write(cmdBuf, 0, (int)blockSize);
                    gotFirstTrackPregap = true;
                    firstTrackPregapSectorsGood++;
                    totalDuration += cmdDuration;
                }
                else
                {
                    // Write empty data
                    if(gotFirstTrackPregap)
                        firstTrackPregapMs.Write(new byte[blockSize], 0, (int)blockSize);
                }

                sectorSpeedStart++;

                double elapsed = (DateTime.UtcNow - timeSpeedStart).TotalSeconds;

                if(elapsed <= 0)
                    continue;

                currentSpeed     = sectorSpeedStart * blockSize / (1048576 * elapsed);
                sectorSpeedStart = 0;
                timeSpeedStart   = DateTime.UtcNow;
            }

            if(firstTrackPregapSectorsGood > 0)
                mediaTags.Add(MediaTagType.CD_FirstTrackPregap, firstTrackPregapMs.ToArray());

            EndProgress?.Invoke();
            UpdateStatus?.Invoke($"Got {firstTrackPregapSectorsGood} first track pregap sectors.");
            _dumpLog.WriteLine("Got {0} first track pregap sectors.", firstTrackPregapSectorsGood);

            firstTrackPregapMs.Close();
        }

        /// <summary>Calculate track pregaps</summary>
        /// <param name="dev">Device</param>
        /// <param name="dumpLog">Dumping log</param>
        /// <param name="updateStatus">Progress update callback</param>
        /// <param name="tracks">List of tracks</param>
        /// <param name="supportsPqSubchannel">Set if drive supports reading PQ subchannel</param>
        /// <param name="supportsRwSubchannel">Set if drive supports reading RW subchannel</param>
        /// <param name="dbDev">Database entry for device</param>
        /// <param name="inexactPositioning">Set if we found the drive does not return the exact subchannel we requested</param>
        /// <param name="dumping">Set if dumping, otherwise media info</param>
        public static void SolveTrackPregaps(Device dev, DumpLog dumpLog, UpdateStatusHandler updateStatus,
                                             Track[] tracks, bool supportsPqSubchannel, bool supportsRwSubchannel,
                                             Database.Models.Device dbDev, out bool inexactPositioning, bool dumping)
        {
            bool                  sense  = true; // Sense indicator
            byte[]                subBuf = null;
            int                   posQ;
            uint                  retries;
            bool?                 bcd = null;
            byte[]                crc;
            Dictionary<uint, int> pregaps = new Dictionary<uint, int>();
            inexactPositioning = false;

            if(!supportsPqSubchannel &&
               !supportsRwSubchannel)
                return;

            // Check if subchannel is BCD
            for(retries = 0; retries < 10; retries++)
            {
                sense = supportsRwSubchannel ? GetSectorForPregapRaw(dev, 11, dbDev, out subBuf, false)
                            : GetSectorForPregapQ16(dev, 11, out subBuf, false);

                if(sense)
                    continue;

                bcd = (subBuf[9] & 0x10) > 0;

                break;
            }

            AaruConsole.DebugWriteLine("Pregap calculator", bcd == true
                                                                ? "Subchannel is BCD"
                                                                : bcd == false
                                                                    ? "Subchannel is not BCD"
                                                                    : "Could not detect drive subchannel BCD");

            if(bcd is null)
            {
                dumpLog?.WriteLine("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect...");

                updateStatus?.
                    Invoke("Could not detect if drive subchannel is BCD or not, pregaps could not be calculated, dump may be incorrect...");

                return;
            }

            // Initialize the dictionary
            foreach(Track t in tracks)
                pregaps[t.TrackSequence] = 0;

            for(int t = 0; t < tracks.Length; t++)
            {
                Track track        = tracks[t];
                int   trackRetries = 0;

                // First track of each session has at least 150 sectors of pregap and is not always readable
                if(tracks.Where(trk => trk.TrackSession == track.TrackSession).OrderBy(trk => trk.TrackSequence).
                          FirstOrDefault().TrackSequence == track.TrackSequence)
                {
                    AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence);

                    if(track.TrackSequence > 1)
                        pregaps[track.TrackSequence] = 150;

                    continue;
                }

                if(t                       > 0                    &&
                   tracks[t - 1].TrackType == tracks[t].TrackType &&
                   dumping)
                {
                    AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0}", track.TrackSequence);

                    continue;
                }

                if(dumping && dev.Manufacturer.ToLowerInvariant().StartsWith("plextor", StringComparison.Ordinal))
                {
                    AaruConsole.DebugWriteLine("Pregap calculator", "Skipping track {0} due to Plextor firmware bug",
                                               track.TrackSequence);

                    continue;
                }

                AaruConsole.DebugWriteLine("Pregap calculator", "Track {0}", track.TrackSequence);

                int   lba           = (int)track.TrackStartSector - 1;
                bool  pregapFound   = false;
                Track previousTrack = tracks.FirstOrDefault(trk => trk.TrackSequence == track.TrackSequence - 1);

                bool goneBack                      = false;
                bool goFront                       = false;
                bool forward                       = false;
                bool crcOk                         = false;
                bool previousPregapIsPreviousTrack = false;

                // Check if pregap is 0
                for(retries = 0; retries < 10 && !pregapFound; retries++)
                {
                    sense = supportsRwSubchannel
                                ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf,
                                                        track.TrackType == TrackType.Audio)
                                : GetSectorForPregapQ16(dev, (uint)lba, out subBuf, track.TrackType == TrackType.Audio);

                    if(sense)
                    {
                        AaruConsole.DebugWriteLine("Pregap calculator", "LBA: {0}, Try {1}, Sense {2}", lba,
                                                   retries + 1, sense);

                        continue;
                    }

                    if(bcd == false)
                        BinaryToBcdQ(subBuf);

                    CRC16CCITTContext.Data(subBuf, 10, out crc);

                    AaruConsole.DebugWriteLine("Pregap calculator",
                                               "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}",
                                               lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3],
                                               subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9],
                                               subBuf[10], subBuf[11], crc[0], crc[1]);

                    crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11];

                    // Try to do a simple correction
                    if(!crcOk)
                    {
                        // Data track cannot have 11xxb in CONTROL
                        if((subBuf[0] & 0x40) > 0)
                            subBuf[0] &= 0x7F;

                        // ADR only uses two bits
                        subBuf[0] &= 0xF3;

                        // Don't care about other Q modes
                        if((subBuf[0] & 0xF) == 1)
                        {
                            // ZERO only used in DDCD
                            subBuf[6] = 0;

                            // Fix BCD numbering
                            for(int i = 1; i < 10; i++)
                            {
                                if((subBuf[i] & 0xF0) > 0xA0)
                                    subBuf[i] &= 0x7F;

                                if((subBuf[i] & 0x0F) > 0x0A)
                                    subBuf[i] &= 0xF7;
                            }
                        }

                        CRC16CCITTContext.Data(subBuf, 10, out crc);

                        crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11];

                        if(crcOk)
                        {
                            AaruConsole.DebugWriteLine("Pregap calculator",
                                                       "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}",
                                                       lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2],
                                                       subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8],
                                                       subBuf[9], subBuf[10], subBuf[11], crc[0], crc[1]);
                        }
                        else
                            continue;
                    }

                    BcdToBinaryQ(subBuf);

                    // Q position
                    if((subBuf[0] & 0xF) != 1)
                        continue;

                    posQ = (subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9] - 150;

                    if(subBuf[1] != track.TrackSequence - 1 ||
                       subBuf[2] == 0                       ||
                       posQ      != lba)
                        break;

                    pregaps[track.TrackSequence] = 0;

                    pregapFound = true;
                }

                if(pregapFound)
                    continue;

                // Calculate pregap
                lba = (int)track.TrackStartSector - 150;

                while(lba > (int)previousTrack.TrackStartSector &&
                      lba <= (int)track.TrackStartSector)
                {
                    // Some drives crash if you try to read just before the previous read, so seek away first
                    if(!forward)
                        sense = supportsRwSubchannel
                                    ? GetSectorForPregapRaw(dev, (uint)lba - 10, dbDev, out subBuf,
                                                            track.TrackType == TrackType.Audio)
                                    : GetSectorForPregapQ16(dev, (uint)lba - 10, out subBuf,
                                                            track.TrackType == TrackType.Audio);

                    for(retries = 0; retries < 10; retries++)
                    {
                        sense = supportsRwSubchannel
                                    ? GetSectorForPregapRaw(dev, (uint)lba, dbDev, out subBuf,
                                                            track.TrackType == TrackType.Audio)
                                    : GetSectorForPregapQ16(dev, (uint)lba, out subBuf,
                                                            track.TrackType == TrackType.Audio);

                        if(sense)
                            continue;

                        if(bcd == false)
                            BinaryToBcdQ(subBuf);

                        CRC16CCITTContext.Data(subBuf, 10, out crc);

                        AaruConsole.DebugWriteLine("Pregap calculator",
                                                   "LBA: {0}, Try {1}, Sense {2}, Q: {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}",
                                                   lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2], subBuf[3],
                                                   subBuf[4], subBuf[5], subBuf[6], subBuf[7], subBuf[8], subBuf[9],
                                                   subBuf[10], subBuf[11], crc[0], crc[1]);

                        crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11];

                        // Try to do a simple correction
                        if(!crcOk)
                        {
                            // Data track cannot have 11xxb in CONTROL
                            if((subBuf[0] & 0x40) > 0)
                                subBuf[0] &= 0x7F;

                            // ADR only uses two bits
                            subBuf[0] &= 0xF3;

                            // Don't care about other Q modes
                            if((subBuf[0] & 0xF) == 1)
                            {
                                // ZERO only used in DDCD
                                subBuf[6] = 0;

                                // Fix BCD numbering
                                for(int i = 1; i < 10; i++)
                                {
                                    if((subBuf[i] & 0xF0) > 0xA0)
                                        subBuf[i] &= 0x7F;

                                    if((subBuf[i] & 0x0F) > 0x0A)
                                        subBuf[i] &= 0xF7;
                                }
                            }

                            CRC16CCITTContext.Data(subBuf, 10, out crc);

                            crcOk = crc[0] == subBuf[10] && crc[1] == subBuf[11];

                            if(crcOk)
                            {
                                AaruConsole.DebugWriteLine("Pregap calculator",
                                                           "LBA: {0}, Try {1}, Sense {2}, Q (FIXED): {3:X2} {4:X2} {5:X2} {6:X2} {7:X2} {8:X2} {9:X2} {10:X2} {11:X2} {12:X2} CRC 0x{13:X2}{14:X2}, Calculated CRC: 0x{15:X2}{16:X2}",
                                                           lba, retries + 1, sense, subBuf[0], subBuf[1], subBuf[2],
                                                           subBuf[3], subBuf[4], subBuf[5], subBuf[6], subBuf[7],
                                                           subBuf[8], subBuf[9], subBuf[10], subBuf[11], crc[0],
                                                           crc[1]);

                                break;
                            }
                        }

                        if(crcOk)
                            break;
                    }

                    if(retries == 10)
                    {
                        if(sense)
                        {
                            trackRetries++;

                            if(trackRetries >= 10)
                            {
                                if(pregaps[track.TrackSequence] == 0)
                                {
                                    if((previousTrack.TrackType == TrackType.Audio &&
                                        track.TrackType         != TrackType.Audio) ||
                                       (previousTrack.TrackType != TrackType.Audio &&
                                        track.TrackType         == TrackType.Audio))
                                    {
                                        dumpLog?.
                                            WriteLine("Could not read subchannel for this track, supposing 150 sectors.");

                                        updateStatus?.
                                            Invoke("Could not read subchannel for this track, supposing 150 sectors.");
                                    }
                                    else
                                    {
                                        dumpLog?.
                                            WriteLine("Could not read subchannel for this track, supposing 0 sectors.");

                                        updateStatus?.
                                            Invoke("Could not read subchannel for this track, supposing 0 sectors.");
                                    }
                                }
                                else
                                {
                                    dumpLog?.
                                        WriteLine($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors.");

                                    updateStatus?.
                                        Invoke($"Could not read subchannel for this track, supposing {pregaps[track.TrackSequence]} sectors.");
                                }

                                break;
                            }

                            dumpLog?.WriteLine($"Could not read subchannel for sector {lba}");
                            updateStatus?.Invoke($"Could not read subchannel for sector {lba}");

                            lba++;
                            forward = true;

                            continue;
                        }

                        dumpLog?.WriteLine($"Could not get correct subchannel for sector {lba}");
                        updateStatus?.Invoke($"Could not get correct subchannel for sector {lba}");
                    }

                    if(subBuf.All(b => b == 0))
                    {
                        inexactPositioning = true;

                        AaruConsole.DebugWriteLine("Pregap calculator", "All Q empty for LBA {0}", lba);

                        break;
                    }

                    BcdToBinaryQ(subBuf);

                    // If it's not Q position
                    if((subBuf[0] & 0xF) != 1)
                    {
                        // This means we already searched back, so search forward
                        if(goFront)
                        {
                            lba++;
                            forward = true;

                            if(lba == (int)previousTrack.TrackStartSector)
                                break;

                            continue;
                        }

                        // Search back
                        goneBack = true;
                        lba--;
                        forward = false;

                        continue;
                    }

                    // Previous track
                    if(subBuf[1] < track.TrackSequence)
                    {
                        lba++;
                        forward                       = true;
                        previousPregapIsPreviousTrack = true;

                        // Already gone back, so go forward
                        if(goneBack)
                            goFront = true;

                        continue;
                    }

                    // Same track, but not pregap
                    if(subBuf[1] == track.TrackSequence &&
                       subBuf[2] > 0)
                    {
                        lba--;
                        forward = false;

                        if(previousPregapIsPreviousTrack)
                            break;

                        continue;
                    }

                    previousPregapIsPreviousTrack = false;

                    // Pregap according to Q position
                    posQ = (subBuf[7] * 60 * 75) + (subBuf[8] * 75) + subBuf[9] - 150;
                    int diff    = posQ                        - lba;
                    int pregapQ = (int)track.TrackStartSector - lba;

                    if(diff != 0)
                    {
                        AaruConsole.DebugWriteLine("Pregap calculator", "Invalid Q position for LBA {0}, got {1}", lba,
                                                   posQ);

                        inexactPositioning = true;
                    }

                    // Received a Q post the LBA we wanted, just go back. If we are already going forward, break
                    if(posQ > lba)
                    {
                        if(forward)
                            break;

                        lba--;

                        continue;
                    }

                    // Bigger than known change, otherwise we found it
                    if(pregapQ > pregaps[track.TrackSequence])
                    {
                        // If CRC is not OK, only accept pregaps less than 10 sectors longer than previously now
                        if(crcOk || pregapQ - pregaps[track.TrackSequence] < 10)
                        {
                            AaruConsole.DebugWriteLine("Pregap calculator", "Pregap for track {0}: {1}",
                                                       track.TrackSequence, pregapQ);

                            pregaps[track.TrackSequence] = pregapQ;
                        }

                        // We are going forward, so we have already been in the previous track, so add 1 to pregap and get out of here
                        else if(forward)
                        {
                            pregaps[track.TrackSequence]++;

                            break;
                        }
                    }
                    else if(pregapQ == pregaps[track.TrackSequence])
                        break;

                    lba--;
                    forward = false;
                }
            }

            foreach(Track trk in tracks)
            {
                trk.TrackPregap = (ulong)pregaps[trk.TrackSequence];

                // Do not reduce pregap, or starting position of session's first track
                if(tracks.Where(t => t.TrackSession == trk.TrackSession).OrderBy(t => t.TrackSequence).FirstOrDefault().
                          TrackSequence == trk.TrackSequence)
                    continue;

                if(dumping)
                {
                    // Minus five, to ensure dumping will fix if there is a pregap LBA 0
                    int red = 5;

                    while(trk.TrackPregap > 0 &&
                          red             > 0)
                    {
                        trk.TrackPregap--;
                        red--;
                    }
                }

                trk.TrackStartSector -= trk.TrackPregap;

            #if DEBUG
                dumpLog?.WriteLine($"Track {trk.TrackSequence} pregap is {trk.TrackPregap} sectors");
                updateStatus?.Invoke($"Track {trk.TrackSequence} pregap is {trk.TrackPregap} sectors");
            #endif
            }
        }

        /// <summary>Reads a RAW subchannel sector for pregap calculation</summary>
        /// <param name="dev">Device</param>
        /// <param name="lba">LBA</param>
        /// <param name="dbDev">Database entry for device</param>
        /// <param name="subBuf">Read subchannel</param>
        /// <param name="audioTrack">Set if it is an audio track</param>
        /// <returns><c>true</c> if read correctly, <c>false</c> otherwise</returns>
        static bool GetSectorForPregapRaw(Device dev, uint lba, Database.Models.Device dbDev, out byte[] subBuf,
                                          bool audioTrack)
        {
            byte[] cmdBuf;
            bool   sense;
            subBuf = null;

            if(audioTrack)
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 2448, 1, MmcSectorTypes.Cdda, false, false, false,
                                   MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.Raw, dev.Timeout,
                                   out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 2448, 1, MmcSectorTypes.AllTypes, false, false, true,
                                       MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Raw,
                                       dev.Timeout, out _);
            }
            else
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 2448, 1, MmcSectorTypes.AllTypes, false, false, true,
                                   MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Raw,
                                   dev.Timeout, out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 2448, 1, MmcSectorTypes.Cdda, false, false, false,
                                       MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.Raw,
                                       dev.Timeout, out _);
            }

            if(!sense)
            {
                byte[] tmpBuf = new byte[96];
                Array.Copy(cmdBuf, 2352, tmpBuf, 0, 96);
                subBuf = DeinterleaveQ(tmpBuf);
            }
            else
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 96, 1, MmcSectorTypes.AllTypes, false, false, false,
                                   MmcHeaderCodes.None, false, false, MmcErrorField.None, MmcSubchannel.Raw,
                                   dev.Timeout, out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 96, 1, MmcSectorTypes.Cdda, false, false, false,
                                       MmcHeaderCodes.None, false, false, MmcErrorField.None, MmcSubchannel.Raw,
                                       dev.Timeout, out _);

                if(!sense)
                {
                    subBuf = DeinterleaveQ(cmdBuf);
                }
                else if(dbDev?.ATAPI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA == true) == true ||
                        dbDev?.SCSI?.RemovableMedias?.Any(d => d.SupportsPlextorReadCDDA  == true) == true ||
                        dev.Manufacturer.ToLowerInvariant()                                        == "plextor")
                    sense = dev.PlextorReadCdDa(out cmdBuf, out _, lba, 96, 1, PlextorSubchannel.All, dev.Timeout,
                                                out _);

                {
                    if(!sense)
                        subBuf = DeinterleaveQ(cmdBuf);
                }
            }

            return sense;
        }

        /// <summary>Reads a Q16 subchannel sector for pregap calculation</summary>
        /// <param name="dev">Device</param>
        /// <param name="lba">LBA</param>
        /// <param name="subBuf">Read subchannel</param>
        /// <param name="audioTrack">Set if it is an audio track</param>
        /// <returns><c>true</c> if read correctly, <c>false</c> otherwise</returns>
        static bool GetSectorForPregapQ16(Device dev, uint lba, out byte[] subBuf, bool audioTrack)
        {
            byte[] cmdBuf;
            bool   sense;
            subBuf = null;

            if(audioTrack)
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 2368, 1, MmcSectorTypes.Cdda, false, false, false,
                                   MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.Q16, dev.Timeout,
                                   out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 2368, 1, MmcSectorTypes.AllTypes, false, false, true,
                                       MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Q16,
                                       dev.Timeout, out _);
            }
            else
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 2368, 1, MmcSectorTypes.AllTypes, false, false, true,
                                   MmcHeaderCodes.AllHeaders, true, true, MmcErrorField.None, MmcSubchannel.Q16,
                                   dev.Timeout, out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 2368, 1, MmcSectorTypes.Cdda, false, false, false,
                                       MmcHeaderCodes.None, true, false, MmcErrorField.None, MmcSubchannel.Q16,
                                       dev.Timeout, out _);
            }

            if(!sense)
            {
                subBuf = new byte[16];
                Array.Copy(cmdBuf, 2352, subBuf, 0, 16);
            }
            else
            {
                sense = dev.ReadCd(out cmdBuf, out _, lba, 16, 1, MmcSectorTypes.AllTypes, false, false, false,
                                   MmcHeaderCodes.None, false, false, MmcErrorField.None, MmcSubchannel.Q16,
                                   dev.Timeout, out _);

                if(sense)
                    sense = dev.ReadCd(out cmdBuf, out _, lba, 16, 1, MmcSectorTypes.Cdda, false, false, false,
                                       MmcHeaderCodes.None, false, false, MmcErrorField.None, MmcSubchannel.Q16,
                                       dev.Timeout, out _);

                if(!sense)
                    subBuf = cmdBuf;
            }

            return sense;
        }

        /// <summary>De-interleaves Q subchannel</summary>
        /// <param name="subchannel">Interleaved subchannel</param>
        /// <returns>De-interleaved Q subchannel</returns>
        static byte[] DeinterleaveQ(byte[] subchannel)
        {
            int[] q = new int[subchannel.Length / 8];

            // De-interlace Q subchannel
            for(int iq = 0; iq < subchannel.Length; iq += 8)
            {
                q[iq / 8] =  (subchannel[iq] & 0x40) << 1;
                q[iq / 8] += subchannel[iq + 1] & 0x40;
                q[iq / 8] += (subchannel[iq + 2] & 0x40) >> 1;
                q[iq / 8] += (subchannel[iq + 3] & 0x40) >> 2;
                q[iq / 8] += (subchannel[iq + 4] & 0x40) >> 3;
                q[iq / 8] += (subchannel[iq + 5] & 0x40) >> 4;
                q[iq / 8] += (subchannel[iq + 6] & 0x40) >> 5;
                q[iq / 8] += (subchannel[iq + 7] & 0x40) >> 6;
            }

            byte[] deQ = new byte[q.Length];

            for(int iq = 0; iq < q.Length; iq++)
            {
                deQ[iq] = (byte)q[iq];
            }

            return deQ;
        }

        /// <summary>In place converts Q subchannel from binary to BCD numbering</summary>
        /// <param name="q">Q subchannel</param>
        static void BinaryToBcdQ(byte[] q)
        {
            q[1] = (byte)(((q[1] / 10) << 4) + (q[1] % 10));
            q[2] = (byte)(((q[2] / 10) << 4) + (q[2] % 10));
            q[3] = (byte)(((q[3] / 10) << 4) + (q[3] % 10));
            q[4] = (byte)(((q[4] / 10) << 4) + (q[4] % 10));
            q[5] = (byte)(((q[5] / 10) << 4) + (q[5] % 10));
            q[6] = (byte)(((q[6] / 10) << 4) + (q[6] % 10));
            q[7] = (byte)(((q[7] / 10) << 4) + (q[7] % 10));
            q[8] = (byte)(((q[8] / 10) << 4) + (q[8] % 10));
            q[9] = (byte)(((q[9] / 10) << 4) + (q[9] % 10));
        }

        /// <summary>In place converts Q subchannel from BCD to binary numbering</summary>
        /// <param name="q">Q subchannel</param>
        static void BcdToBinaryQ(byte[] q)
        {
            q[1] = (byte)((q[1] / 16 * 10) + (q[1] & 0x0F));
            q[2] = (byte)((q[2] / 16 * 10) + (q[2] & 0x0F));
            q[3] = (byte)((q[3] / 16 * 10) + (q[3] & 0x0F));
            q[4] = (byte)((q[4] / 16 * 10) + (q[4] & 0x0F));
            q[5] = (byte)((q[5] / 16 * 10) + (q[5] & 0x0F));
            q[6] = (byte)((q[6] / 16 * 10) + (q[6] & 0x0F));
            q[7] = (byte)((q[7] / 16 * 10) + (q[7] & 0x0F));
            q[8] = (byte)((q[8] / 16 * 10) + (q[8] & 0x0F));
            q[9] = (byte)((q[9] / 16 * 10) + (q[9] & 0x0F));
        }
    }
}