/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
/*
 * EXTENSION
 * ISSUE: DVCORE-2521
 * FIXED BY: Roman Pedchenko
 * DATE: 2014-12-05
 * ADOPTED FROM: TEIID-2153, 2171, 2359, 2149, 2358, 2410, 2271, 2467, 2429, 2363, 2160, 2461, 2797, 2326, 1557
 * END EXTENSION
 */
package org.teiid.common.buffer.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.teiid.common.buffer.FileStore;
import org.teiid.common.buffer.StorageManager;
import org.teiid.core.TeiidComponentException;
import org.teiid.query.QueryPlugin;

/**
 * A storage manager that combines smaller files into a larger
 * logical file.
 * 
 * The buffer methods assume that buffers cannot go beyond single
 * file boundaries.
 */
public class SplittableStorageManager implements StorageManager {
	
	public static final long DEFAULT_MAX_FILESIZE = 2 * 1024l;
    private long maxFileSize = DEFAULT_MAX_FILESIZE * 1024l * 1024l; // 2GB
	private StorageManager storageManager;
	
	public SplittableStorageManager(StorageManager storageManager) {
		this.storageManager = storageManager;
	}
	
	@Override
	public FileStore createFileStore(String name) {
		return new SplittableFileStore(name);
	}
	
	@Override
	public void initialize() throws TeiidComponentException {
		storageManager.initialize();
	}
	
	public class SplittableFileStore extends FileStore {
	    private String name;
		private List<FileStore> storageFiles = new ArrayList<FileStore>();
		
		private volatile long len;
	    
	    public SplittableFileStore(String name) {
			this.name = name;
		}
	    
	    @Override
	    public long getLength() {
	    	return len;
	    }
	    
	    @Override
	    protected int readWrite(long fileOffset, byte[] b, int offSet,
	    		int length, boolean write) throws IOException {
	    	FileStore store = null;
	    	if (!write) {
	    		synchronized (this) {
		    		if (fileOffset > len) {
		    			throw new IOException(QueryPlugin.Util.gs(QueryPlugin.Event.DVCORE301271, fileOffset, length));
		    		}
		    		store = storageFiles.get((int)(fileOffset/maxFileSize));
	    		}
		    	return store.read(fileOffset%maxFileSize, b, offSet, length);
			}
	    	synchronized (this) {
		    	ensureLength(fileOffset + length);
	    		store = storageFiles.get((int)(fileOffset/maxFileSize));
			}
	    	long fileBegin = fileOffset%maxFileSize;
	    	length = Math.min(length, (int)Math.min(Integer.MAX_VALUE, maxFileSize - fileBegin));
			store.write(fileBegin, b, offSet, length);
			return length;
	    }

		private void ensureLength(long length) throws IOException {
			if (length <= len) {
				return;
			}
			int numFiles = (int)(length/maxFileSize);
			long lastFileSize = length%maxFileSize;
			if (lastFileSize > 0) {
				numFiles++;
			}
			for (int i = storageFiles.size(); i < numFiles; i++) {
				FileStore newFileInfo = storageManager.createFileStore(name + "_" + storageFiles.size()); //$NON-NLS-1$
				storageFiles.add(newFileInfo);
				if (lastFileSize == 0 || i != numFiles - 1) {
					newFileInfo.setLength(maxFileSize);
				}
			}
			if (lastFileSize > 0) {
				storageFiles.get(storageFiles.size() - 1).setLength(lastFileSize);
			}
			len = length;
		}
		
	    @Override
	    public synchronized void setLength(long length) throws IOException {
			if (length > len) {
				ensureLength(length);
			} else {
				int numFiles = (int)(length/maxFileSize);
				long lastFileSize = length%maxFileSize;
				if (lastFileSize > 0) {
					numFiles++;
				}
				int toRemove = storageFiles.size() - numFiles;
				for (int i = 0; i < toRemove; i++) {
					FileStore store = storageFiles.remove(storageFiles.size() -1);
					store.remove();
				}
				if (lastFileSize > 0) {
					storageFiles.get(storageFiles.size() - 1).setLength(lastFileSize);
				}
			}
			len = length;
	    }
	    
		public synchronized void removeDirect() {
			for (int i = storageFiles.size() - 1; i >= 0; i--) {
				this.storageFiles.remove(i).remove();
			}
		}
		
	}
	
    public long getMaxFileSize() {
		return maxFileSize;
	}
	
    public void setMaxFileSize(long maxFileSize) {
    	this.maxFileSize = maxFileSize * 1024l * 1024l;
	}
    
    public void setMaxFileSizeDirect(long maxFileSize) {
    	this.maxFileSize = maxFileSize;
    }
    
    public StorageManager getStorageManager() {
		return storageManager;
	}

	@Override
	public long getMaxStorageSpace() {
		return storageManager.getMaxStorageSpace();
	}

}
