package jitd.io;

import java.io.File;

import jitd.ArrayCog;
import jitd.BTreeCog;
import jitd.Cog;
import jitd.ConcatCog;
import jitd.FileCog;
import jitd.IOCog;
import jitd.KeyValueIterator;
import jitd.LeafCog;
import jitd.PageCog;
import jitd.SubArrayCog;
import jitd.memmanage.MemoryManager;
import jitd.util.Utils;

import org.apache.logging.log4j.Logger;

public class PageHandler{

	private long PAGE_SIZE = 20*1024;
	private ManifestFileManager indexFileManager;
	private long freedMemory=0L;
	private static Logger log =
            org.apache.logging.log4j.LogManager.getLogger();

	public PageHandler(){
		this.indexFileManager = new ManifestFileManager(null,"indexManifestFile","index");
	}

	public Cog pageOut(Cog root){
		Cog cog = null;
		DataFormatter<File> dataFormatter = new FileDataFormatter();
		if(root instanceof IOCog){
			return root;
		}
		else if(root instanceof ArrayCog){
			ArrayCog acog = (ArrayCog) root;
			MemoryManager.removeArrayCog(acog.length()*16, acog);
			File file = root.acceptFormatter(dataFormatter);
			FileCog fcog = MemoryManager.getFileCog(file);
			SubArrayCog scog = new SubArrayCog(null, 0, acog.length());
			scog.setFileCog(fcog);
			cog = scog;
		}
		else if(root instanceof SubArrayCog){
			SubArrayCog acog = (SubArrayCog) root;
			if(acog.getBase() !=null){
				MemoryManager.removeArrayCog(acog.getBase().length()*16, acog.getBase());
			}
			File file = root.acceptFormatter(dataFormatter);
			return root;
//			cog = new FileCog(file);
		}
		else if(root instanceof LeafCog){
			File file = root.acceptFormatter(dataFormatter);
			cog = MemoryManager.getFileCog(file);
		}
		else{
			File file = dataFormatter.generateIndexFromTree(root,indexFileManager.getNextManagedFile());
			cog = new PageCog(file);
		}
		dataFormatter.close();
		return cog;
	}

	public void pageInFileSection(SubArrayCog sc){
		if(sc.getBase() == null && sc.getFileCog()!=null){
			ArrayCog ac = new ArrayCog(sc.length());
			KeyValueIterator ite = sc.iterator();
			int count=0;
			while(ite.next()){
				ac.keys[count] = ite.getKey();
				ac.values[count] = ite.getValue();
				count++;
			}
			assert(count==sc.length());
			sc.setBaseCog(ac);
			sc.setStart(0);
			sc.setCount(count);
			sc.getBase().addSubArrayCog(sc);
			sc.getFileCog().getAssociatedSubArrayCogs().remove(sc);
			if(sc.getFileCog().getAssociatedSubArrayCogs().isEmpty()){
//				System.out.println("deleted file : " + sc.getFileCog().getFile() + ". length : "+sc.getFileCog().length());
//				System.out.println(sc.getFileCog().getFile().delete());
				sc.getFileCog().getFile().delete();
			}
			sc.setFileCog(null);
		}
	}

	public Cog pageIn(IOCog page){
		Cog cog = null;
		log.trace("Paging in {}",page.getFile());
		DataFormatter<File> dataFormatter = new FileDataFormatter();
		if(page instanceof PageCog){
			cog = dataFormatter.restoreTreeFromIndex(page.getFile());
		}
		else if(page instanceof FileCog){
			ArrayCog arrayCog = (ArrayCog)dataFormatter.restoreArrayFromDataFile(page.getFile(), page.length());
			arrayCog.setAssociatedSubArrayCogs(((FileCog)page).getAssociatedSubArrayCogs());
			for(SubArrayCog sc : arrayCog.getAssociatedSubArrayCogs()){
				sc.setBase(arrayCog);
				sc.setFileCog(null);
			}
		}
		log.trace("deleting file {} after paging in. Successful {}",page.getFile(), page.getFile().delete());
		dataFormatter.close();
		return cog;
	}

