/*
  Copyright (C) 2004-2005, 2008, 2010-2011, 2013, 2017
   Rocky Bernstein <rocky@gnu.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 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/>.
*/

/*! Common image routines.

  Because _img_private_t may vary over image formats, the routines are
  included into the image drivers after _img_private_t is defined.  In
  order for the below routines to work, there is a large part of
  _img_private_t that is common among image drivers. For example, see
  image.h
*/

#include "image.h"
#include "image_common.h"
#include <cdio/util.h>
#include "_cdio_stdio.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

/*!
  Eject media -- there's nothing to do here except free resources.
  We always return DRIVER_OP_UNSUPPORTED.
 */
driver_return_code_t
_eject_media_image(void *p_user_data)
{
  _free_image (p_user_data);
  return DRIVER_OP_UNSUPPORTED;
}

/*!
  We don't need the image any more. Free all memory associated with
  it.
 */
void
_free_image (void *p_user_data)
{
  _img_private_t *p_env = p_user_data;
  track_t i_track;

  if (NULL == p_env) return;

  for (i_track=0; i_track < p_env->gen.i_tracks; i_track++) {
    track_info_t *p_tocent = &(p_env->tocent[i_track]);
    CDIO_FREE_IF_NOT_NULL(p_tocent->filename);
    CDIO_FREE_IF_NOT_NULL(p_tocent->isrc);
    if (p_tocent->data_source) cdio_stdio_destroy(p_tocent->data_source);
  }

  CDIO_FREE_IF_NOT_NULL(p_env->psz_mcn);
  CDIO_FREE_IF_NOT_NULL(p_env->psz_cue_name);
  CDIO_FREE_IF_NOT_NULL(p_env->psz_access_mode);
  cdtext_destroy(p_env->gen.cdtext);
  cdio_generic_stdio_free(p_env);
  free(p_env);
}

/*!
  Return the value associated with the key "arg".
*/
const char *
_get_arg_image (void *user_data, const char key[])
{
  _img_private_t *p_env = user_data;

  if (!strcmp (key, "source")) {
    return p_env->gen.source_name;
  } else if (!strcmp (key, "cue")) {
    return p_env->psz_cue_name;
  } else if (!strcmp(key, "access-mode")) {
    return "image";
  } else if (!strcmp (key, "mmc-supported?")) {
    return "false";
  }
  return NULL;
}

/*!
  Return CD-Text object
 */
cdtext_t *
_get_cdtext_image (void *user_data)
{
  generic_img_private_t *p_env = user_data;

  if(!p_env)
    return NULL;

  return p_env->cdtext;
}

/*!
  Get disc type associated with cd_obj.
*/
discmode_t
_get_discmode_image (void *p_user_data)
{
  _img_private_t *p_env = p_user_data;
  return p_env->disc_mode;
}

/*!
  Return the the kind of drive capabilities of device.

 */
void
_get_drive_cap_image (const void *user_data,
		      cdio_drive_read_cap_t  *p_read_cap,
		      cdio_drive_write_cap_t *p_write_cap,
		      cdio_drive_misc_cap_t  *p_misc_cap)
{

  *p_read_cap  = CDIO_DRIVE_CAP_READ_CD_DA
    | CDIO_DRIVE_CAP_READ_CD_G
    | CDIO_DRIVE_CAP_READ_CD_R
    | CDIO_DRIVE_CAP_READ_CD_RW
    | CDIO_DRIVE_CAP_READ_MODE2_FORM1
    | CDIO_DRIVE_CAP_READ_MODE2_FORM2
    | CDIO_DRIVE_CAP_READ_MCN
    ;

  *p_write_cap = 0;

  /* In the future we may want to simulate
     LOCK, OPEN_TRAY, CLOSE_TRAY, SELECT_SPEED, etc.
  */
  *p_misc_cap  = CDIO_DRIVE_CAP_MISC_FILE;
}

