/*
 *
 * $Id: k3bdevice.h 679274 2007-06-23 13:23:58Z trueg $
 * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * 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 2 of the License, or
 * (at your option) any later version.
 * See the file "COPYING" for the exact licensing terms.
 */


#ifndef K3BDEVICE_H
#define K3BDEVICE_H

#include <qstringlist.h>
#include <qvaluelist.h>
#include <qglobal.h>

#include <k3bdevicetypes.h>
#include <k3bdiskinfo.h>
#include <k3bcdtext.h>
#include <k3bmsf.h>
#include <k3bdevice_export.h>

#ifdef Q_OS_FREEBSD
struct cam_device;
#endif

namespace K3bDevice
{
  class Toc;

  /**
   * \brief The main class representing a device.
   *
   * Devices are constructed by the DeviceManager.
   *
   * All methods except for open and close in Device are thread-safe which basicly means that
   * no two commands are sent to the device at the same time.
   */
  // FIXME: all methods are const which makes no sense at all!
  class LIBK3BDEVICE_EXPORT Device
    {
    public:
#ifdef Q_OS_FREEBSD
      typedef struct cam_device* Handle;
#else
    // file descriptor
      typedef int Handle;
#endif

      /**
       * The available cdrdao drivers
       * \deprecated This will be moved to libk3b
       */
      static const char* cdrdao_drivers[];

      // FIXME: make this protected
      ~Device();

      /**
       * The interface type.
       *
       * \return K3bDevice::SCSI or K3bDevice::IDE.
       */
      Interface interfaceType() const;

      /**
       * \deprecated use readCapabilities() and writeCapabilities()
       * The device type.
       *
       * @return A bitwise or of K3bDevice::DeviceType.
       */
      int type() const;

      /**
       * The mediatypes this device is able to read.
       *
       * \return A bitwise or of K3bDevice::MediaType
       */
      int readCapabilities() const;

      /**
       * The media types this device is able to write.
       *
       * \return A bitwise or of K3bDevice::MediaType
       */      
      int writeCapabilities() const;

      /**
       * \return Vendor string as reported by the device's firmware.
       */
      const QString& vendor() const { return m_vendor; }

      /**
       * \return Description string as reported by the device's firmware.
       */
      const QString& description() const { return m_description; }

      /**
       * \return Version string as reported by the device's firmware.
       */
      const QString& version() const { return m_version; }

      /**
       * Shortcut for \code writesCd() || writesDvd() \endcode
       *
       * \return true if the device is able to burn media.
       */
      bool burner() const;

      /**
       * Shortcut for \code type() & DEVICE_CD_R \endcode
       *
       * \return true if the device is able to burn CD-R media.
       */
      bool writesCd() const;

      /**
       * Shortcut for \code type() & DEVICE_CD_RW \endcode
       *
       * \return true if the device is able to burn CD-RW media.
       */
      bool writesCdrw() const;

      /**
       * Shortcut for \code writesDvdMinus() || writesDvdPlus() \endcode
       *
       * \return true if the device is able to burn DVD media.
       */
      bool writesDvd() const;


      /**
       * Shortcut for \code type() & (DEVICE_DVD_PLUS_R|DEVICE_DVD_PLUS_RW) \endcode
       *
       * \return true if the device is able to burn DVD+R or DVD+RW media.
       */
      bool writesDvdPlus() const;

      /**
       * Shortcut for \code type() & (DEVICE_DVD_R|DEVICE_DVD_RW) \endcode
       *
       * \return true if the device is able to burn DVD-R or DVD-RW media.
       */
      bool writesDvdMinus() const;

      /**
       * Shortcut for \code type() & DEVICE_DVD_ROM \endcode
       *
       * \return true if the device is able to read DVD media.
       */
      bool readsDvd() const;

      /**
       * @deprecated Use burnfree()
       */
      bool burnproof() const;

      /**
       * @return true is the device is a writer and supports buffer underrun free recording (BURNFREE)
       */
      bool burnfree() const;

