
/***************************************************************

Demonstration code from 'Professional Linux Programming'

Written by Neil Matthew, Rick Stones et. al.

Copyright (C) 2000 Wrox Press.

http://www.wrox.com

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.

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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

***************************************************************/



#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "dvd.h"

/*

DVD Store Application Reference API Implementation

This file contains definitions of functions conforming
to the DVD Store application database APIs.

It is not intended to form the basis of a real
implementation - that will use a real database.
This implementation can be used to:
- confirm understanding of the API functionality
- allow testing to proceed at an early stage
- allow concurrent development of front and back ends

The implementation herein uses flat files to store
the data tables, one per file. A simplistic approach
is taken to data access. No thought has been given
to optimization, the code just needs to work.

All of the database files are created in the current
directory.

*/


#if 0
/* Prototypes -- as in dvd.h */
int dvd_open_db(void);
int dvd_close_db(void);

int dvd_member_set(dvd_store_member *member_record_to_update);
int dvd_member_get(int member_id, dvd_store_member *member_record_to_complete);
int dvd_member_create(dvd_store_member *member_record_to_add, int *member_id);
int dvd_member_delete(int member_id);
int dvd_member_search(char *lname, int *result_ids[], int *count);
int dvd_member_get_id_from_number(char *member_no, int *member_id);

int dvd_title_set(dvd_title *title_record_to_update);
int dvd_title_get(int title_id, dvd_title *title_record_to_complete);
int dvd_title_create(dvd_title *title_record_to_add, int *title_id);
int dvd_title_delete(int title_id);
int dvd_title_search(char *title, char *name, int *result_ids[], int *count);

int dvd_disk_set(dvd_disk *disk_record_to_update);
int dvd_disk_get(int disk_id, dvd_disk *disk_record_to_complete);
int dvd_disk_create(dvd_disk *disk_record_to_add, int *disk_id);
int dvd_disk_delete(int disk_id);
int dvd_disk_search(int title_id, int *result_ids[], int *count);

int dvd_get_genre_list(char **genre_list[], int *count); 
int dvd_get_classification_list(char **class_list[], int *count); 
int dvd_err_text(int errno, char **message_to_show); 
int dvd_today(char **date);

int dvd_rent_title(int member_id, int title_id, int *disk_id);
int dvd_rented_disk_info(int disk_id, int *member_id, char *date_rented);
int dvd_disk_return(int disk_id, int *member_id, char *date);
int dvd_overdue_disks(char *date1, char *date2, int *disk_ids[], int *count);

int dvd_title_available(int title_id, char *date, int *count);
int dvd_reserve_title(char date[], int title_id, int member_id);
int dvd_reserve_title_cancel(int member_id);
int dvd_reserve_title_query_by_member(int member_id, int *title_id);
int dvd_reserve_title_query_by_titledate(int title_id, char *date, int *member_ids[], int *count);

#endif

/* Implementation */

/* Internal data structures for reservations and rentals */

typedef struct _reservation_struct {
  int member_id;
  int title_id;
  char date[9];
} dvd_reserve;

typedef struct _rental_struct {
  int disk_id;
  int member_id;
  char date[9];
} dvd_rental;

static int dvd_rental_set(int disk_id, dvd_rental *rental);
static int dvd_reserve_set(int member_id, dvd_reserve *reserve);

/* Database definitions */

#define MEMBER_FILE "member.dat"
static FILE *member_file = NULL;
static int member_count = 0;

#define TITLE_FILE "title.dat"
static FILE *title_file = NULL;
static int title_count = 0;

#define DISK_FILE "disk.dat"
static FILE *disk_file = NULL;
static int disk_count = 0;

#define RENTAL_FILE "rental.dat"
static FILE *rental_file = NULL;
static int rental_count = 0;

#define RESERVE_FILE "reserve.dat"
static FILE *reserve_file = NULL;
static int reserve_count = 0;

/* Helper functions to open files */

