/********************************************************************************************
 Develop a simulation program     
 1. handling client requests
 2. calculate the size of AUX buffer
 3. implement the LRU algorithm (pure algorithm at this moment - part 1, 2, 3)
 4. record the information for analysis and determine the performance of LRU algorithm
 ******************************************************************************************** *
 */


package  project;

import  java.util.*;


/**
 Provide interface for receive client request and send the response
 */
public class BufferControl {
    //two buffer: one for auxiliary buffer and one for main buffer
    private long totalCount;                    // total number of request
    private long totalHitCount;                 // total number of hit count
    private long lastAuxModifyTime;             //Last aux modification time
    private long modifyPeriod;
    private int fileSizeBound;
    private HashMap cacheMap;
    private AuxiliaryBuffer auxBuffer;
    private MainBuffer mainBuffer;
    private Replacement replacement;
    private long recordPeriod;
    private long lastRecordTime;
    
    /**
     * put your documentation comment here
     */
    public BufferControl () throws Exception
    {
        this("PureLRU", 10000, 1000000, 10000);
    }

    /**
     * put your documentation comment here
     * @param     String algorithm
     * @param     int bufferSize
     * @param     long modifyPeriod
     * @param     int fileSizeBound
     */
    public BufferControl (String algorithm, int bufferSize, long modifyPeriod, 
            int fileSizeBound) throws Exception
    {
        // the mechanism of create main buffer and auxiliary buffer is not yet confirm
        // test only
        totalCount = 0;
        totalHitCount = 0;
        auxBuffer = new AuxiliaryBuffer();
        mainBuffer = new MainBuffer(bufferSize);
        cacheMap = new HashMap();
        this.modifyPeriod = modifyPeriod;
        this.fileSizeBound = fileSizeBound;
        lastAuxModifyTime = System.currentTimeMillis();
        Class algorithmClass = Class.forName("project." + algorithm);
        replacement = (Replacement)(algorithmClass.newInstance());
        lastRecordTime = System.currentTimeMillis();
        recordPeriod = 1000;  //1 second
    }

    /**
     * put your documentation comment here
     */
    public void printHitStatic () {
        Set cacheSet = cacheMap.keySet();
        ArrayList cacheList = new ArrayList(cacheSet);
        Collections.sort(cacheList, Collections.reverseOrder());

        //for (int i = 0; i < cacheList.size(); i++)
        for (int i = (cacheList.size() - 1); i >= 0 ; i--)
            System.out.println(cacheList.get(i) + "\t" + cacheMap.get(cacheList.get(i)));

        System.out.println("Total Cache Hit is: " + totalHitCount);
        System.out.println("Total Request is: " + totalCount);
        System.out.println("Main Buffer Size: " + (mainBuffer.getSizeUsed()+ mainBuffer.avaliableSize()));
        System.out.println("Aux buffer modification period: " + modifyPeriod);
    }

    // Critical Question: is it better to make two buffer as object????
    /**
     * handle the request from the client. 
     * It checks the auxiliary buffer have the file name or not.
     * 
     * If there is the file name the client wanted in the aux buffer, get the information from the main and update the timeStamp
     *
     * If there is no that file name, the system will then: 
     * 1. In the case of main buffer is not full:
     *    Add a record to the aux buffer and main buffer.
     * 
     * 
     * 2. In the case of main buffer is full:
     *    If it is one-timer, put the info to the aux buffer only
     *    else modified the aux buffer and main buffer according to the LRU algorithm
     */
    public void requestFile (String fileName) throws NoSuchFieldException{
        // for recording hit ratio
        if ((System.currentTimeMillis() - lastRecordTime) > recordPeriod) {
            cacheMap.put(new Date(), new Float((float)totalHitCount/totalCount));
            lastRecordTime = System.currentTimeMillis();
        }  
	
        long fileSize;
        if (fileSizeBound == 0)
            fileSize = 1000;                     //Use 1000 bytes
        else 
            fileSize = (int)Math.random()*fileSizeBound+1;        // in byte
	    
        totalCount++;
	
        if (auxBuffer.containFile(fileName)) {
	
            if(mainBuffer.containFile(fileName))	//possible that file add in the beginning that main buffer have enough space
            {
                    totalHitCount++;
                    System.out.println("Get files from cache");                     //test messager
            }
            else {
                    //check one-timer, if not one-timer and main buffer did not have the data, then put data to main buffer
                    int hitcount = auxBuffer.getHitCount(fileName);
                    if (hitcount == 1) {
                        if (mainBuffer.avaliableSize() < fileSize) {
                            replacement.doAlgorithm(mainBuffer, auxBuffer, fileSize, fileName);
                        }
                        else {
                            System.out.println("There are enough size in Main");
                        }
            
                        if (mainBuffer.avaliableSize() >= fileSize) {
                            mainBuffer.addRecord(fileName, fileSize);
                            System.out.println(fileName+" added at Main buffer");                //test messager
                        }
                        else
                            throw new ArithmeticException("The replacement algorithm have error, not enough space after replacement");
                    }
                    else
                        throw new IllegalStateException("Main cache don't contain: "+fileName+" where Aux contain that file and more than 2 hit");
            }
            
            auxBuffer.updateRecord(fileName);

        } 
        else {
            if (!auxBuffer.isAvaliable()) {
                System.out.println("Some old files will remove from Aux");      //test messager
                String oldFileName = auxBuffer.getLRUfile(fileName);
                mainBuffer.removeRecord(oldFileName);
                auxBuffer.removeRecord(oldFileName);
                System.out.println("remove old files from Aux");                //test messager
            }
	        else
                System.out.println("Aux have free space");                //test messager
	    
            if (auxBuffer.isAvaliable()) {
                auxBuffer.addRecord(fileName);
                System.out.println("Add files to Aux");             //test messager
            }
            else
                throw new ArithmeticException("Aux don't have space even I have free space from aux");
		
            if (mainBuffer.avaliableSize() > fileSize) {
                mainBuffer.addRecord(fileName, fileSize);
            }
            else
                System.out.println("Nothing need to do at main buffer");
        }

        // reset auxBuffer
        if ((System.currentTimeMillis() - lastAuxModifyTime) > modifyPeriod) {
            System.out.println("We are resetting the aux size");
            auxBuffer.resetSize(mainBuffer);            
            lastAuxModifyTime = System.currentTimeMillis();
        }

    }
}
