/*
 * Crail: A Multi-tiered Distributed Direct Access File System
 *
 * Author: Patrick Stuedi <[email protected]>
 *
 * Copyright (C) 2016, IBM Corporation
 *
 * 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.ibm.crail.core;

import java.io.IOException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;

import com.ibm.crail.CrailBuffer;
import com.ibm.crail.CrailOutputStream;
import com.ibm.crail.CrailResult;
import com.ibm.crail.conf.CrailConstants;
import com.ibm.crail.metadata.BlockInfo;
import com.ibm.crail.storage.StorageEndpoint;
import com.ibm.crail.storage.StorageFuture;
import com.ibm.crail.utils.CrailImmediateOperation;
import com.ibm.crail.utils.CrailUtils;

public class CoreOutputStream extends CoreStream implements CrailOutputStream {
	private static final Logger LOG = CrailUtils.getLogger();
	private AtomicLong inFlight;
	private long writeHint;
	private CrailImmediateOperation noOp;
	private boolean open;
	
	public CoreOutputStream(CoreNode file, long streamId, long writeHint) throws Exception {
		super(file, streamId, file.getCapacity());
		this.writeHint = Math.max(0, writeHint);
		this.inFlight = new AtomicLong(0);
		this.noOp = new CrailImmediateOperation(0);
		this.open = true;
		if (CrailConstants.DEBUG){
			LOG.info("CoreOutputStream, open, path " + file.getPath() + ", fd " + file.getFd() + ", streamId " + streamId + ", isDir " + file.getType().isDirectory() + ", writeHint " + this.writeHint);
		}
	}
	
	final public Future<CrailResult> write(CrailBuffer dataBuf) throws Exception {
		if (!open) {
			throw new IOException("Stream closed, cannot write");
		}
		if (dataBuf.remaining() <= 0) {
			return noOp;
		}
		
		inFlight.incrementAndGet();
		CoreDataOperation future = dataOperation(dataBuf);
		if (position() < writeHint){
			prefetchMetadata();
		} 	
		if (future.isSynchronous()){
			future.get();
		}		
		return future;
	}	
	
	final public long getWriteHint() {
		return this.writeHint;
	}
	
	public Future<Void> sync() throws IOException {
		if (inFlight.get() != 0){
			LOG.info("Cannot sync, pending operations, opcount " + inFlight.get());
			throw new IOException("Cannot close, pending operations, opcount " + inFlight.get());
		}		
		return super.sync();
	}
	
	public void close() throws Exception {
		if (!open){
			return;
		}
		if (inFlight.get() != 0){
			LOG.info("Cannot close, pending operations, opcount " + inFlight.get() + ", path " + getFile().getPath());
			throw new IOException("Cannot close, pending operations, opcount " + inFlight.get() + ", fd " + getFile().getFd() + ", streamId " + getStreamId() + ", capacity " + getFile().getCapacity());
		}
		
		sync().get();
		updateIOStats();
		node.closeOutputStream(this);
		open = false;
		if (CrailConstants.DEBUG){
			LOG.info("CoreOutputStream, close, path " + this.getFile().getPath() + ", fd " + getFile().getFd() + ", streamId " + getStreamId() + ", capacity " + getFile().getCapacity());
		}	
	}
	
	// ----------------------
	
	StorageFuture trigger(StorageEndpoint endpoint, CoreSubOperation opDesc, CrailBuffer buffer, BlockInfo block) throws Exception {
		StorageFuture dataFuture = endpoint.write(buffer, block, opDesc.getBlockOffset());
		return dataFuture;		
	}	
	
	synchronized void update(long newCapacity) {
		inFlight.decrementAndGet();
		setCapacity(newCapacity);
	}
}