/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#pragma once

//  CCdInfo   -  Information about media type of an inserted cd
//  CCdIoSupport -  Wrapper class for libcdio with the interface of CIoSupport
//     and detecting the filesystem on the Disc.
//
// by Bobbin007 in 2003
//  CD-Text support by Mog - Oct 2004

#include "threads/CriticalSection.h"

#include <map>
#include <memory>
#include <string>
#include <utility>

#include "PlatformDefs.h" // for ssize_t typedef, used by cdio

#include <cdio/cdio.h>

namespace MEDIA_DETECT
{

#define STRONG "__________________________________\n"
//#define NORMAL ""

#define FS_NO_DATA              0  /* audio only */
#define FS_HIGH_SIERRA          1
#define FS_ISO_9660             2
#define FS_INTERACTIVE          3
#define FS_HFS                  4
#define FS_UFS                  5
#define FS_EXT2                 6
#define FS_ISO_HFS              7  /* both hfs & isofs filesystem */
#define FS_ISO_9660_INTERACTIVE 8  /* both CD-RTOS and isofs filesystem */
#define FS_3DO                  9
#define FS_UDF                 11
#define FS_ISO_UDF             12
#define FS_UNKNOWN             15
#define FS_MASK                15

#define XA                     16
#define MULTISESSION           32
#define PHOTO_CD               64
#define HIDDEN_TRACK          128
#define CDTV                  256
#define BOOTABLE              512
#define VIDEOCDI             1024
#define ROCKRIDGE            2048
#define JOLIET               4096
#define CVD                  8192 /* Choiji Video CD */

#define IS_ISOFS      0
#define IS_CD_I       1
#define IS_CDTV       2
#define IS_CD_RTOS    3
#define IS_HS         4
#define IS_BRIDGE     5
#define IS_XA         6
#define IS_PHOTO_CD   7
#define IS_EXT2       8
#define IS_UFS        9
#define IS_BOOTABLE  10
#define IS_VIDEO_CD  11  /* Video CD */
#define IS_CVD       12  /* Chinese Video CD - slightly incompatible with SVCD */
#define IS_UDF       14

typedef struct signature
{
  unsigned int buf_num;
  unsigned int offset;
  const char *sig_str;
  const char *description;
}
signature_t;

typedef std::map<cdtext_field_t, std::string> xbmc_cdtext_t;

typedef struct TRACKINFO
{
  int nfsInfo;          // Information of the Tracks Filesystem
  int nJolietLevel;     // Jouliet Level
  int ms_offset;        // Multisession Offset
  int isofs_size;       // Size of the ISO9660 Filesystem
  int nFrames;          // Can be used for cddb query
  int nMins;            // minutes playtime part of Track
  int nSecs;            // seconds playtime part of Track
  xbmc_cdtext_t cdtext; // CD-Text for this track
}
trackinfo;

/*! \brief Helper enum class for the MMC tray state
*/
enum class CdioTrayStatus
{
  /* The MMC tray state is reported closed */
  CLOSED,
  /* The MMC tray state is reported open */
  OPEN,
  /* The MMC tray status operation is not supported */
  UNKNOWN,
  /* Generic driver error */
  DRIVER_ERROR
};

class CCdInfo
{
public:
  CCdInfo()
  {
    m_bHasCDDBInfo = true;
    m_nLength = m_nFirstTrack = m_nNumTrack = m_nNumAudio = m_nFirstAudio = m_nNumData = m_nFirstData = 0;
  }

  trackinfo GetTrackInformation( int nTrack ) { return m_ti[nTrack -1]; }
  xbmc_cdtext_t GetDiscCDTextInformation() { return m_cdtext; }

  bool HasDataTracks() { return (m_nNumData > 0); }
  bool HasAudioTracks() { return (m_nNumAudio > 0); }
  int GetFirstTrack() { return m_nFirstTrack; }
  int GetTrackCount() { return m_nNumTrack; }
  int GetFirstAudioTrack() { return m_nFirstAudio; }
  int GetFirstDataTrack() { return m_nFirstData; }
  int GetDataTrackCount() { return m_nNumData; }
  int GetAudioTrackCount() { return m_nNumAudio; }
  uint32_t GetCddbDiscId() { return m_ulCddbDiscId; }
  int GetDiscLength() { return m_nLength; }
  std::string GetDiscLabel(){ return m_strDiscLabel; }