      /**
       * Shortcut for \code writingModes() & WRITINGMODE_SAO \endcode
       *
       * \deprecated use supportsWritingMode()
       */
      bool dao() const;

      /**
       * Check if the device supports a certain writing mode.
       *
       * \return true if the device supports the requested writing mode or false otherwise.
       */
      bool supportsWritingMode( WritingMode mode ) const { return (m_writeModes & mode); }

      /**
       * Shortcut for 
       * \code
       *  writingModes() & (WRITINGMODE_RAW|WRITINGMODE_RAW_R16|WRITINGMODE_RAW_R96P|WRITINGMODE_RAW_R96R)
       * \endcode
       */
      bool supportsRawWriting() const;

      /**
       * @return true if the device is a DVD-R(W) writer which supports test writing.
       */
      bool dvdMinusTestwrite() const { return m_dvdMinusTestwrite; }

      int maxReadSpeed() const { return m_maxReadSpeed; }
      int currentWriteSpeed() const { return m_currentWriteSpeed; }

      /**
       * Size of the device's internal writing buffer.
       *
       * \return The size of the buffer in KB.
       */
      int bufferSize() const { return m_bufferSize; }

      /**
       * @return the corresponding device name.
       */
      const QString& devicename() const;

      /**
       * for SCSI devices this should be something like /dev/scd0 or /dev/sr0
       * for IDE device this should be something like /dev/hdb1
       */
      const QString& blockDeviceName() const { return m_blockDevice; }

      /**
       * This is only valid for SCSI devices. Without devfs it's something
       * like /dev/sg0. Otherwise something like /dev/scsi/host0/bus0/target0/lun0/generic.
       *
       * This is not needed in K3b at all. But cdrecord and cdrdao use the sg devices and
       * we need it to fixup it's permissions in K3bSetup.
       */
      const QString& genericDevice() const { return m_genericDevice; }

      /**
       * \return All device nodes for this drive.
       */
      const QStringList& deviceNodes() const;

      /**
       * \see K3bDevice::Device::deviceNodes()
       */
      void addDeviceNode( const QString& );

      /**
       * Makes only sense to use with scsi devices
       * @return a string for use with the cdrtools
       * @deprecated
       */
      QString busTargetLun() const;

      int scsiBus() const { return m_bus; }
      int scsiId() const { return m_target; }
      int scsiLun() const { return m_lun; }

      int maxWriteSpeed() const { return m_maxWriteSpeed; }

      /**
       * \deprecated the cdrdao driver has no place in this library. It will be removed.
       */
      const QString& cdrdaoDriver() const { return m_cdrdaoDriver; }

      /**
       * returns: 0 auto (no cdrdao-driver selected)
       *          1 yes
       *          2 no
       *
       * \deprecated cdrdao specific stuff has no place in this library. It will be removed.
       */
      int cdTextCapable() const;

      /**
       * internal K3b value.
       * \deprecated This should not be handled here.
       */
      void setCurrentWriteSpeed( int s ) { m_currentWriteSpeed = s; }

      /**
       * Use this if the speed was not detected correctly.
       */
      void setMaxReadSpeed( int s ) { m_maxReadSpeed = s; }

      /**
       * Use this if the speed was not detected correctly.
       */
      void setMaxWriteSpeed( int s ) { m_maxWriteSpeed = s; }

      /**
       * Use this if cdrdao is not able to autodetect the nessessary driver.
       * \deprecated the cdrdao driver has no place in this library. It will be removed.
       */
      void setCdrdaoDriver( const QString& d ) { m_cdrdaoDriver = d; }

      /**
       * Only used if the cdrdao-driver is NOT set to "auto".
       * In that case it must be manually set because there
       * is no way to autosense the cd-text capability.
       *
       * \deprecated the cdrdao driver has no place in this library. It will be removed.
       */
      void setCdTextCapability( bool );

      /**
       * checks if unit is ready (medium inserted and ready for command)
       *
       * Refers to the MMC command: TEST UNIT READY
       */
      bool testUnitReady() const;

      /**
       * checks if disk is empty, returns @p K3bDevice::State
       */
      int isEmpty() const;

