package util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;

/**
 * Collection of useful functions.
 * 
 * @author Krisi
 */
public class Tools {

	public static char SEPERATOR = '.';				// seperator, for numbers??
	public static char KOMMA	 = ',';				// the mathematical komma (e.g. ',' or '.')
	
	public static int getAvailableProcessors(){
		Runtime runtime = Runtime.getRuntime();
	    int nrOfProcessors = runtime.availableProcessors();
	    
	    return Math.max(1, nrOfProcessors);
	}
	
	// ---- Sleep etc. ---- --------------------------------------------
	//
	
    /**
     * Sleep with try+catch.
     * Ignores values smaller than 1.
     * @return: true if error
     */
    static public boolean sleep(long ms)	{

    	if(ms<1)
    		return false;
    	
    	try{ 
    		Thread.sleep(ms);
    	}
    	catch(Exception e){
    		return true;		//error
    	}
    	return false;			//no error
    }
	//  /**
	//  * Sleep with try+catch and nanos
	//  * @return: true if error
	//  */
	// static public boolean sleep(long ms, int nanos)	{  					
	// 	try{ 
	// 		Thread.sleep(ms, nanos);		// 0-999999 additional nanoseconds to sleep.
	// 	}
	// 	catch(Exception e){
	// 		return true;		//error
	// 	}
	// 	return false;			//no error
	// }
    
	// ---- Strings and Numbers ---- --------------------------------------------
    //
    /**
     * Limit string to a certain length.
     * @param str 		- String
     * @param limitLow 	- limit string is shortened to
     * @param limitMax	- limit at which string is shortened to limitLow
     */
	public static String limitString(String str, int limitLow, int limitMax){
		
		if(str.length() > limitMax){
			int leftLimit  = str.length()-limitLow;
			int rightLimit = str.length();
			
			str = str.substring(leftLimit, rightLimit);
		}
		
		return str;
	}
    
	/** cuts out string from i1 to i2 (inclusive) **/
	public static String substringInverse(String str, int i1, int i2){
		String s = "";
		
		s += str.substring(0, i1);
		if(i2+1 < str.length())
			s += str.substring(i2+1, str.length());
		
		return s;
	}
	
	/**
	 * @param num... int array
	 * @return maximum int from array
	 */
	public static int max(int[] num){
		
		if(num==null || num.length<1){
			return -1;
		}
		int max = num[0];
		
		for(int i : num){
			if(i>max)
				max=i;
		}
		
		return max;
	}
	
	/**
	 * Splits all numbers out of an String 'str' and puts them into an String Array
	 * @param str
	 * @return String[] with numbers
	 */
	public static String[] numberSplit(String str) {
		
		ArrayList<String> parts = new ArrayList<String>(6);
		String temp="";

		for(int i=0; i<str.length(); i++){

			char c = str.charAt(i);

			if(Character.isDigit(c)){	//add digit
				temp+=c;
			}
			else{				//finish digit or ignore sign
				if(temp.length()>0){
					parts.add(temp);
					temp="";
				}
			}
		}
		
		if(temp.length()>0)		//add last or single number
			parts.add(temp);
		return parts.toArray(new String[1]);
	}
	
	/**
	 * Turns the String around. "abc" -> "cba"
	 * 
	 * @param s - String
	 * @return  - turned String
	 */
	public static String turnString(String s){
	
		StringBuffer sb = new StringBuffer(s.length());
		
		for(int i=s.length()-1; i>=0; i--){
			sb.append(s.charAt(i));
		}
		
		return sb.toString();
	}
	