  // CD-ROM with ISO 9660 filesystem
  bool IsIso9660( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_9660); }
  // CD-ROM with joliet extension
  bool IsJoliet( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & JOLIET) ? false : true; }
  // Joliet extension level
  int GetJolietLevel( int nTrack ) { return m_ti[nTrack - 1].nJolietLevel; }
  // ISO filesystem size
  int GetIsoSize( int nTrack ) { return m_ti[nTrack - 1].isofs_size; }
  // CD-ROM with rockridge extensions
  bool IsRockridge( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & ROCKRIDGE) ? false : true; }

  // CD-ROM with CD-RTOS and ISO 9660 filesystem
  bool IsIso9660Interactive( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_9660_INTERACTIVE); }

  // CD-ROM with High Sierra filesystem
  bool IsHighSierra( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_HIGH_SIERRA); }

  // CD-Interactive, with audiotracks > 0 CD-Interactive/Ready
  bool IsCDInteractive( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_INTERACTIVE); }

  // CD-ROM with Macintosh HFS
  bool IsHFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_HFS); }

  // CD-ROM with both Macintosh HFS and ISO 9660 filesystem
  bool IsISOHFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_HFS); }

  // CD-ROM with both UDF and ISO 9660 filesystem
  bool IsISOUDF( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_ISO_UDF); }

  // CD-ROM with Unix UFS
  bool IsUFS( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_UFS); }

  // CD-ROM with Linux second extended filesystem
  bool IsEXT2( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_EXT2); }

  // CD-ROM with Panasonic 3DO filesystem
  bool Is3DO( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_3DO); }

  // Mixed Mode CD-ROM
  bool IsMixedMode( int nTrack ) { return (m_nFirstData == 1 && m_nNumAudio > 0); }

  // CD-ROM with XA sectors
  bool IsXA( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & XA) ? false : true; }

  // Multisession CD-ROM
  bool IsMultiSession( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & MULTISESSION) ? false : true; }
  // Gets multisession offset
  int GetMultisessionOffset( int nTrack ) { return m_ti[nTrack - 1].ms_offset; }

  // Hidden Track on Audio CD
  bool IsHiddenTrack( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & HIDDEN_TRACK) ? false : true; }

  // Photo CD, with audiotracks > 0 Portfolio Photo CD
  bool IsPhotoCd( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & PHOTO_CD) ? false : true; }

  // CD-ROM with Commodore CDTV
  bool IsCdTv( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & CDTV) ? false : true; }

  // CD-Plus/Extra
  bool IsCDExtra( int nTrack ) { return (m_nFirstData > 1); }

  // Bootable CD
  bool IsBootable( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & BOOTABLE) ? false : true; }

  // Video CD
  bool IsVideoCd( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & VIDEOCDI && m_nNumAudio == 0); }

  // Chaoji Video CD
  bool IsChaojiVideoCD( int nTrack ) { return (m_ti[nTrack - 1].nfsInfo & CVD) ? false : true; }

  // Audio Track
  bool IsAudio( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_NO_DATA); }

  // UDF filesystem
  bool IsUDF( int nTrack ) { return ((m_ti[nTrack - 1].nfsInfo & FS_MASK) == FS_UDF); }

  // Has the cd a filesystem that is readable by the xbox
  bool IsValidFs() { return (IsISOHFS(1) || IsIso9660(1) || IsIso9660Interactive(1) || IsISOUDF(1) || IsUDF(1) || IsAudio(1)); }

  void SetFirstTrack( int nTrack ) { m_nFirstTrack = nTrack; }
  void SetTrackCount( int nCount ) { m_nNumTrack = nCount; }
  void SetFirstAudioTrack( int nTrack ) { m_nFirstAudio = nTrack; }
  void SetFirstDataTrack( int nTrack ) { m_nFirstData = nTrack; }
  void SetDataTrackCount( int nCount ) { m_nNumData = nCount; }
  void SetAudioTrackCount( int nCount ) { m_nNumAudio = nCount; }
  void SetTrackInformation(int nTrack, trackinfo nInfo)
  {
    if (nTrack > 0 && nTrack <= 99)
      m_ti[nTrack - 1] = std::move(nInfo);
  }
  void SetDiscCDTextInformation(xbmc_cdtext_t cdtext) { m_cdtext = std::move(cdtext); }

  void SetCddbDiscId( uint32_t ulCddbDiscId ) { m_ulCddbDiscId = ulCddbDiscId; }
  void SetDiscLength( int nLength ) { m_nLength = nLength; }
  bool HasCDDBInfo() { return m_bHasCDDBInfo; }
  void SetNoCDDBInfo() { m_bHasCDDBInfo = false; }

  void SetDiscLabel(const std::string& strDiscLabel){ m_strDiscLabel = strDiscLabel; }