      /**
       * @return true if inserted media is rewritable.
       */
      bool rewritable() const;

      /**
       * Check if the inserted media is a DVD.
       *
       * \return true if the inserted media is a DVD.
       */
      bool isDVD() const;

      /**
       * @return The number of sessions on the media.
       */
      int numSessions() const;

      /**
       * @return The toc of the media or an empty (invalid) K3bDevice::Toc if 
       *         no or an empty media is inserted.
       */
      Toc readToc() const;

      /**
       * Append ISRC and MCN to the TOC if found
       * This has been moved to a separate method since it can take a very long time
       * to scan for all ISRCs.
       */
      void readIsrcMcn( Toc& toc ) const;

      /**
       * Read the CD-TEXT of an audio or mixed-mode CD.
       *
       * \return A CdText object filled with the CD-TEXT values or an empty one in case of
       *         pure data media or if the CD does not contain CD-TEXT.
       */
      CdText readCdText() const;

      /**
       * @return The K3bDevice::Track::DataMode of the track.
       * @see K3bDevice::Track
       */
      int getTrackDataMode( const Track& track ) const;

      /**
       * @return the mode of a data track. K3bDevice::Track::MODE1, K3bDevice::Track::MODE2, 
       *         K3bDevice::Track::XA_FORM1, or K3bDevice::Track::XA_FORM2.
       */
      int getDataMode( const K3b::Msf& sector ) const;

      /**
       * block or unblock the drive's tray
       * \return true on success and false on error.
       * \see eject()
       */
      bool block( bool ) const;

      /**
       * Eject the media.
       * \return true on success and false on error.
       * \see load()
       */
      bool eject() const;

      /**
       * Load the media.
       * @return true on success and false on error.
       */
      bool load() const;

      /**
       * Enable or disable auto-ejecting. For now this is a no-op on non-Linux systems.
       * \param enabled if true auto-ejecting will be enabled, otherwise disabled.
       * \return true if the operation was successful, false otherwise
       */
      bool setAutoEjectEnabled( bool enabled ) const;

      /**
       * The supported writing modes.
       *
       * \return A bitwise or of K3bDevice::WritingMode or 0 in case of a read-only device.
       */
      int writingModes() const { return m_writeModes; }

      bool readSectorsRaw(unsigned char *buf, int start, int count) const;

      /**
       * Get a list of supported profiles. See enumeration MediaType.
       */
      int supportedProfiles() const;

      /**
       * Tries to get the current profile from the drive.
       * @returns -1 on error (command failed or unknown profile)
       *          MediaType otherwise (MEDIA_NONE means: no current profile)
       */
      int currentProfile() const;

      /**
       * Check if a certain feature is current.
       * \see k3bdevicetypes.h for feature constants.
       * \return 1 if the feature is current, 0 if not, -1 on error
       */
      int featureCurrent( unsigned int feature ) const;

      /**
       * This is the method to use!
       */
      DiskInfo diskInfo() const;

      /**
       * Refers to MMC command READ CAPACITY
       */
      bool readCapacity( K3b::Msf& ) const;

      /**
       * Refers to MMC command READ FORMAT CAPACITY
       *
       * @param wantedFormat The requested format type.
       * @param result If true is returned this contains the requested value.
       * @param currentMax If not 0 this will be filled with the Current/Maximum Descriptor value.
       * @param currentMax If not 0 this will be filled with the Current/Maximum Format Type.
       */
      bool readFormatCapacity( int wantedFormat, K3b::Msf& result,
			       K3b::Msf* currentMax = 0, int* currentMaxFormat = 0 ) const;

      /**
       * Determine the type of the currently mounted medium
       *
       * @returns K3bDevice::MediaType
       */
      int mediaType() const;

      /**
       * Returnes the list of supported writing speeds as reported by
       * mode page 2Ah.
       *
       * This only works with MMC3 compliant drives.
       */
      QValueList<int> determineSupportedWriteSpeeds() const;

