/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.config;

import freenet.support.LogThresholdCallback;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.Logger.LogLevel;
import freenet.support.io.Closer;
import freenet.support.io.FileUtil;
import freenet.support.io.LineReadingInputStream;

/**
 * Global Config object which persists to a file.
 *
 * Reads the config file into a SimpleFieldSet when created.
 * During init, SubConfig's are registered, and fed the relevant parts of the SFS.
 * Once initialization has finished, we check whether there are any options remaining.
 * If so, we complain about them.
 * And then we write the config file back out.
 */
public class FilePersistentConfig extends PersistentConfig {

	final File filename;
	final File tempFilename;
	final protected String header;
	protected final Object storeSync = new Object();
	protected boolean writeOnFinished;

        private static volatile boolean logMINOR;
	static {
		Logger.registerLogThresholdCallback(new LogThresholdCallback(){
			@Override
			public void shouldUpdate(){
				logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
			}
		});
	}

	public static FilePersistentConfig constructFilePersistentConfig(File f) throws IOException {
		return constructFilePersistentConfig(f, null);
	}

	public static FilePersistentConfig constructFilePersistentConfig(File f, String header) throws IOException {
		File filename = f;
		File tempFilename = new File(f.getPath()+".tmp");
		return new FilePersistentConfig(load(filename, tempFilename), filename, tempFilename, header);
	}

	static SimpleFieldSet load(File filename, File tempFilename) throws IOException {
		boolean filenameExists = filename.exists();
		boolean tempFilenameExists = tempFilename.exists();
		if(filenameExists && !filename.canWrite()) {
			Logger.error(FilePersistentConfig.class, "Warning: Cannot write to config file: "+filename);
			System.err.println("Warning: Cannot write to config file: "+filename);
		}
		if(tempFilenameExists && !tempFilename.canWrite()) {
			Logger.error(FilePersistentConfig.class, "Warning: Cannot write to config tempfile: "+tempFilename);
			System.err.println("Warning: Cannot write to config tempfile: "+tempFilename);
		}
		if(filenameExists) {
			if(filename.canRead() && filename.length() > 0) {
				try {
					return initialLoad(filename);
				} catch (FileNotFoundException e) {
					System.err.println("Cannot open config file "+filename+" : "+e+" - checking for temp file "+tempFilename);
				} catch (EOFException e) {
					System.err.println("Empty config file "+filename+" (end of file)");
				}
				// Other IOE's indicate a more serious problem.
			} else {
				// We probably won't be able to write it either.
				System.err.println("Cannot read config file "+filename);
			}
		}
		if(tempFilename.exists()) {
			if(tempFilename.canRead() && tempFilename.length() > 0) {
				try {
					return initialLoad(tempFilename);
				} catch (FileNotFoundException e) {
					System.err.println("Cannot open temp config file either: "+tempFilename+" : "+e);
				} // Other IOE's indicate a more serious problem.
			} else {
				System.err.println("Cannot read (temp) config file "+tempFilename);
				throw new IOException("Cannot read (temp) config file "+tempFilename);
			}
		}
		System.err.println("No config file found, creating new: "+filename);
		return null;
	}

	protected FilePersistentConfig(SimpleFieldSet origFS, File fnam, File temp) throws IOException {
		this(origFS, fnam, temp, null);
	}

	protected FilePersistentConfig(SimpleFieldSet origFS, File fnam, File temp, String header) throws IOException {
		super(origFS);
		this.filename = fnam;
		this.tempFilename = temp;
		this.header = header;
	}

	/** Load the config file into a SimpleFieldSet.
	 * @throws IOException */
	private static SimpleFieldSet initialLoad(File toRead) throws IOException {
		if(toRead == null) return null;
		FileInputStream fis = null;
		BufferedInputStream bis = null;
		LineReadingInputStream lis = null;
		try {
			fis = new FileInputStream(toRead);
			bis = new BufferedInputStream(fis);
			lis = new LineReadingInputStream(bis);
			// Config file is UTF-8 too!
			return new SimpleFieldSet(lis, 1024*1024, 128, true, true, true); // FIXME? advanced users may edit the config file, hence true?
		} finally {
			Closer.close(lis);
			Closer.close(bis);
			Closer.close(fis);
		}
	}

	@Override
	public void register(SubConfig sc) {
		super.register(sc);
	}

	@Override
	public void store() {
		if(!finishedInit) {
			writeOnFinished = true;
			return;
		}
		try {
			synchronized(storeSync) {
				innerStore();
			}
		} catch (IOException e) {
			String err = "Cannot store config: "+e;
			Logger.error(this, err, e);
			System.err.println(err);
			e.printStackTrace();
		}
	}

	/** Don't call without taking storeSync first */
	protected final void innerStore() throws IOException {
		if(!finishedInit)
			throw new IllegalStateException("SHOULD NOT HAPPEN!!");

		SimpleFieldSet fs = exportFieldSet();
		if(logMINOR)
			Logger.minor(this, "fs = " + fs);
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(tempFilename);
			synchronized(this) {
				fs.setHeader(header);
				fs.writeToBigBuffer(fos);
			}
			fos.close();
			fos = null;
			FileUtil.renameTo(tempFilename, filename);
		}
		finally {
			Closer.close(fos);
		}
	}
	
	public void finishedInit() {
		super.finishedInit();
		if(writeOnFinished) {
			writeOnFinished = false;
			store();
		}
	}
}
