/*
 * $Id: VKCCoder.java,v 1.6 2006/03/23 11:44:30 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: VKCCoder.java,v $
 * Revision 1.6  2006/03/23 11:44:30  marbobo
 * + Mögliche NPE abgefangen
 *
 * Revision 1.5  2004/11/10 15:02:47  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.4  2004/08/01 21:28:18  marbobo
 * - Unnötige Casting-Anweisungen entfernt
 *
 * Revision 1.3  2004/06/24 23:33:32  marbobo
 * Quellcode maintenance
 *
 * Revision 1.2  2003/07/31 13:59:29  bigmac
 * Compilerwarnungen beseitigt
 *
 * Revision 1.1  2003/07/21 12:39:45  marbobo
 * Wieder reingestellt
 *
 * Revision 1.3  2001/08/22 10:21:02  bigmac
 * encode() liefert "" wenn null uebergeben wird
 *
 * Revision 1.2  2001/08/02 13:57:47  bigmac
 * Compilerfehler unter jdk1.4 behoben
 *
 * Revision 1.1.1.1  2001/07/09 08:51:45  vkc
 * VKC Repository
 *
 * Revision 2.6  2001/06/07 19:03:33  bigmac
 * pretty printed
 *
 * Revision 2.5  2001/01/12 09:57:51  richert
 * mit base64Decode()
 *
 * Revision 2.4  2000/09/29 13:00:46  bigmac
 * *** empty log message ***
 *
 * Revision 2.3  2000/08/21 12:13:00  lawern
 * Debug-Ausgaben auskommentiert
 *
 * Revision 2.2  2000/08/21 11:45:13  richert
 * Rudimentaere Unterstuetzung fuer das webinterface.
 *
 * Revision 2.1  2000/07/11 14:36:34  bigmac
 * *** empty log message ***
 *
 * Revision 2.0  2000/05/05 14:47:12  bigmac
 * VKC auf Java 2 umgestellt
 *
 * Revision 1.4  2000/04/08 10:43:04  richert
 * encode() und decode() um String-Variante ergaenzt.
 *
 * Revision 1.3  2000/03/30 14:00:22  richert
 * Padding-Bug behoben.
 *
 * Revision 1.2  2000/03/28 08:47:30  richert
 * base64 haelt nun die <76 Zeichen/Zeile - Beschraenung aus rfc2045 ein.
 *
 * Revision 1.1  2000/01/07 12:59:24  lawern
 * initiale Version
 *
 *
 */

package vkc.util;

import vkc.misc.VKCLogManager;

public class VKCCoder {
	static boolean debug = false;
	static short spercent = (short)'%';
	static short splus = (short)'+';
	static byte bspace = (byte)' ';
	static byte bplus = (byte)'+';
	static byte bpercent = (byte)'%';

