/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.jackrabbit.core.stats;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.concurrent.PriorityBlockingQueue;

import org.apache.jackrabbit.api.stats.QueryStatDto;

/**
 * Default {@link QueryStatCore} implementation
 * 
 */
public class QueryStatImpl implements QueryStatCore {

    private final static Comparator<QueryStatDto> comparator = new QueryStatDtoComparator();

    private final BoundedPriorityBlockingQueue<QueryStatDto> slowQueries = new BoundedPriorityBlockingQueue<QueryStatDto>(
            15, comparator);

    private final static Comparator<QueryStatDtoImpl> comparatorOccurrence = new QueryStatDtoOccurrenceComparator();

    /**
     * the real queue size will be bigger than the desired number of popular
     * queries by POPULAR_QUEUE_MULTIPLIER times
     */
    private static final int POPULAR_QUEUE_MULTIPLIER = 5;

    private final BoundedPriorityBlockingQueue<QueryStatDtoImpl> popularQueries = new BoundedPriorityBlockingQueue<QueryStatDtoImpl>(
            15 * POPULAR_QUEUE_MULTIPLIER, comparatorOccurrence);

    private static final class BoundedPriorityBlockingQueue<E> extends
            PriorityBlockingQueue<E> {

        private static final long serialVersionUID = 1L;
        private int maxSize;

        public BoundedPriorityBlockingQueue(int maxSize,
                Comparator<? super E> comparator) {
            super(maxSize + 1, comparator);
            this.maxSize = maxSize;
        }

        @Override
        public boolean offer(E e) {
            boolean s = super.offer(e);
            if (!s) {
                return false;
            }
            if (size() > maxSize) {
                poll();
            }
            return true;
        }

        public synchronized void setMaxSize(int maxSize) {
            if (maxSize < this.maxSize) {
                // shrink the queue
                int delta = super.size() - maxSize;
                for (int i = 0; i < delta; i++) {
                    E t = poll();
                    if (t == null) {
                        break;
                    }
                }
            }
            this.maxSize = maxSize;
        }

        public int getMaxSize() {
            return maxSize;
        }
    }

    private boolean enabled = false;

    public QueryStatImpl() {
    }

    public int getSlowQueriesQueueSize() {
        return slowQueries.getMaxSize();
    }

    public void setSlowQueriesQueueSize(int size) {
        slowQueries.setMaxSize(size);
    }

    public boolean isEnabled() {
        return enabled;
    }

    public synchronized void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void logQuery(final String language, final String statement,
            long durationMs) {
        if (!enabled) {
            return;
        }
        final QueryStatDtoImpl qs = new QueryStatDtoImpl(language, statement,
                durationMs);
        slowQueries.offer(qs);

        synchronized (popularQueries) {
            Iterator<QueryStatDtoImpl> iterator = popularQueries.iterator();
            while (iterator.hasNext()) {
                QueryStatDtoImpl qsdi = iterator.next();
                if (qsdi.equals(qs)) {
                    qs.setOccurrenceCount(qsdi.getOccurrenceCount() + 1);
                    iterator.remove();
                    break;
                }
            }
            popularQueries.offer(qs);
        }
    }

    public void clearSlowQueriesQueue() {
        slowQueries.clear();
    }

    public QueryStatDto[] getSlowQueries() {
        QueryStatDto[] top = slowQueries.toArray(new QueryStatDto[slowQueries
                .size()]);
        Arrays.sort(top, Collections.reverseOrder(comparator));
        for (int i = 0; i < top.length; i++) {
            top[i].setPosition(i + 1);
        }
        return top;
    }

    public QueryStatDto[] getPopularQueries() {
        QueryStatDtoImpl[] top;
        int size = 0;
        int maxSize = 0;
        synchronized (popularQueries) {
            top = popularQueries.toArray(new QueryStatDtoImpl[popularQueries
                    .size()]);
            size = popularQueries.size();
            maxSize = popularQueries.getMaxSize();
        }
        Arrays.sort(top, Collections.reverseOrder(comparatorOccurrence));
        int retSize = Math.min(size, maxSize / POPULAR_QUEUE_MULTIPLIER);
        QueryStatDto[] retval = new QueryStatDto[retSize];
        for (int i = 0; i < retSize; i++) {
            retval[i] = top[i];
            retval[i].setPosition(i + 1);
        }
        return retval;
    }

    public int getPopularQueriesQueueSize() {
        return popularQueries.getMaxSize() / POPULAR_QUEUE_MULTIPLIER;
    }

    public void setPopularQueriesQueueSize(int size) {
        popularQueries.setMaxSize(size * POPULAR_QUEUE_MULTIPLIER);
    }

    public void clearPopularQueriesQueue() {
        popularQueries.clear();
    }

    public void reset() {
        clearSlowQueriesQueue();
        clearPopularQueriesQueue();
    }
}
