/*
 * (C) Copyright 2014 mjahnen <jahnen@in.tum.de>
 *
 * 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 com.github.mjdev.libaums.partition;

import java.io.IOException;
import java.nio.ByteBuffer;

import android.util.Log;

import com.github.mjdev.libaums.driver.BlockDeviceDriver;
import com.github.mjdev.libaums.fs.FileSystem;
import com.github.mjdev.libaums.fs.FileSystemFactory;

/**
 * This class represents a partition on an mass storage device. A partition has
 * a certain file system which can be accessed via {@link #getFileSystem()}.
 * This file system is needed to to access the files and directories of a
 * partition.
 * <p>
 * The method {@link #getVolumeLabel()} returns the volume label for the
 * partition. Calling the method is equivalent to calling
 * {@link FileSystem#getVolumeLabel()}.
 * 
 * @author mjahnen
 * 
 */
public class Partition implements BlockDeviceDriver {

	private static final String TAG = Partition.class.getSimpleName();

	// private PartitionTableEntry partitionTableEntry;
	private BlockDeviceDriver blockDevice;
	/**
	 * The logical block address where on the device this partition starts.
	 */
	private int logicalBlockAddress;
	private int blockSize;
	private FileSystem fileSystem;

	private Partition() {

	}

	/**
	 * Creates a new partition with the information given.
	 * 
	 * @param entry
	 *            The entry the partition shall represent.
	 * @param blockDevice
	 *            The underlying block device.
	 * @return The newly created Partition.
	 * @throws IOException
	 *             If reading from the device fails.
	 */
	public static Partition createPartition(PartitionTableEntry entry, BlockDeviceDriver blockDevice)
			throws IOException {
		Partition partition = null;

		// we currently only support fat32
		int partitionType = entry.getPartitionType();
		if (partitionType == 0x0b || partitionType == 0x0c) {
			partition = new Partition();
			// partition.partitionTableEntry = entry;
			partition.logicalBlockAddress = entry.getLogicalBlockAddress();
			partition.blockDevice = blockDevice;
			partition.blockSize = blockDevice.getBlockSize();
			partition.fileSystem = FileSystemFactory.createFileSystem(entry, partition);
		} else {
			Log.w(TAG, "unsupported partition type: " + entry.getPartitionType());
		}

		return partition;
	}

	/**
	 * 
	 * @return the file system on the partition which can be used to access
	 *         files and directories.
	 */
	public FileSystem getFileSystem() {
		return fileSystem;
	}

	/**
	 * This method returns the volume label of the file system / partition.
	 * Calling this method is equivalent to calling
	 * {@link FileSystem#getVolumeLabel()}.
	 * 
	 * @return Returns the volume label of this partition.
	 */
	public String getVolumeLabel() {
		return fileSystem.getVolumeLabel();
	}

	@Override
	public void init() {

	}

	@Override
	public void read(long offset, ByteBuffer dest) throws IOException {
		long devOffset = offset / blockSize + logicalBlockAddress;
		// TODO try to make this more efficient by for example making tmp buffer
		// global
		if (offset % blockSize != 0) {
			Log.w(TAG, "device offset " + offset + " not a multiple of block size");
			ByteBuffer tmp = ByteBuffer.allocate(blockSize);

			blockDevice.read(devOffset, tmp);
			tmp.clear();
			tmp.position((int) (offset % blockSize));
			dest.put(tmp);

			devOffset++;
		}

		if (dest.remaining() > 0) {
			ByteBuffer buffer;
			if (dest.remaining() % blockSize != 0) {
				Log.w(TAG, "we have to round up size to next block sector");
				int rounded = blockSize - dest.remaining() % blockSize + dest.remaining();
				buffer = ByteBuffer.allocate(rounded);
				buffer.limit(rounded);
			} else {
				buffer = dest;
			}

			blockDevice.read(devOffset, buffer);

			if (dest.remaining() % blockSize != 0) {
                System.arraycopy(buffer.array(), 0, dest.array(), dest.position(), dest.remaining());
			}
		}
	}

	@Override
	public void write(long offset, ByteBuffer src) throws IOException {
		long devOffset = offset / blockSize + logicalBlockAddress;
		// TODO try to make this more efficient by for example making tmp buffer
		// global
		if (offset % blockSize != 0) {
			Log.w(TAG, "device offset " + offset + " not a multiple of block size");
			ByteBuffer tmp = ByteBuffer.allocate(blockSize);

			blockDevice.read(devOffset, tmp);
			tmp.clear();
			tmp.position((int) (offset % blockSize));
			int remaining = Math.min(tmp.remaining(), src.remaining());
			tmp.put(src.array(), src.position(), remaining);
			src.position(src.position() + remaining);
			tmp.clear();
			blockDevice.write(devOffset, tmp);

			devOffset++;
		}

		if (src.remaining() > 0) {
            // TODO try to make this more efficient by for example only allocating
            // blockSize and making it global
            ByteBuffer buffer;
            if (src.remaining() % blockSize != 0) {
                Log.w(TAG, "we have to round up size to next block sector");
                int rounded = blockSize - src.remaining() % blockSize + src.remaining();
                buffer = ByteBuffer.allocate(rounded);
                buffer.limit(rounded);

                // TODO: instead of just writing 0s at the end of the buffer do we need to read what
                // is currently on the disk and save that then?
                System.arraycopy(src.array(), src.position(), buffer.array(), 0, src.remaining());
            } else {
                buffer = src;
            }

            blockDevice.write(devOffset, buffer);
        }
	}

	@Override
	public int getBlockSize() {
		return blockDevice.getBlockSize();
	}
}

@@ -3618,32 +3618,34 @@
 Size != 0) %7B%0A%09%09%09
+//
 Log.w(TAG, %22devi
@@ -3693,32 +3693,32 @@
 f block size%22);%0A
-
 %09%09%09ByteBuffer tm
@@ -3852,16 +3852,96 @@
 Size));%0A
+%09%09%09int limit = Math.min(dest.remaining(), tmp.capacity());%0A%09%09%09tmp.limit(limit);%0A
 %09%09%09dest.
@@ -4072,16 +4072,18 @@
 ) %7B%0A%09%09%09%09
+//
 Log.w(TA
@@ -4781,24 +4781,26 @@
  != 0) %7B%0A%09%09%09
+//
 Log.w(TAG, %22
@@ -5480,32 +5480,32 @@
 ockSize != 0) %7B%0A
-
                 
@@ -5504,16 +5504,18 @@
         
+//
 Log.w(TA

Check capacity of tmp buffer while reading