	/**
	 * Get bytes from an Integer
	 * @param x	- integer
	 * @return	- byte[4], bytes from the Integer
	 */
	public static byte[] getBytes(int x){
		
		byte[] bA = new byte[4];
		
		for(int i=bA.length-1; i>=0; i--){	// 4 byte to shift
			
			bA[i] = (byte)x;
			x = x>>8;	// right shift 8 bit
		}
		
		return bA;
	}
	/**
	 * Fill bytes from Integer into an Array of the length 'length'
	 * (at the end of the new array)
	 * 
	 * @param x			- integer to split
	 * @param length	- length of return array
	 * @return			- byte[nrOfBytes], bytes from the Integer
	 */
	public static byte[] getBytes(int x, int length){
		
		byte[] bA1 = getBytes(x);
		byte[] bA2 = new byte[length];
		int len1 = bA1.length;
		int len2 = bA2.length;
		
		int i;
		for(i=0; i<len1 && i<len2; i++){	// copy
			bA2[len2-1-i] = bA1[len1-1-i];
		}
		//for(   ; i<len2; i++){				// fill up - zero anyway
		//	bA2[i] = 0;
		//}
		
		return bA2;
	}
	
	/**
	 * Turns the Byte Array
	 * @param bA1
	 * @return inversed byte[]
	 */
	public static byte[] invertBytes(byte[] bA1){
		
		int len = bA1.length;
		byte[] bA2 = new byte[len];
		
		for(int i=0; i<len; i++){
			bA2[i] = bA1[len-i-1]; 
		}
		
		return bA2;
	}
	
	/**
	 * Returns the Bits of an 'Long'
	 * 
	 * @param x - Long, 64 bit / 8 Byte
	 * @return  - Bits in String
	 */
	public static String getBitString(long x){
		
		String s = "";
		
		for(int i=0; i<64; i++){	// 64 bit to shift
			s += x&1;				// bit and
			x = x>>1;				// right shift one
			if(i%8==7 && i<63)		// place spaces:
				s+=" ";
		}
		return turnString(s);
	}
	/**
	 * Returns the Bits of an 'Integer'
	 * 
	 * @param x - Integer, 32 bit / 4 Byte
	 * @return  - Bits in String
	 */
	public static String getBitString(int x){
		
		String s = "";
		
		for(int i=0; i<32; i++){	// 32 bit to shift
			s += x&1;				// bit and
			x = x>>1;				// right shift one
			if(i%8==7 && i<31)		// place spaces:
				s+=" ";
		}
		return turnString(s);
	}
	public static String getBitStringAsLong(int x){
		return "00000000 00000000 00000000 00000000 " + getBitString(x);
	}
	
	/** 
	 * Returns the Bits of an 'byte' 
	 * 
	 * @param b - byte, 8 bit
	 * @return  - Bits in String
	 */
	public static String getBitString(byte b){
		
		String s = "";
		int x = b;		// must be int to shift
		
		for(int i=0; i<8; i++){		// 8 bit to shift
			s += x&1;	// bit and
			x = x>>1;	// right shift one
		}
		return turnString(s);
	}
	public static String getBitStringAsInt(byte b){
		return "00000000 00000000 00000000 " + getBitString(b);
	}
	/**
	 * Returns the Bits of an Byte-Array
	 * 
	 * @param bA	- byte []
	 * @return		- Bit String
	 */
	public static String getBitString(byte[] bA){
		
		String s = "";						// empty String
		
		for(int i=0; i<bA.length; i++){		// for every Byte
			s += getBitString(bA[i]) + " ";		// save bits
		}
		return s;
	}
	
	
	/**
	 * Return Hexcode of one character
	 * 
	 * @param c
	 * @return
	 */
	public static String makeHex(char c){
		return "" + getOneHexSign((byte)(c/16)) + getOneHexSign((byte)(c%16));
	}
	
