/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo 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 the following location:
 *
 *   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.apereo.portal.utils.cache;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * This service supports custom {@link net.sf.ehcache.event.CacheEventListener}
 * objects that recognize when "something bad" is happening with a uPortal
 * cache.  Those listeners quickly contact this service with the details.
 * Periodically this service produces a report on bad things that are occurring,
 * if any, and writes it to the log.
 *
 * @author drewwills
 */
@Service("cacheHealthReporterService")
public class CacheHealthReporterService {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private enum Reports {
        CACHE_SIZE_NOT_LARGE_ENOUGH {
            @Override
            protected void doReportInternal(Logger logger, List<ReportTuple> list) {
                if (list.size() != 0) {
                    final StringBuilder report = new StringBuilder();
                    final Map<String,Integer> counts = new HashMap<>();
                    for (ReportTuple tuple : list) {
                        final String cacheName = tuple.getCache().getName();
                        Integer newCount = 1;  // default
                        Integer oldCount = counts.get(cacheName);
                        if (oldCount != null) {
                            newCount = ++oldCount;
                        }
                        counts.put(cacheName, newCount);
                    }
                    report.append("The following cache(s) have insufficient maxElementsInMemory;  " +
                            "there must be room in the cache to hold every object of " +
                            "the corresponding type in the portal:");
                    for (Map.Entry<String,Integer> y : counts.entrySet()) {
                        report.append("\n\t- ").append(y.getKey()).append(" (")
                                .append(y.getValue())
                                .append(" elements evicted due to insufficient size since last report)");
                    }
                    logger.warn(report.toString());
                }
            }
        };

        private final List<ReportTuple> entries = Collections.synchronizedList(new ArrayList<ReportTuple>());

        public final void add(ReportTuple tuple) {
            entries.add(tuple);
        }

        public final void writeReport(Logger logger) {

            // Copy & Reset the entries list
            final List<ReportTuple> list = new ArrayList<>(entries);
            entries.clear();
            doReportInternal(logger, list);

        }

        protected abstract void doReportInternal(Logger logger, List<ReportTuple> list);

    }

    public void generateReports() {
        for (Reports report : Reports.values()) {
            report.writeReport(logger);
        }
    }

    @Async
    public void reportCacheSizeNotLargeEnough(Ehcache cache, Element element) {
        final ReportTuple tuple = new ReportTuple(cache, element);
        Reports.CACHE_SIZE_NOT_LARGE_ENOUGH.add(tuple);
    }

    /*
     * Nested Types
     */

    private static final class ReportTuple {
        private final Ehcache cache;
        private final Element element;
        public ReportTuple(Ehcache cache, Element element) {
            this.cache = cache;
            this.element = element;
        }
        public Ehcache getCache() {
            return cache;
        }
        public Element getElement() {
            return element;
        }
    }

}
