/*
 * deblock_media.c -
 *
 * Written by Eryk Vershen (eryk@apple.com)
 */

/*
 * Copyright 1997,1998 by Apple Computer, Inc.
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */


// for malloc() & free()
#include <stdlib.h>
// for memcpy()
#include <string.h>

#include "deblock_media.h"


/*
 * Defines
 */


/*
 * Types
 */
typedef struct deblock_media *DEBLOCK_MEDIA;

struct deblock_media {
    struct media    m;
    long            need_filtering;
    MEDIA           next_media;
    unsigned long   next_block_size;
    unsigned char   *buffer;
};

struct deblock_globals {
    long        exists;
    long        kind;
};


/*
 * Global Constants
 */


/*
 * Global Variables
 */
static long deblock_inited = 0;
static struct deblock_globals deblock_info;

/*
 * Forward declarations
 */
void deblock_init(void);
DEBLOCK_MEDIA new_deblock_media(void);
long read_deblock_media(MEDIA m, long long offset, unsigned long count, void *address);
long write_deblock_media(MEDIA m, long long offset, unsigned long count, void *address);
long close_deblock_media(MEDIA m);
long os_reload_deblock_media(MEDIA m);


/*
 * Routines
 */
void
deblock_init(void)
{
    if (deblock_inited != 0) {
	return;
    }
    deblock_inited = 1;
    
    deblock_info.kind = allocate_media_kind();
}


DEBLOCK_MEDIA
new_deblock_media(void)
{
    return (DEBLOCK_MEDIA) new_media(sizeof(struct deblock_media));
}


MEDIA
open_deblock_media(long new_block_size, MEDIA m)
{
    DEBLOCK_MEDIA   a;
    unsigned long   block_size;
    
    if (deblock_inited == 0) {
	deblock_init();
    }
    
    a = 0;
    if (m != 0) {
	block_size = media_granularity(m);

	if (new_block_size == block_size) {
	    return m;

	} else if (new_block_size > block_size) {
	    if ((new_block_size % block_size) == 0) {
		/* no filtering necessary */
		a = new_deblock_media();
		if (a != 0) {
		    a->need_filtering = 0;
		    a->next_block_size = block_size;
		    a->buffer = 0;
		}
	    } else {
		/* too hard to bother with */
	    }
	} else /* new_block_size < block_size */ {
	    if ((block_size % new_block_size) == 0) {
		/* block & unblock */
		a = new_deblock_media();
		if (a != 0) {
		    a->need_filtering = 1;
		    a->next_block_size = block_size;
		    a->buffer = malloc(block_size);
		}
	    } else {
		/* too hard to bother with */
	    }
	}
	if (a != 0) {
	    a->m.kind = deblock_info.kind;
	    a->m.grain = new_block_size;
	    a->m.size_in_bytes = media_total_size(m);
	    a->m.do_read = read_deblock_media;
	    a->m.do_write = write_deblock_media;
	    a->m.do_close = close_deblock_media;
	    a->m.do_os_reload = os_reload_deblock_media;
	    a->next_media = m;
	}
    }
    return (MEDIA) a;
}


long
read_deblock_media(MEDIA m, long long offset, unsigned long count, void *address)
{
    DEBLOCK_MEDIA a;
    long rtn_value;
    unsigned long next_size;
    unsigned long partial_offset;
    unsigned long partial_count;
    long long cur_offset;
    unsigned long remainder;
    unsigned char *addr;

    a = (DEBLOCK_MEDIA) m;
    rtn_value = 0;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != deblock_info.kind) {
	/* wrong kind - XXX need to error here - this is an internal problem */
    } else if (count <= 0 || count % a->m.grain != 0) {
	/* can't handle size */
    } else if (offset < 0 || offset % a->m.grain != 0) {
	/* can't handle offset */
    } else if (a->need_filtering == 0) {
	rtn_value = read_media(a->next_media, offset, count, address);
    } else {
	next_size = a->next_block_size;
	addr = address;
	cur_offset = offset;
	remainder = count;
	rtn_value = 1;

	/* read partial */
	partial_offset = cur_offset % next_size;
	if (partial_offset != 0) {
	    partial_count = next_size - partial_offset;
	    if (partial_count > remainder) {
		partial_count = remainder;
	    }
	    rtn_value = read_media(a->next_media, cur_offset - partial_offset, next_size, a->buffer);
	    if (rtn_value != 0) {
		memcpy (addr, a->buffer + partial_offset, partial_count);
		addr += partial_count;
		cur_offset += partial_count;
		remainder -= partial_count;
	    }
	}
	/* read fulls as long as needed */
	if (rtn_value != 0 && remainder > next_size) {
	    partial_count = remainder - (remainder % next_size);
	    rtn_value = read_media(a->next_media, cur_offset, partial_count, addr);
	    addr += partial_count;
	    cur_offset += partial_count;
	    remainder -= partial_count;
	}
	/* read partial */
	if (rtn_value != 0 && remainder > 0) {
	    partial_count = remainder;
	    rtn_value = read_media(a->next_media, cur_offset, next_size, a->buffer);
	    if (rtn_value != 0) {
		memcpy (addr, a->buffer, partial_count);
	    }
	}
    }
    return rtn_value;
}