	/**
	 * Return Hexcode of the last 4 Bit of an Byte as String
	 * 
	 * @param b
	 * @return
	 */
	private static String getOneHexSign(byte b){
		
		if(b<=9){							// Number directly
			return ""+b;
		}
		else if(b<=15){						// A to F
			return "" + (char)('A'+ b);
		}
		else{								// too high
			throw new RuntimeException("Number >= 16");
		}
	}
	
//	/**
//	 * Returns a formatted double as String (separators every third digit).
//	 * @param number		- number to process
//	 * @return 				- formatted number as string
//	 */
//	public static String formatNumber(double number){
//		return formatNumber(number, 0, 0, true, ' ');
//	}
	
//	/**
//	 * Returns a formatted double as String.
//	 * @param number		- number to process
//	 * @param vkSize		- length for String before comma. Vorkommaanteil.
//	 * @param nkSize		- length for String after comma.  Nachkommaanteil.
//	 * @param putSepepators	- puts separator (global, static) all three numbers, for better readability
//	 * @param padding		- padding sign. Filled in before vk and after nk
//	 * @return 				- formatted number as string
//	 */
//	public static String formatNumber(double number, int vkSize, int nkSize, boolean putSepepators, char padding){
//		
//		String numberStr = ""+number;
//		String[] split = numberStr.split("\\.");	// split number string at comma
//		
//		if(split.length<1 || split.length>2)
//			throw new RuntimeException("Should not happen... " + split.length + " -> " + number);
//		
//		StringBuffer vk, nk;								// Vor-Komma, Nach-Komma, def + init:
//		vk = new StringBuffer(split[0]);
//		
//		if(split.length==2)
//			nk =  new StringBuffer(split[1]);
//		else
//			nk =  new StringBuffer();
//		
//		if(putSepepators){									// Seperators:
//			
//			for(int i=vk.length()-3; i>0; i-=3){			// add seperators for "before komma":
//				vk.insert(i, SEPERATOR);
//			}
//			for(int i=vk.length()-3; i>0; i-=3){			// add seperators for "after komma":
//				vk.insert(i, SEPERATOR);
//			}
//		}
//
//		if(padding!=0){										// Padding:
//			
//			while(vk.length()<vkSize) {						// add padding for "before komma":
//				vk.insert(0, padding);
//			}
//			while(nk.length()<nkSize) {						// add padding for "after komma":
//				nk.insert(0, padding);
//			}
//		}
//		
//		String numberStr2 = vk.toString() +"."+ nk.substring(0, nkSize);
//		return numberStr2;
//	}
	
	public static String formatNumber(double number, int nkSize){
		double mult = Math.pow(10, nkSize);
		number *= mult;
		number = ((long)number)/mult;
		return ""+number;
	}
	
	/**
	 * Returns a formatted Long as String (separator every third digit)
	 * @param number
	 * @return
	 */
	public static String formatNumber(long number){
		
		//final char SEPERATOR 	= '.';				// a seperator ('.')
		final String numberStr  = "" + number;		// the number to parse
		
		StringBuffer numBuff = new StringBuffer(numberStr);	// StringBuffer for return number

		for(int i=numberStr.length()-3; i>0; i-=3){			// Set seperators:
			numBuff.insert(i, SEPERATOR);
		}
		
		return numBuff.toString();				// Return formatted number
	}
	
