/*
 * Copyright 2011 Jonathan Anderson
 *
 * 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 me.footlights.core.data.store;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.common.collect.Lists;

import me.footlights.core.FileBackedPreferences;
import me.footlights.core.Preferences;
import me.footlights.core.crypto.Fingerprint;
import me.footlights.core.data.FormatException;
import me.footlights.core.data.NoSuchBlockException;


/** A block store on disk */
public class DiskStore extends LocalStore
{
	public static class Builder
	{
		public DiskStore build() { return new DiskStore(dir, cache); }

		public Builder setCache(LocalStore cache)			{ this.cache = cache;	return this; }
		public Builder setDirectory(File dir)				{ this.dir = dir;		return this; }
		public Builder setPreferences(Preferences prefs)	{ this.prefs = prefs;	return this; }

		public Builder setDefaultDirectory()
		{
			dir = new File(prefs.getString(FileBackedPreferences.CACHE_DIR_KEY()).get());
			dir.mkdirs();

			return this;
		}

		public Builder createTemporaryDirectory() throws IOException
		{
			dir = File.createTempFile("cache", "dir");
			dir.delete();
			dir.mkdir();

			return this;
		}

		private Builder()
		{
			cache = new MemoryStore();
		}

		private File dir;
		private LocalStore cache;
		private Preferences prefs = Preferences.getDefaultPreferences();
	}

	public static Builder newBuilder() { return new Builder(); }

	private DiskStore(File storageDirectory, LocalStore cache)
	{
		super(cache);
		this.dir = storageDirectory;
	}


	@Override
	public Collection<Stat> list() throws IOException
	{
		String names[] = dir.list();

		Collection<Stat> l = Lists.newArrayListWithCapacity(names.length);
		for (String name : names)
			try { l.add(Stat.apply(new File(name))); }
			catch (Exception e) { log.log(Level.WARNING, "Problem with cached file's name", e); }

		return l;
	}
	
	
	@Override
	protected void put(Fingerprint name, ByteBuffer buffer) throws IOException
	{
		FileChannel channel = new FileOutputStream(new File(dir, name.encode())).getChannel();
		channel.write(buffer.duplicate());
		channel.force(true);
	}


	@Override
	protected ByteBuffer get(Fingerprint name)
		throws IOException, NoSuchBlockException
	{
		try
		{
			File file = new File(dir, name.encode());
			long len = file.length();
			
			if (len <= 0) throw new NoSuchBlockException(this, name);
			else if (len > MAX_FILE_SIZE)
				throw new IOException(
					"Block is too large to load (" + len + "B, max loadable size is "
					 + MAX_FILE_SIZE + "B)");

			// The file is a valid block, smaller than MAX_FILE_SIZE (so < 2^31).
			// Read it if it's small, mmap it if it's large.
			FileChannel channel = new FileInputStream(file).getChannel();

			if (len > MAX_READ_SIZE)
				return channel.map(MapMode.READ_ONLY, 0, len);
			else
			{
				ByteBuffer buffer = ByteBuffer.allocate((int) len);
				channel.read(buffer);
				buffer.rewind();
				return buffer.asReadOnlyBuffer();
			}
		}
		catch(FileNotFoundException e)
		{
			throw new NoSuchBlockException(this, name);
		}
		catch(FormatException e)
		{
			throw new IOException("Mangled block: " + e);
		}
	}


	/** The largest block file that we will open - positive 2s-complement. */
	private static int MAX_FILE_SIZE = 0x7FFFFFFF;

	/** Files larger than this value will be mmap'ed, rather than read. */
	private static int MAX_READ_SIZE = (1 << 20);

	private static Logger log = Logger.getLogger(DiskStore.class.getCanonicalName());

	/** The directory that we store files in. */
	private final File dir;
}

@@ -2444,46 +2444,215 @@
 )%0A%09%09
-%09try %7B l.add(Stat.apply(new File(name)
+%7B%0A%09%09%09name = dir + File.separator + name;%0A%09%09%09File f = new File(name);%0A%09%09%09if (!f.exists())%0A%09%09%09%7B%0A%09%09%09%09log.log(Level.SEVERE, %22Cached file does not exist: %22 + name);%0A%09%09%09%09continue;%0A%09%09%09%7D%0A%0A%09%09%09try %7B l.add(Stat.apply(f
 )); 
@@ -2737,24 +2737,28 @@
 name%22, e); %7D
+%0A%09%09%7D
 %0A%0A%09%09return l

Handle cached filenames correctly.