/* generic.c:
 *
 * Generic interface to the protocol handlers.
 *
 * ----------------------------------------------------------------------
 * This code is (C) Copyright 1993 by Frank Munkert.
 * All rights reserved.
 * This software may be freely distributed and redistributed for
 * non-commercial purposes, provided this notice is included.
 */

#include <stdlib.h>
#include <string.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <clib/exec_protos.h>
#include <clib/utility_protos.h>
#ifdef AZTEC_C
#include <pragmas/exec_lib.h>
#include <pragmas/utility_lib.h>
#endif
#ifdef LATTICE
#include <pragmas/exec_pragmas.h>
#include <pragmas/utility_pragmas.h>
#endif

#include "cdrom.h"
#include "generic.h"
#include "iso9660.h"
#include "rock.h"
#include "hfs.h"

#define HAN(vol,func) (*(vol)->handler->func)
#define HANX(vol,func) ((vol)->handler->func)

/* Strncasecmp works like 'strncmp' but ignores case differences.
 */

int Strncasecmp (char *p_str1, char *p_str2, int p_length)
{
  int i;
  int len = 0;
  
  while (len < p_length && *p_str1 && *p_str2) {
    if (i = ToLower (*p_str1++) - ToLower (*p_str2++))
      return i;
    len++;
  }
  return (len == p_length) ? 0 : ToLower (*p_str1) - ToLower (*p_str2);
}

t_protocol Which_Protocol (CDROM *p_cdrom, t_bool p_use_rock_ridge, int *p_skip)
{
  if (Uses_Iso_Protocol (p_cdrom)) {
    if (p_use_rock_ridge) {
      VOLUME tmp_vol; /* temporary volume descriptor */
      t_bool rr;

      tmp_vol.cd = p_cdrom;
      tmp_vol.protocol = PRO_ISO;
      Iso_Init_Vol_Info (&tmp_vol, 0);
      rr = Uses_Rock_Ridge_Protocol (&tmp_vol, p_skip);
      HAN (&tmp_vol, close_vol_info) (&tmp_vol);
      return rr ? PRO_ROCK : PRO_ISO;
    }
  } else if (Uses_HFS_Protocol (p_cdrom, p_skip)) {
    return PRO_HFS;
  } else
    return PRO_UNKNOWN;
}

VOLUME *Open_Volume (CDROM *p_cdrom, t_bool p_use_rock_ridge)
{
  VOLUME *res;
  int skip;
    
  res = AllocMem (sizeof (VOLUME), MEMF_PUBLIC);
  if (!res) {
    iso_errno = ISOERR_NO_MEMORY;
    return NULL;
  }

  res->cd = p_cdrom;
  
  res->valid = 1;  /* may be modified by application program */
  res->locks = 0;  /* may be modified by application program */

  res->protocol = Which_Protocol (p_cdrom, p_use_rock_ridge, &skip);
  if (res->protocol == PRO_UNKNOWN) {
    /* give it a second try: */
    res->protocol = Which_Protocol (p_cdrom, p_use_rock_ridge, &skip);  
  }

  switch (res->protocol) {
  case PRO_ROCK:
  case PRO_ISO:
    if (!Iso_Init_Vol_Info (res, skip)) {
      FreeMem (res, sizeof (VOLUME));
      return NULL;
    }
    break;
  case PRO_HFS:
    if (!HFS_Init_Vol_Info (res, skip)) {
      FreeMem (res, sizeof (VOLUME));
      return NULL;
    }
    break;
  default:
    FreeMem (res, sizeof (VOLUME));
    return NULL;
  }

  return res;
}

void Close_Volume (VOLUME *p_volume)
{
  HAN(p_volume, close_vol_info)(p_volume);
  FreeMem (p_volume, sizeof (VOLUME));
}

CDROM_OBJ *Open_Top_Level_Directory (VOLUME *p_volume)
{
  return HAN(p_volume, open_top_level_directory)(p_volume);
}