private:
  int m_nFirstData;        /* # of first data track */
  int m_nNumData;          /* # of data tracks */
  int m_nFirstAudio;       /* # of first audio track */
  int m_nNumAudio;         /* # of audio tracks */
  int m_nNumTrack;
  int m_nFirstTrack;
  trackinfo m_ti[100];
  uint32_t m_ulCddbDiscId;
  int m_nLength;           // Disclength can be used for cddb query, also see trackinfo.nFrames
  bool m_bHasCDDBInfo;
  std::string m_strDiscLabel;
  xbmc_cdtext_t m_cdtext;  //  CD-Text for this disc
};

class CLibcdio : public CCriticalSection
{
private:
  CLibcdio();
public:
  virtual ~CLibcdio();

  static void ReleaseInstance();
  static std::shared_ptr<CLibcdio> GetInstance();

  // libcdio is not thread safe so these are wrappers to libcdio routines
  CdIo_t* cdio_open(const char *psz_source, driver_id_t driver_id);
  CdIo_t* cdio_open_win32(const char *psz_source);
  void cdio_destroy(CdIo_t *p_cdio);
  discmode_t cdio_get_discmode(CdIo_t *p_cdio);
  CdioTrayStatus mmc_get_tray_status(const CdIo_t* p_cdio);
  driver_return_code_t cdio_eject_media(CdIo_t** p_cdio);
  track_t cdio_get_last_track_num(const CdIo_t *p_cdio);
  lsn_t cdio_get_track_lsn(const CdIo_t *p_cdio, track_t i_track);
  lsn_t cdio_get_track_last_lsn(const CdIo_t *p_cdio, track_t i_track);
  driver_return_code_t cdio_read_audio_sectors(const CdIo_t *p_cdio, void *p_buf, lsn_t i_lsn, uint32_t i_blocks);
  driver_return_code_t cdio_close_tray(const char* psz_source, driver_id_t* driver_id);
  const char* cdio_driver_errmsg(driver_return_code_t drc);

  char* GetDeviceFileName();

private:
  char* s_defaultDevice;
  CCriticalSection m_critSection;
  static std::shared_ptr<CLibcdio> m_pInstance;
};

class CCdIoSupport
{
public:
  CCdIoSupport();
  virtual ~CCdIoSupport();

  bool EjectTray();
  bool CloseTray();

  HANDLE OpenCDROM();
  HANDLE OpenIMAGE( std::string& strFilename );
  int ReadSector(HANDLE hDevice, DWORD dwSector, char* lpczBuffer);
  int ReadSectorMode2(HANDLE hDevice, DWORD dwSector, char* lpczBuffer);
  int ReadSectorCDDA(HANDLE hDevice, DWORD dwSector, char* lpczBuffer);
  void CloseCDROM(HANDLE hDevice);

  void PrintAnalysis(int fs, int num_audio);

  CCdInfo* GetCdInfo(char* cDeviceFileName=NULL);
  void GetCdTextInfo(xbmc_cdtext_t &xcdt, int trackNum);

protected:
  int ReadBlock(int superblock, uint32_t offset, uint8_t bufnum, track_t track_num);
  bool IsIt(int num);
  int IsHFS(void);
  int Is3DO(void);
  int IsJoliet(void);
  int IsUDF(void);
  int GetSize(void);
  int GetJolietLevel( void );
  int GuessFilesystem(int start_session, track_t track_num);

  uint32_t CddbDiscId();
  int CddbDecDigitSum(int n);
  unsigned int MsfSeconds(msf_t *msf);

private:
  char buffer[7][CDIO_CD_FRAMESIZE_RAW];  /* for CD-Data */
  static signature_t sigs[17];
  int i = 0, j = 0;                               /* index */
  int m_nStartTrack;                      /* first sector of track */
  int m_nIsofsSize;                       /* size of session */
  int m_nJolietLevel;
  int m_nMsOffset;                        /* multisession offset found by track-walking */
  int m_nDataStart;                       /* start of data area */
  int m_nFs;
  int m_nUDFVerMinor;
  int m_nUDFVerMajor;

  CdIo* cdio;
  track_t m_nNumTracks = CDIO_INVALID_TRACK;
  track_t m_nFirstTrackNum = CDIO_INVALID_TRACK;

  std::string m_strDiscLabel;

  int m_nFirstData;        /* # of first data track */
  int m_nNumData;          /* # of data tracks */
  int m_nFirstAudio;       /* # of first audio track */
  int m_nNumAudio;         /* # of audio tracks */

  std::shared_ptr<CLibcdio> m_cdio;
};

}
