/*
 * mm-naive.c - The fastest, least memory-efficient malloc package.
 *
 * In this naive approach, a block is allocated by simply incrementing
 * the brk pointer.  A block is pure payload. There are no headers or
 * footers.  Blocks are never coalesced or reused. Realloc is
 * implemented directly using mm_malloc and mm_free.
 *
 * NOTE TO STUDENTS: Replace this header comment with your own header
 * comment that gives a high level description of your solution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

#include "mm.h"
#include "memlib.h"

/*********************************************************
 * NOTE TO STUDENTS: Before you do anything else, please
 * provide your team information in the following struct.
 ********************************************************/
team_t team = {
    /* Team name */
    "Hakaishins",
    /* First member's full name */
    "Prashant Ravi",
    /* First member's email address */
    "prashantravi@cse.iitb.ac.in",
    /* Second member's full name (leave blank if none) */
    "Utkarsh Indolia",
    /* Second member's email address (leave blank if none) */
    "utkarshindolia@cse.iitb.ac.in"
};

/* single word (4) or double word (8) alignment */
#define ALIGNMENT 8

/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)


#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))

#define NUMBER_OF_BINS 11

typedef struct header
{
  unsigned long int size;
  int free;
  struct header *next;
  struct header *prev;
} header_t;

typedef struct footer{
	header_t* header;
	int free;
} footer_t;


/*
 * mm_init - initialize the malloc package.
 */

void *init_mem_sbrk_break = NULL;

header_t *bins[NUMBER_OF_BINS];
int overhead = sizeof(header_t) + sizeof(footer_t);
header_t *inital_pg_break, *curr_pg_break;

int mm_init(void)
{
	//printf("New trace starts\n");

	//This function is called every time before each test run of the trace.
	//It should reset the entire state of your malloc or the consecutive trace runs will give wrong answer.

	int i=0;
	for(i=0;i<NUMBER_OF_BINS;i++){
		bins[i] = NULL;
	}
	inital_pg_break = mem_sbrk(0);
	curr_pg_break = inital_pg_break;
	////printf("Initial program break: %u\n",(unsigned int)inital_pg_break);
	/*
	 * This function should initialize and reset any data structures used to represent the starting state(empty heap)
	 *
	 * This function will be called multiple time in the driver code "mdriver.c"
	 */

    return 0;		//Returns 0 on successfull initialization.
}

//---------------------------------------------------------------------------------------------------------------

int get_index(size_t size){
	if(size!=65) size--;
	int index=0;
	size = size < 64 ? 64 : size;
  if(size>64) index=1;
	while(size/2){
		index++;
		size = size/2;
	}
	return (index-6 < NUMBER_OF_BINS)? index-6 : NUMBER_OF_BINS-1;
}



header_t *get_chunk(header_t *binhead, size_t size){
	header_t *iter = binhead;
	while(iter){
		if(iter->size >= size){
			return iter;
		}
		iter = iter->next;
	}
	return iter;
}

footer_t *get_footer(header_t *header) {
    return (footer_t *)((char *)header + header->size + sizeof(header_t));
}

footer_t *create_footer(header_t *header, footer_t *footer) {
    //footer_t *footer = get_footer(header);
    footer->header = header;
		footer->free = header->free;
		return footer;
}

header_t *add_chunk(header_t *binhead, header_t *chunk){
	//printf("###add_chunk me aa rha h\n");
	if(binhead == NULL){
		//printf("###head pe add hua\n");
		binhead = chunk;
		return binhead;
	}
	header_t *curr = binhead;
	header_t *prev = NULL;

	while(curr && curr->size <= chunk->size){
		prev = curr;
		curr = curr->next;
	}

	if(curr == NULL){
		//printf("###curr == null me add hua\n");
		prev->next = chunk;
		chunk->prev = prev;
	}
	else if(prev){
		//printf("###prev me add hua\n");
		chunk->next = curr;
		chunk->prev = prev;
		curr->prev = chunk;
		prev->next = chunk;
	}
	else{
		//printf("###last else me add hua\n");
		chunk->next = binhead;
		binhead->prev = chunk;
		binhead = chunk;
	}
	return binhead;
}

header_t *remove_chunk(header_t *binhead, header_t *chunk){
	if(binhead == NULL)
		return NULL;
	if(binhead == chunk){
		binhead = binhead->next;
		return binhead;
	}

	header_t *iter = binhead->next;
	while(iter){
		if(iter == chunk){
			if(iter->next == NULL){
				iter->prev->next = NULL;
			}
			else{
				iter->prev->next = iter->next;
				iter->next->prev = iter->prev;
			}
			return binhead;
		}
		iter = iter->next;
	}
	return binhead;
}

