package se.unlogic.eagledns.zoneproviders.file;

import org.apache.log4j.Logger;
import org.xbill.DNS.Name;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Zone;
import se.unlogic.eagledns.SecondaryZone;
import se.unlogic.eagledns.ZoneChangeCallback;
import se.unlogic.eagledns.ZoneProvider;
import se.unlogic.eagledns.ZoneProviderUpdatable;
import se.unlogic.standardutils.numbers.NumberUtils;
import se.unlogic.standardutils.timer.RunnableTimerTask;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;


/**
 * This class loads primary zones from zone files in the file system.
 * The zone files have to formated accordingly to RFC 1035 (http://tools.ietf.org/html/rfc1035)
 * and RFC 1034 (http://tools.ietf.org/html/rfc1034).
 * 
 * @author Robert "Unlogic" Olofsson
 * @author Michael Neale, Red Hat (JBoss division)
 *
 */
public class FileZoneProvider implements ZoneProvider, ZoneProviderUpdatable, Runnable {

	private final Logger log = Logger.getLogger(this.getClass());

	private String name;
	private String zoneFileDirectory;

	private boolean autoReloadZones;
	private Long pollingInterval;

	private Map<String, Long> lastFileList = new HashMap<String, Long>();

	private ZoneChangeCallback changeCallback;

	private Timer watcher;

	public void init(String name) {

		this.name = name;

		if(autoReloadZones && pollingInterval != null){

			watcher = new Timer(true);
			watcher.schedule(new RunnableTimerTask(this), 5000, pollingInterval);
		}
	}

	public void run() {

		if (changeCallback != null && hasDirectoryChanged()){

			log.info("Changes in directory " + zoneFileDirectory + " detected");

			changeCallback.zoneDataChanged();
		}
	}

	private boolean hasDirectoryChanged() {
		File folder = new File(this.zoneFileDirectory);
		File[] files = folder.listFiles();
		if (files.length != lastFileList.size()) {
			return true;
		}
		for (File f : folder.listFiles()) {
			if (!lastFileList.containsKey(f.getName())) {
				return true;
			}
			if (f.lastModified() > lastFileList.get(f.getName())) {
				return true;
			}
		}

		return false;
	}


	/** Refresh our list of zone files for watching */
	private void updateZoneFiles(File[] files) {
		lastFileList = new HashMap<String, Long>();
		for (File f : files) {
			lastFileList.put(f.getName(), f.lastModified());
		}
	}

	public Collection<Zone> getPrimaryZones() {

		File zoneDir = new File(this.zoneFileDirectory);

		if(!zoneDir.exists() || !zoneDir.isDirectory()){

			log.error("Zone file directory specified for FileZoneProvider " + name + " does not exist!");
			return null;

		}else if(!zoneDir.canRead()){

			log.error("Zone file directory specified for FileZoneProvider " + name + " is not readable!");
			return null;
		}

		File[] files = zoneDir.listFiles();
		updateZoneFiles(files);

		if(files == null || files.length == 0){

			log.info("No zone files found for FileZoneProvider " + name + " in directory " + zoneDir.getPath());
			return null;
		}

		ArrayList<Zone> zones = new ArrayList<Zone>(files.length);

		for(File zoneFile : files){

			if(!zoneFile.canRead()){
				log.error("FileZoneProvider " + name + " unable to access zone file " + zoneFile );
				continue;
			}

			Name origin;
			try {

				origin = Name.fromString(zoneFile.getName(), Name.root);
				Zone zone = new Zone(origin, zoneFile.getPath());

				log.debug("FileZoneProvider " + name + " successfully parsed zone file " + zoneFile.getName());

				zones.add(zone);

			} catch (TextParseException e) {

				log.error("FileZoneProvider " + name + " unable to parse zone file " + zoneFile.getName(),e);

			} catch (IOException e) {

				log.error("Unable to parse zone file " + zoneFile + " in FileZoneProvider " + name,e);
			}
		}

		if(!zones.isEmpty()){

			return zones;
		}

		return null;
	}

	public void unload() {

	}


	public String getZoneFileDirectory() {
		return zoneFileDirectory;
	}


	public void setZoneFileDirectory(String zoneFileDirectory) {

		this.zoneFileDirectory = zoneFileDirectory;

		log.debug("zoneFileDirectory set to " + zoneFileDirectory);
	}

	public Collection<SecondaryZone> getSecondaryZones() {

		//Not supported
		return null;
	}

	public void zoneUpdated(SecondaryZone secondaryZone) {

		//Not supported
	}

	public void zoneChecked(SecondaryZone secondaryZone) {

		//Not supported
	}

	public void setChangeListener(ZoneChangeCallback ev) {
		this.changeCallback = ev;
	}


	public void setAutoReloadZones(String autoReloadZones) {
		this.autoReloadZones = Boolean.parseBoolean(autoReloadZones);
	}


	public void setPollingInterval(String pollingInterval) {

		Long value = NumberUtils.toLong(pollingInterval);

		if(value != null && value > 0){

			this.pollingInterval = value;

		}else{

			log.warn("Invalid polling interval specified: " + pollingInterval);
		}
	}
}