/*!
  Return the number of of the first track.
  CDIO_INVALID_TRACK is returned on error.
*/
track_t
_get_first_track_num_image(void *p_user_data)
{
  _img_private_t *p_env = p_user_data;

  if (!p_env->gen.toc_init)
    p_env->gen.cdio->op.read_toc (p_user_data);

  return p_env->gen.toc_init ? p_env->gen.i_first_track : CDIO_INVALID_TRACK;
}

/*!
  Find out if media has changed since the last call.
  @param p_user_data the CD object to be acted upon.
  @return 1 if media has changed since last call, 0 if not. Error
  return codes are the same as driver_return_code_t
  There is no such thing as changing a media image so we will
  always return 0 - no change.
 */
int
get_media_changed_image(const void *p_user_data)
{
  return 0;
}

/*!
  Return the media catalog number (MCN) from the CD or NULL if there
  is none or we don't have the ability to get it.

  Note: string is malloc'd so caller has to free() the returned
  string when done with it.
  */
char *
_get_mcn_image(const void *p_user_data)
{
  const _img_private_t *p_env = p_user_data;

  if (!p_env || !p_env->psz_mcn) return NULL;
  return strdup(p_env->psz_mcn);
}

/*!
  Return the number of tracks.
*/
track_t
_get_num_tracks_image(void *p_user_data)
{
  _img_private_t *p_env = p_user_data;

  return p_env->gen.i_tracks;
}

/*!
  Return the starting MSF (minutes/secs/frames) for the track number
  track_num in obj.  Tracks numbers start at 1.
  The "leadout" track is specified either by
  using track_num LEADOUT_TRACK or the total tracks+1.

*/
bool
_get_track_msf_image(void *p_user_data, track_t i_track, msf_t *msf)
{
  const _img_private_t *p_env = p_user_data;

  if (NULL == msf) return false;

  if (i_track == CDIO_CDROM_LEADOUT_TRACK) i_track = p_env->gen.i_tracks+1;

  if (i_track <= p_env->gen.i_tracks+1 && i_track != 0) {
    *msf = p_env->tocent[i_track-p_env->gen.i_first_track].start_msf;
    return true;
  } else
    return false;
}

/*! Return number of channels in track: 2 or 4; -2 if not
  implemented or -1 for error.
  Not meaningful if track is not an audio track.
*/
int
get_track_channels_image(const void *p_user_data, track_t i_track)
{
  const _img_private_t *p_env = p_user_data;
  return ( p_env->tocent[i_track-p_env->gen.i_first_track].flags
	  & FOUR_CHANNEL_AUDIO ) ? 4 : 2;
}

/*! Return 1 if copy is permitted on the track, 0 if not, or -1 for error.
  Is this meaningful if not an audio track?
*/
track_flag_t
get_track_copy_permit_image(void *p_user_data, track_t i_track)
{
  const _img_private_t *p_env = p_user_data;
  return ( p_env->tocent[i_track-p_env->gen.i_first_track].flags
	   & COPY_PERMITTED ) ? CDIO_TRACK_FLAG_TRUE : CDIO_TRACK_FLAG_FALSE;
}

/*! Return 1 if track has pre-emphasis, 0 if not, or -1 for error.
  Is this meaningful if not an audio track?

  pre-emphasis is a non linear frequency response.
*/
track_flag_t
get_track_preemphasis_image(const void *p_user_data, track_t i_track)
{
  const _img_private_t *p_env = p_user_data;
  return ( p_env->tocent[i_track-p_env->gen.i_first_track].flags
	   & PRE_EMPHASIS ) ? CDIO_TRACK_FLAG_TRUE : CDIO_TRACK_FLAG_FALSE;
}

/*! Return the starting LBA for the pregap for track number i_track.
  Track numbers start at 1.
  CDIO_INVALID_LBA is returned on error.
*/
lba_t
get_track_pregap_lba_image(const void *p_user_data, track_t i_track)
{
  const _img_private_t *p_env = p_user_data;
  lba_t pregap, start_lba;

  pregap    = p_env->tocent[i_track-p_env->gen.i_first_track].pregap;
  start_lba = p_env->tocent[i_track-p_env->gen.i_first_track].start_lba;

  /* avoid initializing pregap to CDIO_INVALID_LBA by letting calloc
     do the work.  also, nero files have the pregap set equal
     to the start of the track when there is no pregap
  */
  if (!pregap || pregap == start_lba) {
    pregap = CDIO_INVALID_LBA;
  }

  return pregap;
}