static int open_db_table(char *name, FILE **file_ptr, 
			 int *count_ptr, int record_size)
{
  long file_length;
  int file_records;
  FILE *file;

  /* We want to open for reading and writing, but
     create if necessary. Here we open for append
     to create if needed, then reopen as we need
  */
  file = fopen(name,"a+");
  if(!file)
    return DVD_ERR_NO_FILE;
  file_length = ftell(file);

  file = freopen(name, "r+", file);

  file_records = file_length/record_size;
  if(file_records * record_size != file_length)
    return DVD_ERR_BAD_TABLE;

  if(file_records == 0) {
    /* We just created the file. As we use id zero as a
       sentinel we need to reserve the first entry in the
       file, so add a dummy record here */
    file_records = 1;
  }

  *file_ptr = file;
  *count_ptr = file_records;
  return DVD_SUCCESS;
}

/* This is a mandatory API */
static int dvd_db_ok = 0;

int dvd_open_db_login(const char *user, const char *password)
{
  return dvd_open_db();
}


int dvd_open_db()
{
  /* Only open the database table once */
  if(dvd_db_ok)
    return DVD_SUCCESS;

  if(open_db_table(MEMBER_FILE, &member_file, 
		   &member_count, sizeof(dvd_store_member)))
    return DVD_ERR_BAD_MEMBER_TABLE;

  if(open_db_table(TITLE_FILE, &title_file, 
		   &title_count, sizeof(dvd_title)))
    return DVD_ERR_BAD_TITLE_TABLE;

  if(open_db_table(DISK_FILE, &disk_file, 
		   &disk_count, sizeof(dvd_disk)))
    return DVD_ERR_BAD_DISK_TABLE;

  if(open_db_table(RENTAL_FILE, &rental_file, 
		   &rental_count, sizeof(dvd_rental)))
    return DVD_ERR_BAD_RENTAL_TABLE;

  if(open_db_table(RESERVE_FILE, &reserve_file, 
		   &reserve_count, sizeof(dvd_reserve)))
    return DVD_ERR_BAD_RESERVE_TABLE;

  dvd_db_ok = 1;
  return DVD_SUCCESS;
}

int dvd_close_db()
{
  /* Close the database tables
     Ought to check return result from the fclose
  */
  if(member_file) 
    fclose(member_file);
  if(title_file)
    fclose(title_file);
  if(disk_file)
    fclose(disk_file);
  dvd_db_ok = 0;
  
  return DVD_SUCCESS;
}

static int file_set(FILE *file, long file_position, int size, void *data)
{
  if(fseek(file, file_position, SEEK_SET) != 0)
    return DVD_ERR_BAD_SEEK;

  if(fwrite(data, size, 1, file) != 1)
    return DVD_ERR_BAD_WRITE;
  return DVD_SUCCESS;
}

static int file_get(FILE *file, long file_position, int size, void *data)
{
  if(fseek(file, file_position, SEEK_SET) != 0)
    return DVD_ERR_BAD_SEEK;

  if(fread(data, size, 1, file) != 1)
    return DVD_ERR_BAD_READ;
  return DVD_SUCCESS;
}

int dvd_member_set(const dvd_store_member *member_record_to_update)
{
  if(member_record_to_update == NULL)
    return DVD_ERR_NULL_POINTER;

  return 
    file_set(member_file, 
	     sizeof(dvd_store_member) * (member_record_to_update -> member_id),
	     sizeof(dvd_store_member),
	     (void *) member_record_to_update);
}

int dvd_member_get(const int member_id, dvd_store_member *member_record_to_complete)
{
  int err = DVD_SUCCESS;

  if(member_record_to_complete == NULL)
    return DVD_ERR_NULL_POINTER;

  err = file_get(member_file, 
		 sizeof(dvd_store_member) * member_id,
		 sizeof(dvd_store_member),
		 (void *) member_record_to_complete);

  /* If we cannot get the member there may be an error
     with the data, or the member may not exist */
  if(err != DVD_SUCCESS)
    return err;

  /* If the retrieved member id is not as expected there
     may be an error, or the member may be deleted */
  if(member_record_to_complete -> member_id == 0)
    return DVD_ERR_NOT_FOUND;
  if(member_id != member_record_to_complete -> member_id)
    return DVD_ERR_BAD_MEMBER_TABLE;

  return DVD_SUCCESS;
}

