/*
*  Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you under the Apache License,
*  Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License.
*  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.synapse.commons.throttle.module.utils;

import org.apache.synapse.commons.throttle.module.utils.impl.DummyHandler;
import org.apache.synapse.commons.throttle.core.AccessInformation;
import org.apache.synapse.commons.throttle.core.ThrottleConstants;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public class StatCollector {

    private Map statsPerUser = new ConcurrentHashMap();
    private Map statsPerDomain = new ConcurrentHashMap();
    private Map statsPerIP = new ConcurrentHashMap();

    private AtomicLong stTime_User = new AtomicLong(-1);
    private AtomicLong stTime_IP = new AtomicLong(-1);
    private AtomicLong stTime_DOMAIN = new AtomicLong(-1);

    private long startTime = 0;
    public static long statCollectionPeriod = 0;
    public static long statDisplayWindow = 0;

    private static volatile StatCollector instance = null;

    public static boolean enableStatCollection = false;

    private StatCollector() {
        startTime = System.nanoTime();
    }

    private void storeStats(AccessInformation accessInfo, String callerID, int throttleType) {
        long timeStamp = System.nanoTime();
        System.out.println(timeStamp);
        if (ThrottleConstants.DOMAIN_BASE == throttleType) {
            stTime_DOMAIN.compareAndSet(-1, timeStamp);
            statsPerDomain.put(new Long(timeStamp), new StatDO(accessInfo.isAccessAllowed(),
                    callerID, new Long(timeStamp)));
        } else if (ThrottleConstants.IP_BASE == throttleType) {
            stTime_IP.compareAndSet(-1, timeStamp);
            statsPerIP.put(new Long(timeStamp), new StatDO(accessInfo.isAccessAllowed(),
                    callerID, new Long(timeStamp)));
        } else if (ThrottleConstants.ROLE_BASE == throttleType) {
            stTime_User.compareAndSet(-1, timeStamp);
            statsPerUser.put(new Long(timeStamp), new StatDO(accessInfo.isAccessAllowed(),
                    callerID, new Long(timeStamp)));
        }
    }

    public static StatCollector getInstance() {
        if (instance == null) {
            synchronized (StatCollector.class) {
                if (instance == null) {
                    instance = new StatCollector();
                }
            }
        }
        return instance;
    }

    public static void setStatCollectionPeriod(long period) {
        statCollectionPeriod = period;
    }

    public static void setStatDisplayWindow(long period) {
        statDisplayWindow = period;
    }

    public static void collect(AccessInformation accessInfo, String callerID, int type) {
        if(!enableStatCollection){
            return;
        }
        getInstance().storeStats(accessInfo, callerID, type);
    }

    private void flushAll() {
        statsPerDomain.clear();
        statsPerUser.clear();
        statsPerIP.clear();

        stTime_User = new AtomicLong(-1);
        stTime_IP = new AtomicLong(-1);
        stTime_DOMAIN = new AtomicLong(-1);

        statCollectionPeriod = 0;
        statDisplayWindow = 0;

        startTime = 0;
    }

    public static void flushStats(){
        getInstance().flushAll();
    }

    public static void displayStats(int throttleType) {
        StatCollector collector = getInstance();
        Map sortedMap = null;
        if (ThrottleConstants.DOMAIN_BASE == throttleType) {
            System.out.println("Start Time : Domain Base Throttling ::" + collector.stTime_DOMAIN);
            sortedMap = sortByComparator(collector.statsPerDomain);
        } else if (ThrottleConstants.IP_BASE == throttleType) {
            System.out.println("Start Time : IP Base Throttling ::" + collector.stTime_IP);
            sortedMap = sortByComparator(collector.statsPerIP);
        } else if (ThrottleConstants.ROLE_BASE == throttleType) {
            System.out.println("Start Time : Role Base Throttling ::" + collector.stTime_User);
            sortedMap = sortByComparator(collector.statsPerUser);
        }
        Set set = sortedMap.entrySet();
        Iterator iterator = set.iterator();
        String prevCallerID = null;
        boolean prevAccessState = false;
        Long phaseStartTimestamp = null;
        Long phaseEndTimestamp = null;
        Long prevTimestamp = null;

        String callerID = null;

        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Long timestamp = (Long) entry.getKey();
            callerID = ((StatDO) entry.getValue()).getCallerID();
            boolean currAccessState = ((StatDO) entry.getValue()).isAccessAllowed();

            if (prevCallerID == null || callerID.equals(prevCallerID)) {
                if (prevCallerID != null && prevAccessState != currAccessState) {
                    //a phase changes
                    phaseEndTimestamp = prevTimestamp;
                    System.out.println("Access Phase change for :" + callerID +
                            "  duration of this phase :" +
                            (phaseEndTimestamp - phaseStartTimestamp) / 1000.0 + " ms");
                    phaseStartTimestamp = timestamp;
                } else {
                    //same access phase
                    if (phaseStartTimestamp == null) {
                        phaseStartTimestamp = timestamp;
                    }
                }
            } else {
                //caller changes
                phaseStartTimestamp = timestamp;
            }
            prevCallerID = callerID;
            prevAccessState = currAccessState;
            prevTimestamp = timestamp;
        }


        iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            System.out.println("Timestamp : " + entry.getKey()
                               + " CallerID : " + ((StatDO) entry.getValue()).getCallerID() +
                                " Role : " + DummyHandler.apiKey2roleMap.
                    get(((StatDO) entry.getValue()).getCallerID()) +
                               (((StatDO) entry.getValue()).isAccessAllowed()
                                       ? " --> Allowed" : "--> Denied"));

        }

    }

    private static Map sortByComparator(Map unsortMap) {

        List list = new LinkedList(unsortMap.entrySet());

        //sort list based on comparator
        Collections.sort(list, new Comparator() {
            public int compare(Object o1, Object o2) {
                return ((Comparable) ((Map.Entry) (o1)).getValue())
                        .compareTo(((Map.Entry) (o2)).getValue());
            }
        });

        //put sorted list into map again
        Map sortedMap = new LinkedHashMap();
        for (Iterator it = list.iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            sortedMap.put(entry.getKey(), entry.getValue());
        }
        return sortedMap;
    }

    private class StatDO implements Comparable {
        private boolean accessAllowed = false;
        private String callerID = "";
        private Long timestamp;

        public StatDO(boolean accessAllowed, String callerID, Long timestamp) {
            this.accessAllowed = accessAllowed;
            this.callerID = callerID;
            this.timestamp = timestamp;
        }

        public boolean isAccessAllowed() {
            return accessAllowed;
        }

        public String getCallerID() {
            return callerID;
        }

        public Long getTimestamp() {
            return timestamp;
        }

        public int compareTo(Object o) {
            int result = 0;
            result = callerID.compareTo(((StatDO) o).getCallerID());
            if (result != 0) {
                return result;
            }
            return timestamp.compareTo(((StatDO) o).getTimestamp());
        }
    }

    public static void main(String[] args) {
        AccessInformation acAllwd = new AccessInformation();
        acAllwd.setAccessAllowed(true);

        AccessInformation acDenied = new AccessInformation();
        acDenied.setAccessAllowed(false);

        StatCollector.collect(acAllwd, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);

        StatCollector.collect(acAllwd, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);

        StatCollector.collect(acDenied, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acDenied, "nq21LN39VlKe6OezcOndBx", ThrottleConstants.ROLE_BASE);

        StatCollector.collect(acDenied, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acDenied, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);
        StatCollector.collect(acAllwd, "asdadsadg33332424Gasdad", ThrottleConstants.ROLE_BASE);

        StatCollector.displayStats(ThrottleConstants.ROLE_BASE);
    }
}