/*!
  Return the International Standard Recording Code (ISRC) for track number
  i_track in p_cdio.  Track numbers start at 1.

  Note: string is malloc'd so caller has to free() the returned
  string when done with it.
*/
char *
get_track_isrc_image(const void *p_user_data, track_t i_track)
{
  const _img_private_t *p_env = p_user_data;
  char *isrc = p_env->tocent[i_track-p_env->gen.i_first_track].isrc;

  if (isrc && isrc[0]) {
    return strdup(isrc);
  } else {
    return NULL;
  }
}

/*!
  Read a data sector

  @param p_cdio object to read from

  @param p_buf place to read data into.  The caller should make sure
  this location can store at least ISO_BLOCKSIZE, M2RAW_SECTOR_SIZE,
  or M2F2_SECTOR_SIZE depending on the kind of sector getting read. If
  you don't know whether you have a Mode 1/2, Form 1/ Form 2/Formless
  sector best to reserve space for the maximum, M2RAW_SECTOR_SIZE.

  @param i_lsn sector to read

  @param i_blocksize size of block. Should be either ISO_BLOCKSIZE
  M2RAW_SECTOR_SIZE, or M2F2_SECTOR_SIZE. See comment above under
  p_buf.
  */
driver_return_code_t
read_data_sectors_image ( void *p_user_data, void *p_buf,
			  lsn_t i_lsn,  uint16_t i_blocksize,
			  uint32_t i_blocks )
{
  const _img_private_t *p_env = p_user_data;

  if (!p_env || !p_env->gen.cdio) return DRIVER_OP_UNINIT;

  {
    CdIo_t *p_cdio                = p_env->gen.cdio;
    track_t i_track               = cdio_get_track(p_cdio, i_lsn);
    track_format_t e_track_format = cdio_get_track_format(p_cdio, i_track);

    switch(e_track_format) {
    case TRACK_FORMAT_PSX:
    case TRACK_FORMAT_AUDIO:
    case TRACK_FORMAT_ERROR:
      return DRIVER_OP_ERROR;
    case TRACK_FORMAT_DATA:
      return cdio_read_mode1_sectors (p_cdio, p_buf, i_lsn, false, i_blocks);
    case TRACK_FORMAT_CDI:
    case TRACK_FORMAT_XA:
      return cdio_read_mode2_sectors (p_cdio, p_buf, i_lsn, false, i_blocks);
    }
  }
  return DRIVER_OP_ERROR;
}


/*!
  Set the arg "key" with "value" in the source device.
  Currently "source" to set the source device in I/O operations
  is the only valid key.

*/
driver_return_code_t
_set_arg_image (void *p_user_data, const char key[], const char value[])
{
  _img_private_t *p_env = p_user_data;

  if (!strcmp (key, "source"))
    {
      CDIO_FREE_IF_NOT_NULL(p_env->gen.source_name);
      if (!value) return DRIVER_OP_ERROR;
      p_env->gen.source_name = strdup (value);
    }
  else if (!strcmp (key, "cue"))
    {
      CDIO_FREE_IF_NOT_NULL(p_env->psz_cue_name);
      if (!value) return DRIVER_OP_ERROR;
      p_env->psz_cue_name = strdup (value);
    }
  else if (!strcmp (key, "access-mode"))
    {
      CDIO_FREE_IF_NOT_NULL(p_env->psz_access_mode);
      if (!value) return DRIVER_OP_ERROR;
      p_env->psz_access_mode = strdup (value);
    }
  else
    return DRIVER_OP_ERROR;

  return DRIVER_OP_SUCCESS;
}