header_t *divide_chunk(header_t *chunk, size_t size){
	long int rem_space = chunk->size - size;
	if(rem_space >= 32 + overhead){
		header_t *break_header = (header_t *)((char *)chunk + size + overhead);
		break_header->size = rem_space - overhead;
		break_header->free = 1;
		//printf("Selected chunk size: %lu, Allocating rem_space: %ld\n",chunk->size, break_header->size);
		chunk->size = size;
		footer_t *chunk_footer = NULL, *break_footer = NULL;
		chunk_footer = get_footer(chunk);
		break_footer = get_footer(break_header);
		chunk_footer = create_footer(chunk, chunk_footer);
		break_footer = create_footer(break_header, break_footer);
		//if(chunk_footer->header == chunk){
			//printf("...Chunk k liye footer kaam kr rha h\n");
	//	}
	//	if(break_footer->header == break_header){
			//printf("...Break_header k liye footer kaam kr rha h\n");
	//	}
		break_header->next = NULL;
		break_header->prev = NULL;
		bins[get_index(break_header->size)] = add_chunk(bins[get_index(break_header->size)],break_header);
	}
	return chunk;
}

void printbins(){
	//printf("New printbin call from here\n");
	int i=0;
	for(i=0;i<NUMBER_OF_BINS;i++){
		//printf("Bin %d from here\n", i);
		header_t *iter = bins[i];
		while(iter){
			//printf("Chunk start: %u, Chunk size: %lu, Chunk free: %d, Chunk next: %u, Chunk prev: %u, Chunk end: %u\n",(unsigned int)iter, iter->size, iter->free, (unsigned int)iter->next, (unsigned int)iter->prev,(unsigned int)get_footer(iter)+sizeof(footer_t));
			iter = iter->next;
		}
		//printf("Bin Change\n");
	}
}
/*
 * mm_malloc - Allocate a block by incrementing the brk pointer.
 *     Always allocate a block whose size is a multiple of the alignment.
 */
void *mm_malloc(size_t size)
{
	/*
	 * This function should keep track of the allocated memory blocks.
	 * The block allocation should minimize the number of holes (chucks of unusable memory) in the heap memory.
	 * The previously freed memory blocks should be reused.
	 * If no appropriate free block is available then the increase the heap  size using 'mem_sbrk(size)'.
	 * Try to keep the heap size as small as possible.
	 */

	if(size <= 0){		// Invalid request size
		return NULL;
	}
	////printf("size of header: %u\nsize of footer: %u\n",sizeof(header_t),sizeof(footer_t));
	//printf("Allocating %u\n",size);
	size = ((size+7)/8)*8;		//size alligned to 8 bytes
	size_t tot_size = size + overhead;
	int bin = get_index(size);
	////printf("size: %u\tindex: %d\n",size,bin);
	header_t *chunk = NULL;
	while(chunk == NULL){
		if(bin>NUMBER_OF_BINS-1)
			break;
		chunk = get_chunk(bins[bin],size);
		bin++;
	}
	bin--;
	if(chunk){
		chunk->free = 0;
		chunk = divide_chunk(chunk,size);
		bins[bin] = remove_chunk(bins[bin],chunk);
		chunk->next = NULL;
		chunk->prev	= NULL;
		//printbins();
		return (void *)(chunk + 1);
	}

	void *block = mem_sbrk(tot_size);
	if (block == (void *)-1)
	{
		return NULL;
	}
	chunk = block;
	chunk->size = size;
	chunk->free = 0;
	chunk->next = NULL;
	chunk->prev = NULL;
	footer_t *chunk_footer = NULL;
	chunk_footer = get_footer(chunk);
	chunk_footer = create_footer(chunk, chunk_footer);
	if(chunk_footer->header == chunk){
		//printf("...Chunk (niche waala) k liye footer kaam kr rha h\n");
	}
	////printf("Addresses are: %u\ttot_size: %u\n",(unsigned int)chunk,tot_size);
	curr_pg_break = (header_t *)((unsigned int)chunk + tot_size);
	////printf("Current Program Break: %u\n",(unsigned int)curr_pg_break);
	//printbins();
	return (void *)(chunk + 1);
	//mem_sbrk() is wrapper function for the sbrk() system call.
	//Please use mem_sbrk() instead of sbrk() otherwise the evaluation results
	//may give wrong results
}