	public static byte[] encode (byte[] data) {
		if (data == null) {
			return null;
		}
		
		byte[] ba = null;
		byte[] resarr = null;
		
		try	{
			int index = 0;
			int hex;

			ba = new byte[data.length << 2];
			
			for (int i = 0; i < data.length; i++) {
				int b = data[i] & 0xFF;
				char c = (char) b;

				if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9'))) {
					ba[index++] = (byte) b;
				} else {
					if (c == ' ') {
						ba[index++] = bplus;
					} else {
						ba[index++] = bpercent;
						//erste Hex-Ziffer
						hex = b >> 4;
						
						if (hex < 10) {
							ba[index++] = (byte)(hex + 48);
						} else {
							ba[index++] = (byte)(hex + 87);
						}
						
						//zweite Hex-Ziffer
						hex = (byte)(b & 0x0F);
						
						if (hex < 10) {
							ba[index++] = (byte)(hex + 48);
						} else {
							ba[index++] = (byte)(hex + 87);
						}
					}
				}
			}
			
			resarr = new byte[index];
			System.arraycopy(ba, 0, resarr, 0, index);
			ba = null;
			
			return resarr;
		} catch (Exception e) {
			if (debug) {
				e.printStackTrace();
			}
			
			return null;
		}
	}

	// ###############################################################
	// Method: decode()
	// ###############################################################
	//
	// Purpose: Byte-Arrays base64-dekodieren
	//
	// Return: null => Fehler aufgetreten oder data war null
	//         Byte-Arraysonst, sonst
	//
	// ###############################################################

	public static byte[] decode (byte[] data) {
		if (data == null) {
			return null;
		}
		
		byte[] ba = null;
		try {
			int i1, i2;
			int index = 0;
			ba = new byte[data.length];

			for (int i = 0; i < data.length ; i++) {
				short b = data[i];
				
				if (b == spercent) {
					i1 = data[++i] & 0xFF;

					if (i1 >= 0x61 && i1 <= 0x66) {// a bis f ..
						i1 = 10 + i1 - 0x61; // nach 10 bis 15 konvertieren
					} else {
						i1 = i1 - 0x30; // sonst ASCII-Code von '1'-1 abziehen
					}
					
					i1 = i1 <<4;
					i2 = data[++i];

					if (i2 >= 0x61 && i2 <= 0x66) {// a bis f ..
						i2 = 10 + i2 - 0x61; // nach 10 bis 15 konvertieren
					} else {
						i2 = i2 - 0x30; // sonst ASCII-Code von '1'-1 abziehen
					}
					
					ba[index++] = (byte)(i1 + i2);
				} else {
					if (b == splus) {
						ba[index++] = bspace;
					} else {
						ba[index++] = data[i];
					}
				}
			}

			byte resarr[] = new byte[index];
			System.arraycopy(ba, 0, resarr, 0, index);
			ba = null;
			
			return resarr;
		} catch (Exception e) {
			if (debug) {
				e.printStackTrace();
			}

			return null;
		}
	}

	public static void main(String[] args) {
		/*
		File fd = new File(args[0]);
		byte[] fileData = new byte[(int)fd.length()];
		try
	{
		 FileInputStream fis = new FileInputStream(fd);
		 System.out.println("Laenge: "+fileData.length);
		 fis.read(fileData);
		 byte[] encoded = base64Encode(fileData);
		 String s = new String(encoded);
		 System.out.println(s);
	}
		catch (FileNotFoundException e)
	{
	}
		catch (IOException e)
	{
	}
		*/
		//System.out.println(crypt(args[0], args[1], 0,0)); // (new Date()).getTime(), Math.random()));
		//System.out.println(crypt("richert", "riPxEKMV1rleM", 0,0)); //(new Date()).getTime(), Math.random()));

		//System.out.println(UnixCrypt.crypt(args[0]));


		/*
		 byte test[] = new byte[256];
		 for (int i=0; i<256; i++)
		   test[i] = (byte)i;

		 byte encodedTest[] = encode(test);
		 test = decode(encodedTest);

		 for (int i=0; i<test.length; i++)
		   if ((test[i]&0xFF) != i)
		System.out.println("Problem in Zelle "+i);
		*/
	}
	
   /**
	* This method is very similar to URLEncoder.encode(), except that here
	* spaces are also encoded instead of replacing them with a '+'
	*/
	public static String encode(String str) throws Exception {
		if (str == null) {
			return "";
		}
		
		StringBuffer buf = new StringBuffer();
		
		for (int i = 0, len = str.length(); i < len ; i++) {
			char c = str.charAt(i);
			
			if (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9'))) {
				buf.append(c);
			} else {
				buf.append('%');
				char ch1 = Character.forDigit((c >> 4) & 0xF, 16);
				char ch2 = Character.forDigit((c >> 0) & 0xF, 16);

				buf.append(ch1);
				buf.append(ch2);
			}
		}
		
		return buf.toString();
	}

	public static String decode (String str) {
		StringBuffer buf = new StringBuffer();

		for (int i = 0, len = str.length(); i < len ; i++) {
			char c = str.charAt(i);
			
			if (c == '+') {
				c = ' ';
			} else if (c == '%') {
				c = (char)(((short)(Character.digit(str.charAt(++i), 16) << 4)) | (short) Character.digit(str.charAt(++i), 16));
			}
			
			buf.append(c);
		}
		
		return buf.toString();
	}

	public final static String base64Encode(String strInput) {
		if (strInput == null) {
			return null;
		}
		
		try {
			return new String(base64Encode(strInput.getBytes()));
		} catch (Throwable e) {
		    VKCLogManager.errorLog(VKCCoder.class, "", e.getMessage(), e);
			
		    return null;
		}
	}

	// Dieser Algorithmus haelt sich nicht an die
	// 76Zeichen/Zeile-Begrenzung. Koennte bei einigen
	// Routern/Mailservern zu Problemen fuehren.
	public final static byte[] base64Encode(byte[] byteData) {
		if (debug) {
			System.out.println("base64Encode: byteData.length=" + byteData.length);
		}
		
		if (byteData == null) {
			return null;
		}
		
		int iSrcIdx; // index into source (byteData)
		int iDestIdx; // index into destination (byteDest)
		int destLaenge = ((byteData.length + 2) / 3) * 4; // normale Laenge
		destLaenge += (destLaenge / 72) * 2; // + CRLF auf alle 72 Zeichen
		// Bemerkung: In den RFC steht, das eine Laenge von 76 Zeichen nicht ueberschritten werden darf.
		// Wir orientieren uns hier an Netscape Mail, das 72 benutzt.
		
		if (debug) {
			System.out.println("destLaenge=" + destLaenge);
		}
		
		byte byteDest[] = new byte[destLaenge];
		int counter; // 76 Zeichen pro Zeile sind von rfc2045 festgeschrieben worden

		for (iSrcIdx = 0, iDestIdx = 0, counter = 1; iSrcIdx < byteData.length - 2; iSrcIdx += 3, counter++) {
			byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx] >>> 2) & 077);
			byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx + 1] >>> 4) & 017 | (byteData[iSrcIdx] << 4) & 077);
			byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx + 2] >>> 6) & 003 | (byteData[iSrcIdx + 1] << 2) & 077);
			byteDest[iDestIdx++] = (byte)(byteData[iSrcIdx + 2] & 077);
			
			if (counter == 18) {// 19*4=76
				counter = 0;
				iDestIdx += 2;
			}
		}

		if (debug) {
			System.out.println("iSrcIdx=" + iSrcIdx + " byteDest.length=" + byteDest.length + " counter=" + counter);
		}
		
		if (iSrcIdx < byteData.length) {
			byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx] >>> 2) & 077);
			if (iSrcIdx < byteData.length - 1) {
				byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx + 1] >>> 4) & 017 | (byteData[iSrcIdx] << 4) & 077);
				byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx + 1] << 2) & 077);
			} else {
				byteDest[iDestIdx++] = (byte)((byteData[iSrcIdx] << 4) & 077);
			}
		}

		if (debug) {
			System.out.println("Vor der letzten Schleife: iSrcIdx=" + iSrcIdx + " iDestIdx=" + iDestIdx);
		}
		
		for (iSrcIdx = 0, counter = 1; iSrcIdx < iDestIdx; iSrcIdx++, counter++) {
			if (counter == 73) {
				counter = 0;
				byteDest[iSrcIdx++] = (byte) 10;
				byteDest[iSrcIdx] = (byte) 13;
			} else if (byteDest[iSrcIdx] < 26) {
				byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + 'A');
			} else if (byteDest[iSrcIdx] < 52) {
				byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + 'a'-26);
			} else if (byteDest[iSrcIdx] < 62) {
				byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + '0'-52);
			} else if (byteDest[iSrcIdx] < 63) {
				byteDest[iSrcIdx] = (byte)'+';
			} else {
				byteDest[iSrcIdx] = (byte)'/';
			}
		}

		if (debug) {
			System.out.println("iSrcIdx=" + iSrcIdx + " byteDest.length=" + byteDest.length);
		}
		
		for (; iSrcIdx < byteDest.length; iSrcIdx++) {
			byteDest[iSrcIdx] = (byte)'=';
		}
		
		return byteDest;
	}

	private static byte[] codes = new byte[256];
	private static boolean initialisiert = false;

	/**
	 * Decodes a BASE-64 encoded stream to recover the original
	 * data. White space before and after will be trimmed away,
	 * but no other manipulation of the input will be performed.
	 *
	 * As of version 1.2 this method will properly handle input
	 * containing junk characters (newlines and the like) rather
	 * than throwing an error. It does this by pre-parsing the
	 * input and generating from that a count of VALID input
	 * characters.
	 **/
	public final static byte[] base64Decode(char[] data) {
		// as our input could contain non-BASE64 data (newlines,
		// whitespace of any sort, whatever) we must first adjust
		// our count of USABLE data so that...
		// (a) we don't misallocate the output array, and
		// (b) think that we miscalculated our data length
		//     just because of extraneous throw-away junk

		if (!initialisiert) {
			for (int i = 0; i < 256; i++) {
				codes[i] = -1;
			}
			
			for (int i = 'A'; i <= 'Z'; i++) {
				codes[i] = (byte)(i - 'A');
			}
			
			for (int i = 'a'; i <= 'z'; i++) {
				codes[i] = (byte)(26 + i - 'a');
			}
			
			for (int i = '0'; i <= '9'; i++) {
				codes[i] = (byte)(52 + i - '0');
			}
			
			codes['+'] = 62;
			codes['/'] = 63;

			initialisiert = true;
		}


		int tempLen = data.length;
		
		for (int ix = 0; ix < data.length; ix++) {
			if ((data[ix] > 255) || codes[data[ix]] < 0) {
				--tempLen; // ignore non-valid chars and padding
			}
		}
		
		// calculate required length:
		//  -- 3 bytes for every 4 valid base64 chars
		//  -- plus 2 bytes if there are 3 extra base64 chars,
		//     or plus 1 byte if there are 2 extra.

		int len = (tempLen / 4) * 3;
		
		if ((tempLen % 4) == 3) {
			len += 2;
		}
		
		if ((tempLen % 4) == 2) {
			len += 1;
		}
		
		byte[] out = new byte[len];

		int shift = 0; // # of excess bits stored in accum
		int accum = 0; // excess bits
		int index = 0;

		// we now go through the entire array (NOT using the 'tempLen' value)
		for (int ix = 0; ix < data.length; ix++) {
			int value = (data[ix] > 255) ? -1 : codes[data[ix]];

			if (value >= 0) {// skip over non-code
				accum <<= 6; // bits shift up by 6 each time thru
				shift += 6; // loop, with new bits being put in
				accum |= value; // at the bottom.

				if (shift >= 8) {// whenever there are 8 or more shifted in,
					shift -= 8; // write them out (from the top, leaving any
					out[index++] = // excess at the bottom for next iteration.
			        (byte)((accum >> shift) & 0xff);
				}
			}
			
			// we will also have skipped processing a padding null byte ('=') here;
			// these are used ONLY for padding to an even length and do not legally
			// occur as encoded data. for this reason we can ignore the fact that
			// no index++ operation occurs in that special case: the out[] array is
			// initialized to all-zero bytes to start with and that works to our
			// advantage in this combination.
		}

		// if there is STILL something wrong we just have to throw up now!
		if (index != out.length) {
			throw new Error("Miscalculated data length (wrote " + index + " instead of " + out.length + ")");
		}

		return out;
	}

	public final static String base64Decode(String strInput) {
		if (strInput == null) {
			return null;
		}
		
		//System.out.println("strInput.length()="+ strInput.length());

		char charData[] = new char[strInput.length()];
		strInput.getChars(0, strInput.length(), charData, 0);

		return new String(base64Decode(charData));
    }
}