	/**
	 * Returns a String with an formatted size (KB, MB, GB, ...)
	 * 
	 * @param size in byte
	 * @return Size in appropriate form.
	 */
	public static String formatBytes(long size){
		
		//final char SEPERATOR = '.';			// a seperator ('.')
		//final char KOMMA	 = ',';				// the mathematical komma (',')
		
		final String number  = "" + size;		// the size to parse
		
		int dim = (number.length()-1)/3;					// 'dimension' 10^3, 10^6, 10^9, ...
		int div=dim*3;										// Divisor. Not really needed?
		StringBuffer typeBuff = new StringBuffer(" Byte");	// Buffer for return Type (KB, MB, GB, ...)
		
		switch(dim){										// --- Get correct extension: ---
			case 0: /* nix */               break;
			case 1: typeBuff.insert(1, 'K'); break;
			case 2: typeBuff.insert(1, 'M'); break;
			case 3: typeBuff.insert(1, 'G'); break;
			case 4: typeBuff.insert(1, 'T'); break;
			case 5: typeBuff.insert(1, 'P'); break;
			default: typeBuff.insert(1, '_'); div=0; 
		}
		
		StringBuffer numBuff = new StringBuffer(number);	// StringBuffer for return number
		
		if(div == 0){										// --- Just some bytes. return number+type: ---
			// nix
		}
		else{												// --- Put seperator and komma: ---
			
			for(int i=number.length()-3; i>0; i-=3){			// add seperators:
				numBuff.insert(i, SEPERATOR);
			}
			
			for(int i=1; i<numBuff.length(); i++){				// Set komma:
				if(numBuff.charAt(i)==SEPERATOR){
					numBuff.deleteCharAt(i);
					numBuff.insert(i, KOMMA);
					break;
				}
			}
		}
		
		return numBuff.toString() + typeBuff;				// Return number and Type
	}
	
	/** @return true if the 'value' is within 'baseValue' +- 'tolerance'  **/
	public static boolean within(double baseValue, double tolerance, double value){
		
		if(tolerance<0)
			tolerance *= -1;
		
		return value>(baseValue-tolerance) && value<(baseValue+tolerance);
	}
	
	/**
	 * @param d
	 * @return string with excel-compatible comma
	 */
	public static String excelKomma(double d){
		String s = ""+d;
		return s.replace('.', ',');
	}
	/**
	 * @param d - string with excel compatible comma
	 * @return string with java-compatible comma
	 */
	public static String javaKomma(String d){
		return d.replace(',', '.');
	}
	
	/**
	 * Normal rounding. 
	 *   0.1 -> 0
	 *   0.5 -> 1
	 *   0.9 -> 1
	 * @param f
	 * @return
	 */
	public static int round(float f){
		return (int) (f+0.5f);
	}
	/**
	 * Round up. 
	 *   0.1 -> 1
	 *   0.5 -> 1
	 *   1.001 -> 2
	 * @param f
	 * @return
	 */
	public static int roundUp(float f){
		return (int) (f+1f);
	}
	
	/**
	 * Use Byte unsigned as Integer. (-1 = highest value)
	 * 
	 * @param num - byte, 8 bit
	 * @return Integer from all 8 bit. No sign.
	 */
	public static int unsignedByteToInt(byte num){
		
		/* variant B:
		 *
		int mask1 = 0x4F;			// Bit 7 and below is set
		int mask2 = 0x80;			// Bit 8 is set
		int i;
		if(num<0)						// conversion of negative number (take first bit + put back)
			i = (num & mask1) | mask2;
		else							// direct conversion for positive number (first bit is zero)
			i = num;
		
		//Out.pl(Tools.getBitString(num) + " ...num");
		//Out.pl(Tools.getBitString(num&0xFF) + " ...num correct");
		//Out.pl(Tools.getBitString(i) + " ...i");
		
		return i;
		*/
		
		return (num & 0xFF);		// java internal ability. [mask = 8 1-bits]
	}
	
	/**
	 * Use Integer unsigned as Long. (-1 = highest value)
	 * @param num - Integer, 32 bit
	 * @return Long from all 32 Bit. No sign.
	 */
	public static long unsignedIntToLong(int num){
		
		/*
		 * 
		long mask1 = 0x4FFFFFFFl;		// Bit 31 and below is set
		long mask2 = 0x80000000l;		// Bit 32 is set
		//Out.pl(Tools.getBitString(mask1) + " ...mask1");
		//Out.pl(Tools.getBitString(mask2) + " ...mask2");
		//Out.pl(Tools.getBitStringAsLong(num) + " ...num");
		
		long L;
		if(num<0)						// conversion of negative number (take first bit + put back)
			L = (num & mask1) | mask2;
		else							// direct conversion for positive number (first bit is zero)
			L = num;
		
		return L;
		*/
		
		return num & 0xFFFFFFFFl;		// java internal ability. [mask = 32 1-bits]
	}
	