void mm_free(void *ptr)
{
	/*
	 * Searches the previously allocated node for memory block with base address ptr.
	 *
	 * It should also perform coalesceing on both ends i.e. if the consecutive memory blocks are
	 * free(not allocated) then they should be combined into a single block.
	 *
	 * It should also keep track of all the free memory blocks.
	 * If the freed block is at the end of the heap then you can also decrease the heap size
	 * using 'mem_sbrk(-size)'.
	 */
	 if(ptr == NULL){
		 return;
	 }
	 header_t *header = (header_t *)ptr - 1;
	 //printf("Freeing from %u, size %lu\n",(unsigned int)header, header->size);
	 header_t *next_in_mem = (header_t *)((char *)get_footer(header) + sizeof(footer_t));
	 if((unsigned int)next_in_mem >= (unsigned int)curr_pg_break){
		 next_in_mem = NULL;
	 }
	 header_t *prev_in_mem = NULL;
	 footer_t *prev_f = (footer_t *)((char *)header - sizeof(footer_t));
	 if((unsigned int)prev_f <= (unsigned int)inital_pg_break){
		 prev_f = NULL;
	 }
	 if(prev_f){
		prev_in_mem = prev_f->header;
		if((unsigned int)prev_in_mem < (unsigned int)inital_pg_break){
 		 prev_in_mem = NULL;
 	 	}
	 }
	 footer_t *footer = NULL;
	 if(prev_in_mem && prev_in_mem->free){
		 bins[get_index(prev_in_mem->size)] = remove_chunk(bins[get_index(prev_in_mem->size)],prev_in_mem);
		 prev_in_mem->size += header->size + overhead;
		 footer = get_footer(header);
		 footer->header = prev_in_mem;
		 footer->free = 1;
		 prev_in_mem->free = 1;
		 header = prev_in_mem;
	 }

	 if(next_in_mem && next_in_mem->free){
		 bins[get_index(next_in_mem->size)] = remove_chunk(bins[get_index(next_in_mem->size)],next_in_mem);
		 header->size += next_in_mem->size + overhead;
		 footer = get_footer(next_in_mem);
		 footer->header = header;
		 footer->free = 1;
	 }
	 memset((void*)(header + 1), 0, header->size);
	 header->free = 1;
	 header->next = NULL;
	 header->prev = NULL;
	 bins[get_index(header->size)] = add_chunk(bins[get_index(header->size)],header);
	 //printbins();
}

/*
 * mm_realloc - Implemented simply in terms of mm_malloc and mm_free
 */
void *mm_realloc(void *ptr, size_t size)
{
	size = ((size+7)/8)*8; //8-byte alignement

	if(ptr == NULL){			//memory was not previously allocated
		return mm_malloc(size);
	}

	if(size == 0){				//new size is zero
		mm_free(ptr);
		return NULL;
	}

	/*
	 * This function should also copy the content of the previous memory block into the new block.
	 * You can use 'memcpy()' for this purpose.
	 *
	 * The data structures corresponding to free memory blocks and allocated memory
	 * blocks should also be updated.
	*/

	header_t *header;
  header = (header_t *)ptr - 1;
	//printf("Reallocating from %u, size %lu\n",(unsigned int)header, header->size);
  unsigned long int prev_size = header->size;

  if (size == prev_size)
  {
    return ptr;
  }

	if(size<prev_size){
		header = divide_chunk(header,size);
		return ptr;
	}

	if(size>prev_size){
	 header_t *next_in_mem = (header_t *)((char *)get_footer(header) + sizeof(footer_t));
 	 if((unsigned int)next_in_mem >= (unsigned int)curr_pg_break){
 		 next_in_mem = NULL;
 	 }

	 if(next_in_mem && next_in_mem->free){
		unsigned long int tot_size = header->size + next_in_mem->size + overhead;
		if(tot_size>size){
			bins[get_index(next_in_mem->size)] = remove_chunk(bins[get_index(next_in_mem->size)],next_in_mem);
			header->size = tot_size;
			footer_t *footer = get_footer(next_in_mem);
			footer->header = header;
			footer->free = 0;
			return ptr;
		}
	 }
	}

  void *new_chunk = mm_malloc(size);

  unsigned long int cpy_size = (prev_size < size) ? prev_size : size;

  if (new_chunk != NULL)
  {
    memcpy(new_chunk, ptr, cpy_size);
    mm_free(ptr);
    ptr = new_chunk;
		//printbins();
    return ptr;
  }
  else
  {
    return NULL;
  }

}