	public Cog pageIn(IOCog page, Cog parentCog){
		Cog temp = pageIn(page);
		parentCog.replaceChildCog(page, temp);
		return temp;
	}

	public Cog pageOutFixedPageSizes(Cog startNode, long memorySize, boolean dryRun){
		freedMemory = 0L;
		if(startNode instanceof BTreeCog || startNode instanceof ConcatCog){
			Cog lhs = null;
			Cog rhs = null;
			if(startNode instanceof BTreeCog){
				BTreeCog bc = (BTreeCog) startNode;
				lhs = bc.lhs;
				rhs = bc.rhs;
			}
			if(startNode instanceof ConcatCog){
				ConcatCog cc = (ConcatCog) startNode;
				lhs = cc.lhs;
				rhs = cc.rhs;
			}
			long totalCount = pageOutFixedPageSizes(lhs,startNode,memorySize, dryRun)+ pageOutFixedPageSizes(rhs,startNode,memorySize, dryRun) + startNode.approxMemoryFootprint();
			if(totalCount >= PAGE_SIZE && memorySize>freedMemory){
				freedMemory += totalCount;
				Cog page = null;
				if(!dryRun){
					log.info("Total approx MemoryFootprint"+totalCount);
					log.info("Freeing memory");
					page = pageOut(startNode);
					log.info("Paging out {} to PageCog {}",startNode.toString(), page.toString());
				}
				return page;
			}
		}
		else if (startNode instanceof ArrayCog || startNode instanceof SubArrayCog){
			if(dryRun){
				freedMemory += new FileCog(null).approxMemoryFootprint();
				return null;
			}
			else if(startNode.approxMemoryFootprint() >= PAGE_SIZE){
				freedMemory += startNode.approxMemoryFootprint();
				Cog page = null;
				page = pageOut(startNode);
				log.info("Paging out {} to File Cog {}",startNode.toString(), page.toString());
				return page;
			}
		}
		return startNode;
	}

	private long pageOutFixedPageSizes(Cog startNode, Cog parentNode, long memorySize, boolean dryRun){
		if(startNode==null)
			return 0;
		if(startNode instanceof BTreeCog || startNode instanceof ConcatCog){
			Cog lhs = null;
			Cog rhs = null;
			if(startNode instanceof BTreeCog){
				BTreeCog bc = (BTreeCog) startNode;
				lhs = bc.lhs;
				rhs = bc.rhs;
			}
			if(startNode instanceof ConcatCog){
				ConcatCog cc = (ConcatCog) startNode;
				lhs = cc.lhs;
				rhs = cc.rhs;
			}
			long totalCount = pageOutFixedPageSizes(lhs, startNode,memorySize, dryRun) + pageOutFixedPageSizes(rhs, startNode,memorySize, dryRun) +startNode.approxMemoryFootprint();
			if(totalCount >= PAGE_SIZE && memorySize>freedMemory){
				freedMemory += totalCount;
				Cog page = null;
				if(!dryRun){
					page = pageOut(startNode);
					log.info("Paging out {} to Page Cog {}",startNode.toString(), page.toString());
					Utils.replaceCog(startNode, page, parentNode);
				}else{
					page = new PageCog(null);
				}
				return page.approxMemoryFootprint();
			}
			else{
				return totalCount;
			}
		}
		else if (startNode instanceof ArrayCog || startNode instanceof SubArrayCog){
			if(dryRun){
				Cog page = new FileCog(null);
				freedMemory += page.approxMemoryFootprint();
				return page.approxMemoryFootprint();
			}
			else if(startNode.approxMemoryFootprint() >= PAGE_SIZE  && memorySize>freedMemory){
				freedMemory += startNode.approxMemoryFootprint();
				Cog page = null;
				page = pageOut(startNode);
				log.info("Paging out {} to File Cog {}",startNode.toString(), page.toString());
				Utils.replaceCog(startNode, page, parentNode);
				return page.approxMemoryFootprint();
			}
			else{
				return new FileCog(null).approxMemoryFootprint();
			}
		}
		else{
			return startNode.approxMemoryFootprint();
		}
	}

	public long getFreedMemory(){
  		return freedMemory;
  	}
}