	// ---- System and Memory ---- --------------------------------------------
	//
	
	/**
	 * Print system information
	 * @param doGarbageCollect
	 */
	public static void systemInfo(boolean doGarbageCollect){
		
		if(doGarbageCollect)
			Runtime.getRuntime().gc();						// garbage collect
		long freeMemH = Runtime.getRuntime().freeMemory();	// get free memory
		long heapSize = Runtime.getRuntime().totalMemory();	// get heap memory
		long maxMem   = Runtime.getRuntime().maxMemory();	// get max memory
		long usedMem  = (heapSize-freeMemH);				// used memory
		long freeMem  = (maxMem-usedMem);					// total free memory
		int  cores    = Runtime.getRuntime().availableProcessors();	// CPU cores
		
		System.out.println("CPU cores           : " + rightAlign(formatNumber(cores) ,14) );
		System.out.println();
		System.out.println("Used Memory         : " + rightAlign(formatNumber(usedMem) ,14) );
		System.out.println("Free Memory         : " + rightAlign(formatNumber(freeMem) ,14) );		/*
		System.out.println("Free Memory in Heap : " + rightAlign(formatNumber(freeMemH) ,14) );		*/
		System.out.println("Heap size           : " + rightAlign(formatNumber(heapSize),14) );
		System.out.println("Maximum Memory      : " + rightAlign(formatNumber(maxMem)  ,14) );
	}
	
	/**
	 * Get total free memory for java.
	 * @param doGarbageCollect
	 * @return free memory, long
	 */
	public static long getFreeMem(boolean doGarbageCollect){
		if(doGarbageCollect)
			Runtime.getRuntime().gc();						// garbage collect
		long freeMemH = Runtime.getRuntime().freeMemory();	// get free memory
		long heapSize = Runtime.getRuntime().totalMemory();	// get heap memory
		long maxMem   = Runtime.getRuntime().maxMemory();	// get max memory
		long usedMem  = (heapSize-freeMemH);				// used memory
		long freeMem  = (maxMem-usedMem);					// total free memory
		
		return freeMem;
	}
	
	/**
	 * Throws RuntimeException if size is bigger than the available Memory.
	 * @param size
	 * @return available memory in Bytes. 1 MB tolerance subtracted for overhead etc.
	 */
	public static long testAvailableRam(long size){
		//Tools.systemInfo(false);
		long freeMem = getFreeMem(true) - 1024*1024;	// actual free memory minus 1MB overhead and tolerance
		if(size > freeMem)
			throw new RuntimeException("Not enough Memory! Only "+freeMem+" available.");
		
		return freeMem;
	}
	/**
	 * Throws RuntimeException if size is bigger than the available Memory.
	 * OR if size is bigger than Integer.MAX_VALUE.
	 * Uses 'long testAvailableRam(long size)'
	 * @param size
	 * @return available memory in Bytes. 1 MB tolerance subtracted for overhead etc.
	 */
	public static int testAvailableRamInt(long size){
		//Tools.systemInfo(false);
		long freeMem = testAvailableRam(size);
		if(size > Integer.MAX_VALUE)
			throw new RuntimeException("Requested Memory too big for an Integer! Only "+Integer.MAX_VALUE+" possible.");
		
		return (int)freeMem;
	}
	
	/**
	 * Get whole file from target.
	 * Reads a real File into the RAM.
	 * @param fileStr		- file with path+name
	 * @throws IOException	- may be thrown
	 */
	public static byte[] readFileToArray(String fileStr) throws IOException{
		
		File file = new File(fileStr);						// get file information
		
		long sizeL = file.length();							// get file length
		Tools.testAvailableRamInt(sizeL);					// test length and available ram (smaller Integer.MAX_VALUE)
		int  size  = (int) sizeL;							// long to int
		
		FileInputStream in = new FileInputStream(file);		// make input stream for reading
		byte[] data = new byte[size];						// make array for data
		in.read(data);										// read file data
		in.close();											// close input stream
		
		return data;
	}
	
	
	// ---- String alignment ---- --------------------------------------------
	//
	