      /**
       * @returnes the speed in kb/s or 0 on failure.
       */
      int determineMaximalWriteSpeed() const;

      /**
       * Open the device for access via a file descriptor.
       * @return true on success or if the device is already open.
       * @see close()
       *
       * Be aware that this method is not thread-safe.
       */
      bool open( bool write = false ) const;

      /**
       * Close the files descriptor.
       * @see open()
       *
       * Be aware that this method is not thread-safe.
       */
      void close() const;

      /**
       * @return true if the device was successfully opened via @p open()
       */
      bool isOpen() const;

      /**
       * fd on linux, cam on bsd
       */
      Handle handle() const;

      /**
       * \return \li -1 on error (no DVD)
       *         \li 1 (CSS/CPPM)
       *         \li 2 (CPRM) if scrambled
       *         \li 0 otherwise
       */
      int copyrightProtectionSystemType() const;

      // MMC commands

      /**
       * SET SPEED command
       *
       * @param readingSpeed The preferred reading speed (0x0000-0xFFFE). 0xFFFF requests
       *                     fot the logical unit to select the optimal speed.
       * @param writingSpeed The preferred writing speed (0x0000-0xFFFE). 0xFFFF requests
       *                     fot the logical unit to select the optimal speed.
       * @param cav Is the speed pure CAV?
       */
      bool setSpeed( unsigned int readingSpeed,
		     unsigned int writingSpeed,
		     bool cav = false ) const;

      /**
       * if true is returned dataLen specifies the actual length of *data which needs to be
       * deleted after using.
       */
      bool readDiscInformation( unsigned char** data, unsigned int& dataLen ) const;

      /**
       * @param pf If false all fields in the descriptor data is vendor specific. Default should be true.
       */
      bool modeSelect( unsigned char* page, unsigned int pageLen, bool pf, bool sp ) const;

      /**
       * if true is returned pageLen specifies the actual length of *pageData which needs to be
       * deleted after using.
       */
      bool modeSense( unsigned char** pageData, unsigned int& pageLen, int page ) const;

      /**
       * if true is returned dataLen specifies the actual length of *data which needs to be
       * deleted after using.
       */
      bool readTocPmaAtip( unsigned char** data, unsigned int& dataLen, int format, bool msf, int track ) const;

      /**
       * @param type specifies what value means:
       *        \li 00b - value refers to a logical block address
       *        \li 01b - value refers to a track number where 0 will treat the lead-in as if it
       *                  were a logical track and ffh will read the invisible or incomplete track.
       *        \li 10b - value refers to a session number
       *
       */
      bool readTrackInformation( unsigned char** data, unsigned int& dataLen, int type, int value ) const;

      /**
       * if true is returned dataLen specifies the actual length of *data which needs to be
       * deleted after using.
       */
      bool readDiscStructure( unsigned char** data, unsigned int& dataLen, 
			      unsigned int mediaType = 0x0,
			      unsigned int format = 0x0,
			      unsigned int layer = 0x0,
			      unsigned long adress = 0,
			      unsigned int agid = 0x0 ) const;

      /**
       * In MMC5 readDvdStructure was renamed to readDiscStructure. This method does the same
       * like the above.
       */
      bool readDvdStructure( unsigned char** data, unsigned int& dataLen, 
			     unsigned int format = 0x0,
			     unsigned int layer = 0x0,
			     unsigned long adress = 0,
			     unsigned int agid = 0x0 ) const;

      /**
       * if true is returned dataLen specifies the actual length of *data which needs to be
       * deleted after using.
       */
      bool mechanismStatus( unsigned char** data, unsigned int& dataLen ) const;

      /**
       * Read a single feature.
       * data will be filled with the feature header and the descriptor
       */
      bool getFeature( unsigned char** data, unsigned int& dataLen, unsigned int feature ) const;


      /**
       * if true is returned dataLen specifies the actual length of *data which needs to be
       * deleted after using.
       */
      bool getPerformance( unsigned char** data, unsigned int& dataLen,
			   unsigned int type,
			   unsigned int dataType,
			   unsigned int lba = 0 ) const;