int dvd_member_create(dvd_store_member *member_record_to_add, int *member_id)
{
  int err;
  dvd_store_member new_member;
  dvd_reserve null_reserve;

  /* Make a new member record. The caller does not need to fill-in
     the member_id or member_no fields, these will be created before
     the member record is added to the database. The new meber_id
     is returned in the output parameter member_id. This must be
     use to retrieve the new member record to discover or set the
     member_no.
  */

  /* Take a copy of the passed member, in case it is static */
  new_member = *member_record_to_add;

  /* The new member_id is the next available id */
  new_member.member_id = member_count;

  /* The new member_no (in this implementation) is 10000+id */
  sprintf(new_member.member_no, "%05d", member_count+10000);

  /* Add the new member to the member and reservations file */
  err = dvd_member_set(&new_member);
  if(err != DVD_SUCCESS)
    return err;

  memset((void *) &null_reserve, 0, sizeof(dvd_reserve));
  null_reserve.member_id = new_member.member_id;
  err = dvd_reserve_set(null_reserve.member_id, &null_reserve);
  if(err != DVD_SUCCESS)
    return err;

  /* Update member file state */
  *member_id = member_count;
  member_count++;

  return DVD_SUCCESS;
}

int dvd_member_delete(const int member_id)
{
  /* Delete a member by setting the member_id to zero */
  dvd_store_member null_member;
  memset((void *)&null_member, 0, sizeof(null_member));

  return file_set(member_file, 
		  member_id * sizeof(null_member),
		  sizeof(null_member), 
		  &null_member);
}

int dvd_member_get_id_from_number(const char *member_no, int *member_id)
{
  /* Search for a member by member number.
     Note that this is an case insensitive EXACT match 
     in case we ever want to use alpha member "numbers"
  */
  dvd_store_member member;
  int id = 1;
  int err;

  while(err = dvd_member_get(id, &member), 
	err == DVD_SUCCESS || err == DVD_ERR_NOT_FOUND) {
    if(err == DVD_SUCCESS &&
       strcasecmp(member_no, member.member_no) == 0) {
      *member_id = id;
      return DVD_SUCCESS;
    }
    id++;
  }
  return DVD_ERR_NOT_FOUND;
}

int dvd_member_search(const char *lname, int *result_ids[], int *count)
{
  /* Search for member by surname
     Note this a case sensitive match as there is
     no case insensitive strstr!
  */
  dvd_store_member member;
  int id = 1;
  int matches = 0;
  int *results = NULL;
  int results_size = 0;
  int err; 

  *count = 0;
  while(err = dvd_member_get(id, &member),
	err == DVD_SUCCESS || err == DVD_ERR_NOT_FOUND) {
    if(err == DVD_SUCCESS &&
       strstr(member.lname, lname) != NULL &&
       member.member_id != 0) {
      /* Add a result to the list */
      /* Increase capacity if required */
      if(matches >= results_size) {
	results_size += 4;
	results = (int *)realloc(results, sizeof(int)*results_size);
	if(results == NULL)
	  return DVD_ERR_NO_MEMORY;
      }
      results[matches] = member.member_id;
      matches++;
    }
    id++;
  }
  *result_ids = results;
  *count = matches;
  return DVD_SUCCESS;
}

int dvd_title_set(const dvd_title *title_record_to_update)
{
  if(title_record_to_update == NULL)
    return DVD_ERR_NULL_POINTER;

  return 
    file_set(title_file, 
	     sizeof(dvd_title) * (title_record_to_update -> title_id),
	     sizeof(dvd_title),
	     (void *) title_record_to_update);
}

int dvd_title_get(const int title_id, dvd_title *title_record_to_complete)
{
  int err = DVD_SUCCESS;

  if(title_record_to_complete == NULL)
    return DVD_ERR_NULL_POINTER;

  err = file_get(title_file, 
		 sizeof(dvd_title) * title_id,
		 sizeof(dvd_title),
		 (void *) title_record_to_complete);

  /* If we cannot get the title there may be an error
     with the data, or the title may not exist */
  if(err != DVD_SUCCESS)
    return err;

  /* If the retrieved title id is not as expected there
     may be an error, or the title may be deleted */
  if(title_record_to_complete -> title_id == 0)
    return DVD_ERR_NOT_FOUND;
  if(title_id != title_record_to_complete -> title_id)
    return DVD_ERR_BAD_MEMBER_TABLE;

  return DVD_SUCCESS;
}

