/*
 * 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.utils;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import com.ibm.crail.CrailLocationClass;
import com.ibm.crail.conf.CrailConstants;
import com.ibm.crail.metadata.DataNodeInfo;


public class CrailUtils {
	private static Logger LOG = CrailUtils.getLogger();
	
	public static synchronized Logger getLogger(){
		if (LOG == null){
			LOG = LoggerFactory.getLogger("com.ibm.crail");
		}
		return LOG;
	}
	
	public static long getAddress(ByteBuffer buffer) {
		return ((sun.nio.ch.DirectBuffer) buffer).address();
	}	
	
	public static InetSocketAddress getNameNodeAddress() {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		LinkedBlockingQueue<URI> namenodes = new LinkedBlockingQueue<URI>();
		while(tupleTokenizer.hasMoreTokens()){
			String address = tupleTokenizer.nextToken();
			URI uri = URI.create(address);
			namenodes.add(uri);
		}
		
		URI master = namenodes.poll();
		InetSocketAddress nnAddr = createSocketAddrForHost(master.getHost(), master.getPort());
		return nnAddr;
	}
	
	public static URI getPrimaryNameNode() {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		LinkedBlockingQueue<URI> namenodes = new LinkedBlockingQueue<URI>();
		while(tupleTokenizer.hasMoreTokens()){
			String address = tupleTokenizer.nextToken();
			URI uri = URI.create(address);
			namenodes.add(uri);
		}
		
		URI master = namenodes.poll();
		return master;
	}	
	
	public static boolean verifyNamenode(String namenode) {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		ConcurrentHashMap<String, Object> namenodes = new ConcurrentHashMap<String, Object>();
		while(tupleTokenizer.hasMoreTokens()){
			String address = tupleTokenizer.nextToken();
			URI uri = URI.create(address);
			String node = uri.getHost() + ":" + uri.getPort();
			namenodes.put(node, node);
		}		
		
		URI uri = URI.create(namenode);
		String node = uri.getHost() + ":" + uri.getPort();
		return namenodes.containsKey(node);
	}

	public static ConcurrentLinkedQueue<InetSocketAddress> getNameNodeList() {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		ConcurrentLinkedQueue<InetSocketAddress> namenodes = new ConcurrentLinkedQueue<InetSocketAddress>();
		while(tupleTokenizer.hasMoreTokens()){
			String token = tupleTokenizer.nextToken();
			URI uri = URI.create(token);
			InetSocketAddress address = createSocketAddrForHost(uri.getHost(), uri.getPort());
			namenodes.add(address);
		}
		return namenodes;
	}
	
	public static long getServiceId(String namenode) {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		ConcurrentHashMap<String, Long> namenodes = new ConcurrentHashMap<String, Long>();
		long serviceId = 0;
		while(tupleTokenizer.hasMoreTokens()){
			String address = tupleTokenizer.nextToken();
			URI uri = URI.create(address);
			String node = uri.getHost() + ":" + uri.getPort();
			namenodes.put(node, serviceId++);
		}	
		
		URI uri = URI.create(namenode);
		String node = uri.getHost() + ":" + uri.getPort();		
		long id = namenodes.get(node);
		return id;
	}

	public static long getServiceSize() {
		StringTokenizer tupleTokenizer = new StringTokenizer(CrailConstants.NAMENODE_ADDRESS, ",");
		return tupleTokenizer.countTokens();
	}	

	public static final long blockStartAddress(long offset) {
		long blockCount = offset / CrailConstants.BLOCK_SIZE;
		return blockCount*CrailConstants.BLOCK_SIZE;
	}
	
	public static final long bufferStartAddress(long position, long sliceSize) {
		long blockCount = position / sliceSize;
		return blockCount*sliceSize;
	}	
	
	public static final long nextBlockAddress(long offset){
		if ((offset % CrailConstants.BLOCK_SIZE) == 0){
			return offset;
		} else {
			return blockStartAddress(offset) + CrailConstants.BLOCK_SIZE;
		}
	}

	public static final int minFileBuf(long fileSize, int bufSize) {
		int fileLeftOver = Integer.MAX_VALUE;
		
		long _maxInt = (long) Integer.MAX_VALUE;
		if (fileSize < _maxInt){
			fileLeftOver = (int) fileSize;
		}		
		
		return Math.min(bufSize, fileLeftOver);
	}
	
	public static String getCacheDirectory(String id){
		return CrailConstants.CACHE_PATH + "/" + id;
	}	
	
	public static void printStackTrace(){
		StackTraceElement[] elements = Thread.currentThread().getStackTrace();
		for (StackTraceElement e : elements) {
			LOG.info(e.toString());
		}		
	}
	
	public static String getName(String path){
		if (path.equalsIgnoreCase("/")){
			return "";
		}
		if (path.endsWith("/")){
			int termSlash = path.lastIndexOf('/');
			path = path.substring(0, termSlash);
		}
		
		int lastSlash = path.lastIndexOf('/');
		if (lastSlash == -1) {
			return path;
		} else if (lastSlash == 0) {
			String name = path.substring(1);
			return name;
		} else {
			String name = path.substring(lastSlash+1);
			return name;
		}		
	}
	
	public static String getParent(String path){
		if (path.equalsIgnoreCase("/")){
			return null;
		}	
		if (path.endsWith("/")){
			int termSlash = path.lastIndexOf('/');
			path = path.substring(0, termSlash);
		}		
		
		int lastSlash = path.lastIndexOf('/');
		if (lastSlash == -1) {
			return path;
		} else if (lastSlash == 0) {
			return "/";
		} else {
			String parent = path.substring(0, lastSlash);
			return parent;
		}
	}
	
	public static String combinePath(String parent, String name){
		if (parent.endsWith("/")){
			return parent + name;
		} else {
			return parent + "/" + name;
		}
	}

	public static int computeIndex(long offset) {
		long index = offset / CrailConstants.BLOCK_SIZE;
		return (int) index;
	}
	
	public static boolean isLocalAddress(InetAddress addr) {
	    // Check if the address is a valid special local or loop back
	    if (addr.isAnyLocalAddress() || addr.isLoopbackAddress())
	        return true;

	    // Check if the address is defined on any interface
	    try {
	        return NetworkInterface.getByInetAddress(addr) != null;
	    } catch (SocketException e) {
	        return false;
	    }
	}
	
	public static InetSocketAddress createSocketAddrForHost(String host, int port) {
		return new InetSocketAddress(host, port);
	}
	
	public static InetSocketAddress datanodeInfo2SocketAddr(DataNodeInfo dnInfo) throws UnknownHostException{
		return new InetSocketAddress(InetAddress.getByAddress(dnInfo.getIpAddress()), dnInfo.getPort());
	}
	
	public static CrailLocationClass getLocationClass() throws UnknownHostException{
		return CrailLocationClass.get(InetAddress.getLocalHost().getCanonicalHostName().hashCode());
	}
	
	public static void parseMap(String config, ConcurrentHashMap<String, String> map) throws Exception {
		StringTokenizer tupleTokenizer = new StringTokenizer(config, "/");
		while(tupleTokenizer.hasMoreTokens()){
			String tuple = tupleTokenizer.nextToken();
			StringTokenizer commaTokenizer = new StringTokenizer(tuple, ",");
			if (commaTokenizer.countTokens() != 2){
				throw new Exception("parsing Map, wrong format!");
			}
			String key = commaTokenizer.nextToken();
			String value = commaTokenizer.nextToken();
			map.put(key, value);
		}
	}

	public static int getStorageClasses(String storageTypes) {
		StringTokenizer tokenizer = new StringTokenizer(storageTypes, ",");
		return tokenizer.countTokens();
	}
	
	public static String getIPAddressFromBytes(byte[] bytes){
		String address = "/unresolved";
		try {
			address = InetAddress.getByAddress(bytes).toString();
		} catch(Exception e){
		}
		return address;
	}

}