      /**
       * @param sectorType: \li 000b - all types
       *                    \li 001b - CD-DA
       *                    \li 010b - Mode 1
       *                    \li 011b - Mode 2 formless
       *                    \li 100b - Mode 2 form 1
       *                    \li 101b - Mode 2 form 2
       *
       * @param startAdress Lba 0 is mapped to msf 00:00:00 so this method uses
       *                    startAdress+150 as the starting msf.
       *
       * @param endAdress This is the ending address which is NOT included in the read operation.
       *                  Lba 0 is mapped to msf 00:00:00 so this method uses
       *                  endAdress+150 as the ending msf.
       *
       * @param c2:         \li 00b  - No error info
       *                    \li 01b  - 294 bytes, one bit for every byte of the 2352 bytes
       *                    \li 10b  - 296 bytes, xor of all c2 bits, zero pad bit, 294 c2 bits
       *
       * @param subChannel: \li 000b - No Sub-channel data
       *                    \li 001b - RAW P-W Sub-channel (96 bytes)
       *                    \li 010b - Formatted Q Sub-channel (16 bytes)
       *                    \li 100b - Corrected and de-interleaved R-W Sub-channel (96 bytes)
       */
      bool readCdMsf( unsigned char* data,
		      unsigned int dataLen,
		      int sectorType,
		      bool dap,
		      const K3b::Msf& startAdress,
		      const K3b::Msf& endAdress,
		      bool sync,
		      bool header,
		      bool subHeader,
		      bool userData,
		      bool edcEcc,
		      int c2,
		      int subChannel ) const;

      /**
       * @param sectorType: \li 000b - all types
       *                    \li 001b - CD-DA
       *                    \li 010b - Mode 1
       *                    \li 011b - Mode 2 formless
       *                    \li 100b - Mode 2 form 1
       *                    \li 101b - Mode 2 form 2
       *
       * @param c2:         \li 00b  - No error info
       *                    \li 01b  - 294 bytes, one bit for every byte of the 2352 bytes
       *                    \li 10b  - 296 bytes, xor of all c2 bits, zero pad bit, 294 c2 bits
       *
       * @param subChannel: \li 000b - No Sub-channel data
       *                    \li 001b - RAW P-W Sub-channel (96 bytes)
       *                    \li 010b - Formatted Q Sub-channel (16 bytes)
       *                    \li 100b - Corrected and de-interleaved R-W Sub-channel (96 bytes)
       */
      bool readCd( unsigned char* data,
		   unsigned int dataLen,
		   int sectorType,
		   bool dap,
		   unsigned long startAdress,
		   unsigned long length,
		   bool sync,
		   bool header,
		   bool subHeader,
		   bool userData,
		   bool edcEcc,
		   int c2,
		   int subChannel ) const;

      bool read10( unsigned char* data,
		   unsigned int dataLen,
		   unsigned long startAdress,
		   unsigned int length,
		   bool fua = false ) const;

      bool read12( unsigned char* data,
		   unsigned int dataLen,
		   unsigned long startAdress,
		   unsigned long length,
		   bool streaming = false,
		   bool fua = false ) const;

      /**
       * @param subchannelParam: 01h - CD current position
       *                         02h - Media Catalog number (UPC/bar code)
       *                         03h - ISRC
       * @param trackNumber only valid if subchannelParam == 03h
       */
      bool readSubChannel( unsigned char** data,
			   unsigned int& dataLen,
			   unsigned int subchannelParam,
			   unsigned int trackNumber ) const;

      bool readIsrc( unsigned int track, QCString& isrc ) const;

      bool readMcn( QCString& mcn ) const;

      /**
       * MMC command Read Buffer Capacity
       *
       * \return \see K3bScsiCommand::transport()
       */
      int readBufferCapacity( long long& bufferLength, long long& bufferAvail ) const;

      /**
       * @returns the index number on success
       *          -1 on general error
       *          and -2 if there is no index info in that frame
       */
      int getIndex( unsigned long lba ) const;