int dvd_title_create(dvd_title *title_record_to_add, int *title_id)
{
  int err;

  /* Make a new title record. See also notes in member_create. */

  /* Take a copy of the passed member, in case it is static */
  dvd_title new_title = *title_record_to_add;

  /* The new title_id is the next available id */
  new_title.title_id = title_count;

  /* Add the new title to the title file */
  err = dvd_title_set(&new_title);

  if(err != DVD_SUCCESS)
    return err;

  /* Update title file state */
  *title_id = title_count;
  title_count++;

  return DVD_SUCCESS;
}

int dvd_title_delete(const int title_id)
{
  /* Delete a title by setting the title_id to zero */
  dvd_title null_title;
  memset((void *)&null_title, 0, sizeof(null_title));

  return file_set(title_file, 
		  title_id * sizeof(null_title),
		  sizeof(null_title), 
		  &null_title);
}

int dvd_title_search(const char *title, const char *name, int *result_ids[], int *count)
{
  /* Search for title in title_text, or name in director and actors
     Note this a case sensitive match as there is
     no case insensitive strstr!
  */
  dvd_title a_title;
  int id = 1;
  int matches = 0;
  int *results = NULL;
  int results_size = 0;
  int err; 

  *count = 0;
  while(err = dvd_title_get(id, &a_title),
	err == DVD_SUCCESS || err == DVD_ERR_NOT_FOUND) {
    if(err == DVD_SUCCESS && a_title.title_id != 0) {
      /* Valid record */
      if((title && strstr(a_title.title_text, title)) ||
	 (name && strstr(a_title.director, name)) ||
	 (name && strstr(a_title.actor1, name)) ||
	 (name && strstr(a_title.actor2, name))) {
	/* Add a result to the list */
	/* Increase capacity if required */
	if(matches >= results_size) {
	  results_size += 4;
	  results = (int *)realloc(results, sizeof(int)*results_size);
	  if(results == NULL)
	    return DVD_ERR_NO_MEMORY;
	}
	results[matches] = a_title.title_id;
	matches++;
      }
    }
    id++;
  }
  *result_ids = results;
  *count = matches;
  return DVD_SUCCESS;
}

int dvd_disk_set(const dvd_disk *disk_record_to_update)
{
  if(disk_record_to_update == NULL)
    return DVD_ERR_NULL_POINTER;

  return 
    file_set(disk_file, 
	     sizeof(dvd_disk) * (disk_record_to_update -> disk_id),
	     sizeof(dvd_disk),
	     (void *) disk_record_to_update);
}

int dvd_disk_get(const int disk_id, dvd_disk *disk_record_to_complete)
{
  int err = DVD_SUCCESS;

  if(disk_record_to_complete == NULL)
    return DVD_ERR_NULL_POINTER;

  err = file_get(disk_file, 
		 sizeof(dvd_disk) * disk_id,
		 sizeof(dvd_disk),
		 (void *) disk_record_to_complete);

  /* If we cannot get the disk there may be an error
     with the data, or the title may not exist */
  if(err != DVD_SUCCESS)
    return err;

  /* If the retrieved disk id is not as expected there
     may be an error, or the disk may be deleted */
  if(disk_record_to_complete -> disk_id == 0)
    return DVD_ERR_NOT_FOUND;
  if(disk_id != disk_record_to_complete -> disk_id)
    return DVD_ERR_BAD_MEMBER_TABLE;

  return DVD_SUCCESS;
}

int dvd_disk_create(dvd_disk *disk_record_to_add, int *disk_id)
{
  int err;
  dvd_disk new_disk;
  dvd_rental null_rental;

  /* Make a new disk record. See also notes in member_create.
     Ought really to check that the title_id is valid, but
     then again it can be invalidated independently anyway.
  */
  /* Take a copy of the passed member, in case it is static */
  new_disk = *disk_record_to_add;

  /* The new disk_id is the next available id */
  new_disk.disk_id = disk_count;

  /* Add the new disk to the disk file and the rentals file */
  err = dvd_disk_set(&new_disk);
  if(err != DVD_SUCCESS)
    return err;

  memset((void *) &null_rental, 0, sizeof(dvd_rental));
  null_rental.disk_id = new_disk.disk_id;
  err = dvd_rental_set(null_rental.disk_id, &null_rental);
  if(err != DVD_SUCCESS)
    return err;
  
  /* Update disk file state */
  *disk_id = disk_count;
  disk_count++;

  return DVD_SUCCESS;
}