	/**
	 * Aligns string s on the right in space total characters.
	 * Fills right side with spaces.
	 * 
	 * @param s
	 * @param spaces
	 * @return aligned String
	 */
	static public String leftAlign(String s, int space)	{ 
    	StringBuffer b = new StringBuffer(space+8);
    	int spaces=space-s.length(); 
    	b.append(s); 
    	for(int i=0; i<spaces; i++) 
    		b.append(' '); 
    	return b.toString();
	}
	
	/**
	 * See 'leftAlign(String s, int space)'
	 * @param o
	 * @param space
	 * @return aligned String
	 */
    static public String leftAlign(Object o, int space)	{ 
    	return leftAlign(o.toString(), space);
    }
	
	/**
	 * Aligns string s on the right in space 'space'.
	 * Fills left side with spaces.
	 * 
	 * @param s
	 * @param spaces
	 * @return aligned String
	 */
    static public String rightAlign(String s, int space)	{ 
    	
    	StringBuffer b = new StringBuffer(space+8);
    	int spaces=space-s.length(); 
    	for(int i=0; i<spaces; i++) 
    		b.append(' '); 
    	b.append(s); 
    	return b.toString();
    }
    
    /**
     * See 'rightAlign(String s, int space)'
     * @param v
     * @param space
	 * @return aligned String
     */
    static public String rightAlign(Object v, int space) { 	
    	return rightAlign(v.toString(), space);	
    }
    
    /**
     * @param o
     * @return length of object.toString()
     */
    public static int getLength(Object o){
    	String s = o.toString();
    	return s.length();
    }
    
    /**
     * @param numbers
     * @return highest number
     */
    public static long max(long[] numbers){
    	
    	long max = numbers[0];
    	
    	for(long n : numbers) {
    		if(n > max)
    			max=n;
    	}
    	
    	return max;
    }
    
    /**
     * Prints a long[] in a formatted way.
     * @param numbers    - numbers to print
     * @param lineLength - maximum length of a line in character signs
     */
    public static void printFormatedLong(long[] numbers, int lineLength, int numLength){
    	
    	//int  lineLength = 50;
    	//long highestNum = max(numbers);
    	//int  numLength  = getLength(highestNum);
    	
    	String tmp;
    	StringBuffer line = new StringBuffer(lineLength);
    	
    	for(int i=0; i<numbers.length; i++){
    		
    		tmp = leftAlign(numbers[i], numLength);
    		line.append(tmp);
    		
    		if(line.length()+numLength+2 <= lineLength &&
    				i+1 < numbers.length					){
    			line.append(", ");
    		}
    		else{
    			System.out.println(line.toString());
    			line.delete(0, line.length());
    			//line.append("\n");
    		}
    	}
    	
    	if(line.length()>0)
    		System.out.println(line.toString());
    	
    }
    
    /**
     * @see printFormatedLong(long[], int, int)
     * @param numbers
     * @param lineLength
     */
    public static void printFormatedLong(long[] numbers, int lineLength){
    	long highestNum = max(numbers);
    	int  numLength  = getLength(highestNum);
    	
    	printFormatedLong(numbers, lineLength, numLength);
    }
    
    /**
     * @see printFormatedLong(long[], int, int)
     * @param numbers
     */
    public static void printFormatedLong(long[] numbers){
    	printFormatedLong(numbers, 80);
    }
    
    
	// ---- ______________ ---- --------------------------------------------
	//


    
    
	// ---- Classes and Compiling ---- --------------------------------------------
	//
	// See: util.classLoading . ClassLoading.java
}