      bool searchIndex0( unsigned long startSec, unsigned long endSec, long& pregapStart ) const;

      /**
       * For now this just searches index 0 for all tracks and sets
       * the value in the tracks.
       * In the future this should scan for all indices.
       */
      bool indexScan( K3bDevice::Toc& toc ) const;

      /**
       * Seek to the specified sector.
       */
      bool seek( unsigned long lba ) const;

      bool getNextWritableAdress( unsigned int& lastSessionStart, unsigned int& nextWritableAdress ) const;

      /**
       * Retrieve the next writable address from the currently mounted writable medium.
       * \return The next writable address if the medium is empty or appendable or -1
       * if an error occured.
       */
      int nextWritableAddress() const;

      /**
       * Locks the device for usage. This means that no MMC command can be performed
       * until usageUnlock is called.
       *
       * Locking a device is useful when an external application or library is called
       * that opens the device itself.
       *
       * \sa usageUnlock
       */
      void usageLock() const;

      /**
       * Unlock the device after a call to usageLock.
       */
      void usageUnlock() const;

      /**
       * Thread-safe ioctl call for this device for Linux and Net-BSD systems.
       * Be aware that so far this does not include opening the device
       */
//      int ioctl( int request, ... ) const;

    protected:
      bool furtherInit();

#ifdef Q_OS_LINUX
      /**
       * Fallback method that uses the evil cdrom.h stuff
       */
      bool readTocLinux( Toc& ) const;
#endif

      /**
       * The preferred toc reading method for all CDs. Also reads session info.
       * undefined for DVDs.
       */
      bool readRawToc( Toc& ) const;
      bool readFormattedToc( Toc&, int mediaType ) const;

      /**
       * Fixes the last block on CD-Extra disks. This is needed if the readRawToc failed since
       * in that case the first sector of the last session's first track is used as the previous
       * session's last track's last sector which is wrong. There is a 11400 block session lead-in
       * between them. This method fixes this only for the last session and only on linux.
       */
      bool fixupToc( Toc& ) const;

    private:
      /**
       * A Device can only be constructed the the DeviceManager.
       */
      Device( const QString& devname );

      /**
       * Determines the device's capabilities. This needs to be called once before
       * using the device.
       *
       * Should only be used by the DeviceManager.
       *
       * @param checkWritingModes if true the CD writing modes will be checked using 
       *                          MMC_MODE_SELECT.
       */
      bool init( bool checkWritingModes = true );

      void searchIndexTransitions( long start, long end, K3bDevice::Track& track ) const;
      void checkWritingModes();
      void checkFeatures();
      void checkForJustLink();
      void checkFor2AFeatures();
      void checkForAncientWriters();

      /**
       * Internal method which checks if the raw toc data has bcd values or hex.
       * @return 0 if hex, 1 if bcd, -1 if none
       */
      int rawTocDataWithBcdValues( unsigned char* data, unsigned int dataLen ) const;

      bool getSupportedWriteSpeedsVia2A( QValueList<int>& list, bool dvd ) const;
      bool getSupportedWriteSpeedsViaGP( QValueList<int>& list, bool dvd ) const;

      QCString mediaId( int mediaType ) const;

      QString m_vendor;
      QString m_description;
      QString m_version;
      QString m_cdrdaoDriver;
      int m_cdTextCapable;
      int m_maxReadSpeed;
      int m_maxWriteSpeed;
      int m_currentWriteSpeed;

      bool m_dvdMinusTestwrite;

      // only needed for scsi devices
      int m_bus;
      int m_target;
      int m_lun;

      int m_bufferSize;

      int m_writeModes;

      // only needed on FreeBSD
      QString m_passDevice;
      QString m_blockDevice;
      QString m_genericDevice;

      class Private;
      Private* d;
      friend class DeviceManager;
    };

#if defined(Q_OS_LINUX) || defined(Q_OS_NETBSD)
  /**
   * This should always be used to open a device since it
   * uses the resmgr
   *
   * @internal
   */
  int openDevice( const char* name, bool write = false );
#endif
}

#endif