int dvd_disk_delete(const int disk_id)
{
  /* Delete a disk by setting the disk_id to zero */
  dvd_disk null_disk;
  memset((void *)&null_disk, 0, sizeof(null_disk));

  return file_set(disk_file, 
		  disk_id * sizeof(null_disk),
		  sizeof(null_disk), 
		  &null_disk);
}

int dvd_disk_search(const int title_id, int *result_ids[], int *count)
{
  /* Search for disks of a particular title */
  dvd_disk a_disk;
  int id = 1;
  int matches = 0;
  int *results = NULL;
  int results_size = 0;
  int err; 

  *count = 0;
  while(err = dvd_disk_get(id, &a_disk),
	err == DVD_SUCCESS || err == DVD_ERR_NOT_FOUND) {
    if(err == DVD_SUCCESS && a_disk.disk_id != 0) {
      /* Valid record */
      if(title_id == a_disk.title_id) {
	/* Add a result to the list */
	/* Increase capacity if required */
	if(matches >= results_size) {
	  results_size += 4;
	  results = (int *)realloc(results, sizeof(int)*results_size);
	  if(results == NULL)
	    return DVD_ERR_NO_MEMORY;
	}
	results[matches] = a_disk.disk_id;
	matches++;
      }
    }
    id++;
  }
  *result_ids = results;
  *count = matches;
  return DVD_SUCCESS;
}

int dvd_get_genre_list(char **genre_list[], int *count)
{
  static char *genres[] = {
    "Action",
    "Education", 
    "Comedy", 
    "Thriller", 
    "Foreign", 
    "Romance", 
    "Science Fiction" 
  };
  *genre_list = genres;
  *count = sizeof(genres)/sizeof(char *);
  return DVD_SUCCESS;
}

int dvd_get_classification_list(char **class_list[], int *count)
{
  static char *classes[] = {
    "E", "U", "PG", "12", "15", "18"
  };
  *class_list = classes;
  *count = sizeof(classes)/sizeof(char *);
  return DVD_SUCCESS;
}

int dvd_err_text(const int err_number, char **message_to_show)
{
  static char default_msg[80];
  char *msg = default_msg;
  sprintf(default_msg,"unknown error type: %d", err_number);

  switch(err_number) {
  case DVD_SUCCESS:               msg = "no error";              break;
  case DVD_ERR_NO_FILE:           msg = "cannot open file";      break;
  case DVD_ERR_BAD_TABLE:         msg = "corrupt table file";    break;
  case DVD_ERR_NO_MEMBER_TABLE:   msg = "no member table";       break;
  case DVD_ERR_BAD_MEMBER_TABLE:  msg = "corrupt member table";  break;
  case DVD_ERR_BAD_TITLE_TABLE:   msg = "corrupt title table";   break;
  case DVD_ERR_BAD_DISK_TABLE:    msg = "corrupt disk table";    break;
  case DVD_ERR_BAD_RENTAL_TABLE:  msg = "corrupt rental table";  break;
  case DVD_ERR_BAD_RESERVE_TABLE: msg = "corrupt reserve table"; break;
  case DVD_ERR_BAD_SEEK:          msg = "cannot seek in file";   break;
  case DVD_ERR_NULL_POINTER:      msg = "null data pointer";     break;
  case DVD_ERR_BAD_WRITE:         msg = "cannot write to file";  break;
  case DVD_ERR_BAD_READ:          msg = "cannot read file";      break;
  case DVD_ERR_NOT_FOUND:         msg = "no match found";        break;
  case DVD_ERR_NO_MEMORY:         msg = "out of memory";         break;
  }
  *message_to_show = msg;
  return DVD_SUCCESS;
}
 
int dvd_today(char **date)
{
  static char today[9];
  time_t the_ticks = time(NULL);
  struct tm *the_time = localtime(&the_ticks);
  sprintf(today, "%04d%02d%02d", 
	  the_time -> tm_year + 1900,
	  the_time -> tm_mon + 1,
	  the_time -> tm_mday);
  *date = today;
  return DVD_SUCCESS;
}