CDROM_OBJ *Open_Object (CDROM_OBJ *p_current_dir, char *p_path_name)
{
  char *cp = p_path_name;
  CDROM_OBJ *obj, *new;
  VOLUME *vol = p_current_dir->volume;
  char *np;
  char name[100];

  if (*cp == ':') {
    obj = HAN(vol, open_top_level_directory)(p_current_dir->volume);
    cp++;
  } else {
    obj = Clone_Object (p_current_dir);
    if (!obj) {
      iso_errno = ISOERR_NO_MEMORY;
      return NULL;
    }
    while (*cp == '/') {
      new = HAN(vol, find_parent)(obj);
      if (!new)
        return NULL;
      HAN(vol, close_obj)(obj);
      obj = new;
      cp++;
    }
  }

  while (*cp) {
    for (np = name; *cp && *cp != '/'; )
      *np++ = *cp++;
    *np = 0;

    if (*cp)
      cp++;

    if (new = HAN(vol, open_obj_in_directory)(obj, name)) {
      HAN(vol, close_obj)(obj);
      if (*cp && !new->directory_f) {
	HAN(vol, close_obj)(new);
	iso_errno = ISOERR_NOT_FOUND;
	return NULL;
      }
      obj = new;
    } else {
      HAN(vol, close_obj)(obj);
      return NULL;
    }
  }

  return obj;
}

void Close_Object (CDROM_OBJ *p_object)
{
  HAN(p_object->volume, close_obj)(p_object);
}

int Read_From_File (CDROM_OBJ *p_file, char *p_buffer, int p_buffer_length)
{
  return HAN(p_file->volume, read_from_file)(p_file, p_buffer, p_buffer_length);
}

int CDROM_Info (CDROM_OBJ *p_obj, CDROM_INFO *p_info)
{
  return HAN(p_obj->volume, cdrom_info)(p_obj, p_info);
}

t_bool Examine_Next (CDROM_OBJ *p_dir, CDROM_INFO *p_info, unsigned long *p_offset)
{
  return HAN(p_dir->volume, examine_next)(p_dir, p_info, p_offset);
}

CDROM_OBJ *Clone_Object (CDROM_OBJ *p_object)
{
  CDROM_OBJ *new = AllocMem (sizeof (CDROM_OBJ), MEMF_PUBLIC);

  if (!new)
    return NULL;

  memcpy (new, p_object, sizeof (CDROM_OBJ));
  new->obj_info = HAN(p_object->volume,clone_obj_info)(p_object->obj_info);
  if (!new->obj_info) {
    FreeMem (new, sizeof (CDROM_OBJ));
    return NULL;
  }

  return new;
}

CDROM_OBJ *Find_Parent (CDROM_OBJ *p_object)
{
  return HAN(p_object->volume, find_parent)(p_object);
}

t_bool Is_Top_Level_Object (CDROM_OBJ *p_object)
{
  return HAN(p_object->volume, is_top_level_obj)(p_object);
}

t_bool Same_Objects (CDROM_OBJ *p_object1, CDROM_OBJ *p_object2)
{
  if (p_object1->volume != p_object2->volume)
    return 0;
  return HAN(p_object1->volume, same_objects)(p_object1, p_object2);
}

t_ulong Volume_Creation_Date (VOLUME *p_volume)
{
  if (!HANX(p_volume, creation_date))
    return 0;
  return HAN(p_volume, creation_date)(p_volume);
}

t_ulong Volume_Size (VOLUME *p_volume)
{
  return HAN(p_volume, volume_size)(p_volume);
}

void Volume_ID (VOLUME *p_volume, char *p_buffer, int p_buf_length)
{
  HAN(p_volume, volume_id)(p_volume, p_buffer, p_buf_length);
}

t_ulong Location (CDROM_OBJ *p_object)
{
  HAN(p_object->volume, location)(p_object);
}

/* Find a position in a file.
 */

int Seek_Position (CDROM_OBJ *p_object, long p_offset, int p_mode)
{
  t_ulong new_pos;
  t_ulong max_len = HAN(p_object->volume, file_length)(p_object);

  if (p_object->directory_f) {
    iso_errno = ISOERR_BAD_ARGUMENTS;
    return 0;
  }
  
  switch (p_mode) {
  case SEEK_FROM_START:
    if (p_offset < 0 || p_offset > max_len) {
      iso_errno = ISOERR_OFF_BOUNDS;
      return 0;
    }
    new_pos = p_offset;
    break;
  case SEEK_FROM_CURRENT_POS:
    if ((p_offset < 0 && -p_offset > p_object->pos) ||
    	(p_offset > 0 && p_object->pos + p_offset > max_len)) {
      iso_errno = ISOERR_OFF_BOUNDS;
      return 0;
    }
    new_pos = p_object->pos + p_offset;
    break;
  case SEEK_FROM_END:
    if (p_offset > 0 || -p_offset > max_len) {
      iso_errno = ISOERR_OFF_BOUNDS;
      return 0;
    }
    new_pos = max_len + p_offset;
    break;
  default:
    iso_errno = ISOERR_BAD_ARGUMENTS;
    return 0;    
  }
  p_object->pos = new_pos;
  return 1;
}

