#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "caches.h"

ReadCache* read_cache_init(MapIndex *index){
	int i;
	ReadCache *cache;
	cache = (ReadCache*)malloc(sizeof(ReadCache));
	cache->index    = index;
	cache->ref_id   = -1;
	cache->capacity = 2 * 1024;
	cache->reads    = (Read*)malloc(sizeof(Read) * cache->capacity);
	cache->size     = 0;
	cache->offset   = 0;
	cache->start    = 0;
	cache->end      = 0;
	cache->n_ref    = index->mm->n_ref;
	cache->ref_lengths = (int64_t*)malloc(sizeof(int64_t) * cache->n_ref);
	for(i=0;i<cache->n_ref;i++){
		cache->ref_lengths[i] = index->trees[i]->right + MAX_READ_SIZE;
	}
	return cache;
}

void read_cache_free(ReadCache *cache){
	close_map_index(cache->index);
	free(cache->reads);
	free(cache);
}

#define let2read(let, reads) memcpy(&((reads)->read), let, sizeof(maplet)); (reads)->flag = 0; (reads)->y = 0

int read_cache_put(ReadCache *cache, int ref_id, int64_t start, int64_t end){
	maplet *let;
	BTree *tree;
	int i, m, n;
	int64_t t;

	if(ref_id < 0 || ref_id >= cache->index->mm->n_ref) return -2;

	tree = cache->index->trees[ref_id];
	if(start < tree->left) start = tree->left;
	if(end  > tree->right + MAX_READ_SIZE) end   = tree->right + MAX_READ_SIZE;

	if(ref_id != cache->ref_id){
		cache->ref_id = ref_id;
		cache->length = cache->ref_lengths[ref_id];
		goto RELOAD;
	}
	if(start < cache->start){
		if(end < cache->start){
			goto RELOAD;
		} else if(end <= cache->end){
			goto APPEND_LEFT;
		} else {
			goto RELOAD;
		}
	} else if(start <= cache->end){
		if(end <= cache->end){
			return 0;
		} else {
			goto APPEND_RIGHT;
		}
	} else {
		goto RELOAD;
	}

	RELOAD:
	let = read_map_index(cache->index, ref_id, start, end, &n);
	cache->stream_pos = zrtell(cache->index->stream);
	if(let == NULL) return -1;
	if(n > cache->capacity){ cache->capacity = n; cache->reads = (Read*)realloc(cache->reads, sizeof(Read) * cache->capacity);}
	cache->offset = 0;
	memset(cache->reads, 0, sizeof(Read) * cache->capacity);
	for(i=0;i<n;i++){
		let2read(let + i, cache->reads + i);
	}
	cache->size = n;
	free(let);
	if(n){
		cache->start = read_pos(cache->reads[cache->offset].read.pos);
		if(cache->start > start) cache->start = start;
		cache->end   = read_pos(cache->reads[cache->offset+cache->size-1].read.pos);
		if(cache->end < end) cache->end = end;
	} else {
		cache->start = start;
		cache->end   = end;
	}
	return 1;

	APPEND_LEFT:
	t = end;
	end = cache->start;
	if(start >= end) return 0;
	let = read_map_index(cache->index, ref_id, start, end, &n);
	cache->stream_pos = zrtell(cache->index->stream);
	if(let == NULL) return -1;
	m = n;
	for(;n>0;n--){
		if(read_pos(let[n-1].pos) <= cache->start) break;
	}
	if(n <= 0){ cache->start = start; return 0; }
	if(n + cache->size > cache->capacity){
		if(read_pos(let[m-1].pos) >= t){
			if(m > cache->capacity){
				cache->capacity = m;
				cache->reads = (Read*)realloc(cache->reads, sizeof(Read) * cache->capacity);
			}
			n = m;
			cache->offset = cache->capacity;
			cache->size   = 0;
			cache->end    = read_pos(let[n-1].pos);
		} else {
			cache->capacity = cache->size + n;
			cache->reads = (Read*)realloc(cache->reads, sizeof(Read) * cache->capacity);
		}
	}
	if(cache->offset < n){
		if(cache->size)
			memmove(cache->reads + cache->capacity - cache->size, cache->reads + cache->offset, sizeof(Read) * cache->size);
		cache->offset = cache->capacity - cache->size;
	}
	for(;n>0;n--){
		cache->offset --;
		cache->size ++;
		let2read(let + n - 1, cache->reads + cache->offset);
	}
	free(let);
	cache->start = read_pos(cache->reads[cache->offset].read.pos);
	if(cache->start > start) cache->start = start;
	return 2;

	APPEND_RIGHT:
	t = start;
	start = cache->end;
	if(start >= end) return 0;
	if(cache->stream_pos == zrtell(cache->index->stream)){
		let = read_map_next_to(cache->index, ref_id, end + MAX_READ_SIZE, &n);
	} else {
		let = read_map_index(cache->index, ref_id, start, end, &n);
	}
	cache->stream_pos = zrtell(cache->index->stream);
	if(let == NULL) return -1;
	m = n;
	for(n=0;n<m;n++){
		if(read_pos(let[n].pos) > cache->end) break;
	}
	if(n == m){ cache->end = end; return 0; }
	if(m - n + cache->size > cache->capacity){
		if(read_pos(let[0].pos) <= t - MAX_READ_SIZE){
			if(m > cache->capacity){
				cache->capacity = m;
				cache->reads = (Read*)realloc(cache->reads, sizeof(Read) * cache->capacity);
			}
			n = 0;
			cache->offset = 0;
			cache->size   = 0;
			cache->start  = read_pos(let[0].pos);
		} else {
			for(i=cache->size-1;i>=0;i--){
				if(read_pos(cache->reads[cache->offset + i].read.pos) < t - MAX_READ_SIZE) break;
			}
			i ++;
			if(i > 0){
				cache->size -= i;
				if(cache->size){
					memmove(cache->reads, cache->reads + cache->offset + i, sizeof(Read) * cache->size);
					cache->start  = read_pos(cache->reads[0].read.pos);
				} else {
					cache->start  = read_pos(let[0].pos);
				}
				cache->offset = 0;
			}
			if(m - n + cache->size > cache->capacity){
				cache->capacity = m - n + cache->size;
				cache->reads = (Read*)realloc(cache->reads, sizeof(Read) * cache->capacity);
			}
		}
	}
	if(cache->offset){
		memmove(cache->reads, cache->reads + cache->offset, sizeof(Read) * cache->size);
		cache->offset = 0;
	}
	for(;n<m;n++){
		let2read(let + n, cache->reads + cache->offset + cache->size);
		cache->size ++;
	}
	cache->end = read_pos(cache->reads[cache->offset+cache->size-1].read.pos);
	if(cache->end < end) cache->end = end;
	return 3;
}