/*
  Rentals and Reservation Scheme

  Add another table that mirrors the disk table, but contains
  a member_id field (if rented) and a date rented.
*/

/* Helper functions */

static int dvd_rental_set(const int disk_id, dvd_rental *rental)
{
  if(rental == NULL)
    return DVD_ERR_NULL_POINTER;

  return 
    file_set(rental_file, 
	     sizeof(dvd_rental) * (rental -> disk_id),
	     sizeof(dvd_rental),
	     (void *) rental);
}

static int dvd_rental_get(const int disk_id, dvd_rental *rental)
{
  int err = DVD_SUCCESS;

  if(rental == NULL)
    return DVD_ERR_NULL_POINTER;

  err = file_get(rental_file, 
		 sizeof(dvd_rental) * disk_id,
		 sizeof(dvd_rental),
		 (void *) rental);

  if(err != DVD_SUCCESS)
    return err;

  if(disk_id != rental -> disk_id)
    return DVD_ERR_BAD_RENTAL_TABLE;

  return DVD_SUCCESS;
}

static int dvd_reserve_set(const int member_id, dvd_reserve *reserve)
{
  if(reserve == NULL)
    return DVD_ERR_NULL_POINTER;

  return 
    file_set(reserve_file, 
	     sizeof(dvd_reserve) * (reserve -> member_id),
	     sizeof(dvd_reserve),
	     (void *) reserve);
}

static int dvd_reserve_get(const int member_id, dvd_reserve *reserve)
{
  int err = DVD_SUCCESS;

  if(reserve == NULL)
    return DVD_ERR_NULL_POINTER;

  err = file_get(reserve_file, 
		 sizeof(dvd_reserve) * member_id,
		 sizeof(dvd_reserve),
		 (void *) reserve);

  if(err != DVD_SUCCESS)
    return err;

  if(member_id != reserve -> member_id)
    return DVD_ERR_BAD_RENTAL_TABLE;

  return DVD_SUCCESS;
}

/*
  To rent a single title we need to find a disk of that title
  and tell the owner which one to give the customer. BUT, we 
  must ensure that we do not have too many reservations for
  this date for that title already.
*/
int dvd_rent_title(const int member_id, const int title_id, int *disk_id_to_rent)
{
  int count;
  char *date;
  int err;
  dvd_disk disk;
  dvd_rental rental;
  dvd_reserve reserve;
  int disk_id;

  (void) dvd_today(&date);

  /* See if there is a reservation for today, and clear it */
  err = dvd_reserve_get(member_id, &reserve);
  if(err != DVD_SUCCESS)
    return err;
  if(reserve.title_id == title_id &&
     strcmp(date, reserve.date)) {
    reserve.title_id = 0;
    dvd_reserve_set(member_id, &reserve);
  }

  /* Check that there is a copy available (including reservations) */
  err = dvd_title_available(title_id, date, &count);
  if(err != DVD_SUCCESS)
    return err;

  if(count <= 0)
    return DVD_ERR_NOT_FOUND;

  /* Add an entry to the rental table and return allocated disk id */
  for(disk_id = 1; disk_id < disk_count; disk_id++) {
    err = dvd_disk_get(disk_id, &disk);
    if(err == DVD_SUCCESS && title_id == disk.title_id) {
      /* See if this copy is already out */
      err = dvd_rental_get(disk_id, &rental);
      if(err != DVD_SUCCESS)
	return err;
      if(rental.member_id == 0) {
	rental.member_id = member_id;
	strcpy(rental.date, date);
	err = dvd_rental_set(disk_id, &rental);
	if(err != DVD_SUCCESS)
	  return err;
	*disk_id_to_rent = disk_id;
	return DVD_SUCCESS;
      }
    }
  }
  return DVD_ERR_NOT_FOUND;
}

int dvd_rented_disk_info(const int disk_id, int *member_id, char *date_rented)
{
  /* Lookup the disk in the rental table and return the details */
  /* The user can get the title from dvd_disk_get */
  dvd_rental rental;
  int err;
  
  err = dvd_rental_get(disk_id, &rental);
  if(err != DVD_SUCCESS)
    return err;

  if(rental.member_id == 0)
    return DVD_ERR_NOT_FOUND;

  *member_id = rental.member_id;
  strcpy(date_rented, rental.date);
  return DVD_SUCCESS;
}

