/**
 * Copyright (C) 2012 52°North Initiative for Geospatial Open Source Software GmbH
 *
 * Licensed 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.n52.sos.cache;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import org.n52.sos.db.AccessGDB;
import org.n52.util.logging.Logger;

public class CacheScheduler {
	
	public static Logger LOGGER = Logger.getLogger(CacheScheduler.class.getName());

	private static CacheScheduler instance;

	private static final long PERIOD = 1000 * 60 * 60;

	public static final long MINIMUM_UPDATE_DELTA = 1000 * 60 * 15;
	private Timer timer;
	private AccessGDB geoDB;
	
	private List<AbstractEntityCache<?>> candidates = new ArrayList<>();

	private boolean updateCacheOnStartup;

	public static synchronized void init(AccessGDB geoDB, boolean updateCacheOnStartup) {
		if (instance == null) {
			instance = new CacheScheduler(geoDB, updateCacheOnStartup);
		}
	}
	
	public static synchronized CacheScheduler instance() {
		return instance;
	}
	
	private CacheScheduler(AccessGDB geoDB, boolean updateCacheOnStartup) {
		this.geoDB = geoDB;
		this.updateCacheOnStartup = updateCacheOnStartup;
		
		try {
			candidates.add(ObservationOfferingCache.instance());
		} catch (FileNotFoundException e) {
			LOGGER.warn(e.getMessage(), e);
		}
		
		try {
			candidates.add(PropertyUnitMappingCache.instance());
		} catch (FileNotFoundException e) {
			LOGGER.warn(e.getMessage(), e);
		}
		
		this.timer = new Timer(true);
		
		if (!updateCacheOnStartup) {
			LOGGER.info("Update cache on startup disabled!");
		}
		else {
			try {
				List<AbstractEntityCache<?>> requiresUpdates = cacheUpdateRequired();
				if (!requiresUpdates.isEmpty()) {
					LOGGER.info(String.format("Cache update required for: %s", requiresUpdates.toString()));
					/*
					 * now
					 */
					this.timer.schedule(new UpdateCacheTask(requiresUpdates), 0);	
				}
				else {
					LOGGER.info("No cache update required. Last update not longer ago than ms "+MINIMUM_UPDATE_DELTA);
				}
			} catch (FileNotFoundException e) {
				LOGGER.warn(e.getMessage(), e);
				LOGGER.warn("could not initialize cache. disabling scheduled updates.");
				return;
			}			
		}
		
		/*
		 * every 4am
		 */
		Calendar c = new GregorianCalendar();
		c.add(Calendar.DAY_OF_MONTH, 1);
		c.set(Calendar.HOUR_OF_DAY, 4);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		
		Random random = new Random();
		c.add(Calendar.SECOND, 5 + (random.nextInt(11)*2));
		
		this.timer.scheduleAtFixedRate(new UpdateCacheTask(candidates), c.getTime(), PERIOD * 24);
		
		LOGGER.info("Next scheduled cache update: "+c.getTime().toString());
	}
	
	
	public List<AbstractEntityCache<?>> getCandidates() {
		return candidates;
	}

	public boolean isUpdateCacheOnStartup() {
		return updateCacheOnStartup;
	}

	private List<AbstractEntityCache<?>> cacheUpdateRequired() throws FileNotFoundException {
		List<AbstractEntityCache<?>> result = new ArrayList<>();
		
		for (AbstractEntityCache<?> cand : candidates) {
			if (cand.requiresUpdate()) {
				result.add(cand);
			}
		}
		
		return result;
	}

	public void shutdown() {
		this.timer.cancel();
		try {
			ObservationOfferingCache.instance().freeUpdateLock();
		} catch (FileNotFoundException e) {
			LOGGER.warn(e.getMessage(), e);
		}
	}
	
	private class UpdateCacheTask extends TimerTask {

		private List<AbstractEntityCache<?>> candidates;

		public UpdateCacheTask(List<AbstractEntityCache<?>> candidates) {
			this.candidates = candidates;
		}

		@Override
		public void run() {
			try {
				LOGGER.info("update cache... using thread "+ Thread.currentThread().getName());
				
				for (AbstractEntityCache<?> aec : this.candidates) {
					aec.updateCache(geoDB);
				}
				
				LOGGER.info("all caches updated!");
			} catch (IOException e) {
				LOGGER.warn(e.getMessage(), e);
			} catch (CacheException e) {
				LOGGER.warn(e.getMessage(), e);
			} catch (RuntimeException e) {
				LOGGER.warn(e.getMessage(), e);
			}
			
		}
		
	}

}

@@ -1844,24 +1844,86 @@
 Startup;%0A%09%09%0A
+%09%09/*%0A%09%09 * first use the PUMC, others might depend on it%0A%09%09 */%0A
 %09%09try %7B%0A%09%09%09c
@@ -1936,32 +1936,32 @@
 tes.add(
-ObservationOffer
+PropertyUnitMapp
 ingCache
@@ -2051,19 +2051,17 @@
 e);%0A%09%09%7D%0A
-%09%09
 %0A
+
 %09%09try %7B%0A
@@ -2078,32 +2078,32 @@
 tes.add(
-PropertyUnitMapp
+ObservationOffer
 ingCache

change order of cache candidates