long
write_deblock_media(MEDIA m, long long offset, unsigned long count, void *address)
{
    DEBLOCK_MEDIA a;
    long rtn_value;
    unsigned long next_size;
    unsigned long partial_offset;
    unsigned long partial_count;
    long long cur_offset;
    unsigned long remainder;
    unsigned char *addr;
    
    a = (DEBLOCK_MEDIA) m;
    rtn_value = 0;
    if (a == 0) {
	/* no media */
    } else if (a->m.kind != deblock_info.kind) {
	/* wrong kind - XXX need to error here - this is an internal problem */
    } else if (count <= 0 || count % a->m.grain != 0) {
	/* can't handle size */
    } else if (offset < 0 || offset % a->m.grain != 0) {
	/* can't handle offset */
    } else if (a->need_filtering == 0) {
	rtn_value = write_media(a->next_media, offset, count, address);
    } else {
	next_size = a->next_block_size;
	addr = address;
	cur_offset = offset;
	remainder = count;
	rtn_value = 1;

	/* write partial */
	partial_offset = cur_offset % next_size;
	if (partial_offset != 0) {
	    partial_count = next_size - partial_offset;
	    if (partial_count > remainder) {
		partial_count = remainder;
	    }
	    rtn_value = read_media(a->next_media, cur_offset - partial_offset, next_size, a->buffer);
	    if (rtn_value != 0) {
		memcpy (a->buffer + partial_offset, addr, partial_count);
		rtn_value = write_media(a->next_media, cur_offset - partial_offset, next_size, a->buffer);
		addr += partial_count;
		cur_offset += partial_count;
		remainder -= partial_count;
	    }
	}
	/* write fulls as long as needed */
	if (rtn_value != 0 && remainder > next_size) {
	    partial_count = remainder - (remainder % next_size);
	    rtn_value = write_media(a->next_media, cur_offset, partial_count, addr);
	    addr += partial_count;
	    cur_offset += partial_count;
	    remainder -= partial_count;
	}
	/* write partial */
	if (rtn_value != 0 && remainder > 0) {
	    partial_count = remainder;
	    rtn_value = read_media(a->next_media, cur_offset, next_size, a->buffer);
	    if (rtn_value != 0) {
		memcpy (a->buffer, addr, partial_count);
		rtn_value = write_media(a->next_media, cur_offset, next_size, a->buffer);
	    }
	}
    }
    /* recompute size to handle file media */
    a->m.size_in_bytes = media_total_size(a->next_media);
    return rtn_value;
}


long
close_deblock_media(MEDIA m)
{
    DEBLOCK_MEDIA a;
    
    a = (DEBLOCK_MEDIA) m;
    if (a == 0) {
	return 0;
    } else if (a->m.kind != deblock_info.kind) {
	/* XXX need to error here - this is an internal problem */
	return 0;
    }
    
    close_media(a->next_media);
    free(a->buffer);
    return 1;
}


long
os_reload_deblock_media(MEDIA m)
{
    DEBLOCK_MEDIA a;
    
    a = (DEBLOCK_MEDIA) m;
    if (a == 0) {
	return 0;
    } else if (a->m.kind != deblock_info.kind) {
	/* XXX need to error here - this is an internal problem */
	return 0;
    }
    
    os_reload_media(a->next_media);
    return 1;
}