/*
  To return a disk we erase the record for that disk in the rental
  table, but also return the date the disk was rented, so that the
  store owner can charge a fine if needed.
*/
int dvd_disk_return(const int disk_id, int *member_id, char *date)
{
  dvd_rental rental;
  int err;

  err = dvd_rental_get(disk_id, &rental);
  if(err != DVD_SUCCESS)
    return err;

  if(rental.member_id == 0)
    return DVD_ERR_NOT_FOUND;

  strcpy(date, rental.date);
  *member_id = rental.member_id;

  /* Clear the entry in the rental table for this disk */
  memset((void *) &rental, 0, sizeof(dvd_rental));
  rental.disk_id = disk_id;
  return dvd_rental_set(disk_id, &rental);
}

/*
  Reservations

  Another table that mirrors the member table - we allow
  each member to reserve one title at a time, for one date.

  A record is kept of titles and requested date of hire.
  We can allow only as many reservations for any one date as
  we have disks of that title.

  When a member comes into the store and tries to rent a video
  the application should check to see if the title he wants to rent
  has a reservation for today. Then the reservation must be cancelled
  before an attempt is made to rent an actual disk.
*/

int dvd_title_available(const int title_id, const char *date, int *count)
{
  int copies = 0;
  int disk_id, member_id;
  dvd_disk disk;
  dvd_rental rental;
  dvd_reserve reserve;
  int err;

  /* Find out how many copies of this title we have */
  for(disk_id = 1; disk_id < disk_count; disk_id++) {
    err = dvd_disk_get(disk_id, &disk);
    if(err == DVD_ERR_NOT_FOUND)
      continue;
    if(err != DVD_SUCCESS)
      return err;
    if(title_id == disk.title_id) {
      copies++;
      /* Check the rental table to disregard copies already rented */
      err = dvd_rental_get(disk_id, &rental);
      if(err != DVD_SUCCESS)
	return err;
      if(rental.member_id != 0)
	copies--;
    }
  }

  /* Scan the reservation table and count the times
     this title appears at this date */
  for(member_id = 1; member_id < member_count; member_id++) {
    err = dvd_reserve_get(member_id, &reserve);
    if(err != DVD_SUCCESS)
      return err;
    if(title_id == reserve.title_id &&
       strcmp(date, reserve.date) == 0)
      copies--;
  }

  /* All done */
  *count = copies;
  return DVD_SUCCESS;
}

int dvd_reserve_title(const char *date, const int title_id, const int member_id)
{
  int count;
  dvd_reserve reserve;

  reserve.member_id = member_id;
  reserve.title_id = title_id;
  strcpy(reserve.date, date);

  /* Check title is available on this date */
  if(dvd_title_available(title_id, date, &count) == DVD_SUCCESS &&
     count > 0) {
    /* Make entry in the reservation table */
    return dvd_reserve_set(member_id, &reserve);
  }
  return DVD_ERR_NOT_FOUND;
}

int dvd_reserve_title_cancel(const int member_id)
{
  /* Clear out entry in the reservation table */
  dvd_reserve reserve;
  memset((void *) &reserve, 0, sizeof(dvd_reserve));
  reserve.member_id = member_id;
  return dvd_reserve_set(member_id, &reserve);
}

int dvd_reserve_title_query_by_member(const int member_id, int *title_id)
{
  /* Retrieve a reservation for this member (if any) */
  dvd_reserve reserve;
  int err;
  err = dvd_reserve_get(member_id, &reserve);
  if(err != DVD_SUCCESS)
    return err;
  *title_id = reserve.title_id;
  return DVD_SUCCESS;
}

int dvd_reserve_title_query_by_titledate(const int title_id, const char *date, int *member_ids[], int *count)
{
  /* Return a list of members who have reserved this title on this date */
  /* Null date means any date */
  return DVD_ERR_NOT_FOUND;
}

int dvd_overdue_disks(const char *date1, const char *date2, int *disk_ids[], int *count)
{
  /* Scan the rental table for disks whose rented date is after date1
   and before date2. Null dates mean beginning of epoch and tomorrow */
  return DVD_ERR_NOT_FOUND;
}
