package com.mgie.encryption;

public class GenerationLibrary
{
	public final static boolean DEBUG = false;
	
	public final static int TOTAL_VALUE_BASE32 = 32;
	
	public final static int TOTAL_INT_BIT = 32;
	public final static int TOTAL_BPC = 5;
	public final static int TOTAL_CODE = ((TOTAL_INT_BIT + TOTAL_INT_BIT + TOTAL_BPC - 1) / TOTAL_BPC);
	public final static int TOTAL_CODE_WITH_CHECKBITS = ((TOTAL_INT_BIT + TOTAL_INT_BIT + TOTAL_BPC - 1) / TOTAL_BPC) + 2;
	
	public final static int MAX_RANGE = 1000000000;
	public final static int TOTAL_NUMBER_DIGIT = 10;
	public final static int TOTAL_DIGIT = 9;
	
	public final static int DIGIT_TABLE [] =
	{
		10,
		100,
		1000,
		10000,
		100000,
		1000000,
		10000000,
		100000000,
		1000000000,
	};
	
	public final static int UID = 0;
	public final static int SCORE = 1;
	
	public final static int MASK1 = 0xAAAAAAAA;	//10101010 10101010 10101010 10101010
	public final static int MASK2 = 0x55555555;	//01010101 01010101 01010101 01010101
	
	public final static int SINGLE_BIT_MARK = 0x1;	//00000001
	
	public final static int [][] UID_TABLE = 
	{
		{5,	2,	4,	1,	7,	0,	3,	9,	6,	8},
		{2,	4,	1,	7,	0,	3,	9,	6,	8,	5},
		{4,	1,	7,	0,	3,	9,	6,	8,	5,	2},
		{1,	7,	0,	3,	9,	6,	8,	5,	2,	4},
		{7,	0,	3,	9,	6,	8,	5,	2,	4,	1},
		{0,	3,	9,	6,	8,	5,	2,	4,	1,	7},
		{3,	9,	6,	8,	5,	2,	4,	1,	7,	0},
		{9,	6,	8,	5,	2,	4,	1,	7,	0,	3},
		{6,	8,	5,	2,	4,	1,	7,	0,	3,	9},
		{8,	5,	2,	4,	1,	7,	0,	3,	9,	6}
	};
	
	public final static int [][] SOURCE_TABLE = 
	{
		{7,	4,	5,	2,	3,	6,	1,	8,	9,	0},
		{4,	5,	2,	3,	6,	1,	8,	9,	0,	7},
		{5,	2,	3,	6,	1,	8,	9,	0,	7,	4},
		{2,	3,	6,	1,	8,	9,	0,	7,	4,	5},
		{3,	6,	1,	8,	9,	0,	7,	4,	5,	2},
		{6,	1,	8,	9,	0,	7,	4,	5,	2,	3},
		{1,	8,	9,	0,	7,	4,	5,	2,	3,	6},
		{8,	9,	0,	7,	4,	5,	2,	3,	6,	1},
		{9,	0,	7,	4,	5,	2,	3,	6,	1,	8},
		{0,	7,	4,	5,	2,	3,	6,	1,	8,	9}
	};
	
	public static long seed1 = 1010002406;
	public static long seed2 = 501;
	public static int seed3 = 23;
	
	public static char [] BASE32 = 
	{
		'4',	'H',	'J',	'K',	'9',	'R',	'S',	'8',	'X',	'U',
		'6',	'E',	'7',	'D',	'G',	'W',	'2',	'N',	'A',	'3',
		'Z',	'B',	'C',	'L',	'F',	'P',	'V',	'5',	'Y',	'T',
		'Q',	'M'								

	};
	
	public static String BASE32_STR;
	static 
	{
		//fustion table
		for (int i = 0; i < BASE32.length; ++i)
		{
			seed1 = seed1 - (seed2 * i);
			int index = (int)(seed1 % BASE32.length);
			char tmp = BASE32[index];
			BASE32[index] = BASE32[i];
			BASE32[i] = tmp;
			
			BASE32_STR = new String (BASE32);
		}
	}
	
	
	public GenerationLibrary()
	{
		super();
	}
	
	public static int[] decryption(String code)
	{
		int[] result = new int[2]; 
		result[0] = -1;
		result[1] = -1;
		try
		{
			if (DEBUG)
			{
				System.out.println ("encode code: " + code);
				System.out.println(TOTAL_CODE);
			}
			
			if (code.length() == TOTAL_CODE)
			{
				result = base32DecodingData32(code, BASE32_STR);
				
				int uid = (result[0] & MASK1) | (result[1] & MASK2);
				int score = (result[0] & MASK2) | (result[1] & MASK1);

				if (DEBUG)
				{
					System.out.println ("encode result[UID]: " + result[UID]);
					System.out.println ("encode result[SCORE]: " + result[SCORE]);
				}

				result[UID] = decodeNumber(uid, UID_TABLE);
				result[SCORE] = decodeNumber(score, SOURCE_TABLE);

				if (DEBUG)
				{
					System.out.println ("orignal result[UID]: " + result[UID]);
					System.out.println ("orignal result[SCORE]: " + result[SCORE]);
				}
			}
		} catch (Exception e)
		{
			
		}
		return result;
	}
	
	public static String encryption (int uid, int score)
	{
		String result = null;
		
		if (DEBUG)
		{
			System.out.println ("orignal uid: " + uid);
			System.out.println ("orignal score: " + score);
		}
		
		uid = encodeNumber(uid, UID_TABLE);
		score = encodeNumber(score, SOURCE_TABLE);
		
		if (DEBUG)
		{
			System.out.println ("encode uid: " + uid);
			System.out.println ("encode score: " + score);
		}
		
		int part1 = (uid & MASK1) | (score & MASK2);
		int part2 = (uid & MASK2) | (score & MASK1);
		
		int[] data32 = new int[2];
		data32[0] = part1;
		data32[1] = part2;
		
		try
		{
			result = base32Encoding(data32, BASE32_STR);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		
		if (DEBUG)
		{
			System.out.println ("encode result: " + result);
		}
		
		return result;
	}
	
	public static int encodeNumber (int raw, int[][] table)
	{
		int result = 0;
		if (raw >= 0 && raw < MAX_RANGE)
		{
			for (int i = TOTAL_DIGIT - 1; i >= 0; --i)
			{
				int ref10_1 = DIGIT_TABLE[i];
				int ref10_2 = ref10_1 / TOTAL_NUMBER_DIGIT;
				int tmp = raw % ref10_1;
				int oneDigit = tmp / ref10_2;
				int encryptOneDigit = table[i][oneDigit];
				result = result + (encryptOneDigit * ref10_2);
			}
		}
		return result;
	}
	
	public static int decodeNumber (int encoded, int[][] table)
	{
		int result = 0;
		if (encoded >= 0 && encoded < MAX_RANGE)
		{
			for (int i = TOTAL_DIGIT - 1; i >= 0; --i)
			{
				int ref10_1 = DIGIT_TABLE[i];
				int ref10_2 = ref10_1 / TOTAL_NUMBER_DIGIT;
				int tmp = encoded % ref10_1;
				int oneDigit = tmp / ref10_2;
				int decryptOneDigit = 0;
				for (int j = 0; j < TOTAL_NUMBER_DIGIT; ++j)
				{
					if (table[i][j] == oneDigit)
					{
						decryptOneDigit = j;
					}
				}
				
				result = result + (decryptOneDigit * ref10_2);
			}
			
		}
		return result;
	}
	
	public static String base32Encoding ( int[] data32, String base32_reftable )
	{
		StringBuffer sb = new StringBuffer();
		int remainBit = TOTAL_BPC;
		int bits = 0;
		int thebit = 0;
		try
		{
			for (int i = 0; i < data32.length; ++i)
			{
				if (DEBUG)
				{
					System.out.println ("base32Encoding data32[" + i + "]: " + data32[i]);
				}
				for (int j = TOTAL_INT_BIT - 1; j >= 0; --j)
				{
					if (remainBit == 0)
					{
						if (DEBUG)
						{
							System.out.println ("base32Encoding charAT: " + base32_reftable.charAt(bits));
						}
						
						sb.append(base32_reftable.charAt(bits));
						bits = 0;
						remainBit = TOTAL_BPC;
					}
					thebit = (data32[i] & (SINGLE_BIT_MARK << j));
					thebit = thebit >> j;
					--remainBit;
					bits = bits | (thebit << remainBit);
				}
			}
		} catch (Exception e)
		{
			if (DEBUG)
			{
				System.out.println("Exception, bits: " + bits); 
			}
		}
		sb.append(BASE32[bits]);
		return sb.toString();
	}
	
	
	public static int[] base32DecodingData32 ( String code, String base32_reftable )
	{
		StringBuffer sb = new StringBuffer();
		int remainBit = TOTAL_INT_BIT;
		int bits = 0;
		int thebit = 0;
		
		int totalInt = code.length() * 5 / TOTAL_INT_BIT;
		int currentInt = 0;
		char [] charCode = code.toCharArray();
		int [] data32 = new int [totalInt];
		for (int i = 0; i < charCode.length; ++i)
		{
			if (DEBUG)
			{
				System.out.println ("base32Encoding charAT: " + charCode[i]);
			}
			bits = base32_reftable.indexOf(charCode[i]);
			if (remainBit > TOTAL_BPC)
			{
				remainBit = remainBit - TOTAL_BPC;
				data32[currentInt] = data32[currentInt] | (bits << remainBit);
			} else 
			{
				int j = 0;
				for (; 0 < remainBit; --remainBit, ++j)
				{
					int ref = TOTAL_BPC - j - 1;
					thebit = (bits & (SINGLE_BIT_MARK << ref));
					thebit = thebit >> ref;
					data32[currentInt] = data32[currentInt] | (thebit << (remainBit - 1));
				}
				if (DEBUG)
				{
					System.out.println ("base32Decoding data32[" + currentInt + "]: " + data32[currentInt]);
				}
				++currentInt;
				if (currentInt < totalInt)
				{
					remainBit = TOTAL_INT_BIT;
				
					for ( ; j < TOTAL_BPC; ++j)
					{
						int ref = TOTAL_BPC - j - 1;
						thebit = (bits & (SINGLE_BIT_MARK << ref));
						thebit = thebit >> ref;
						remainBit = remainBit - 1;
						data32[currentInt] = data32[currentInt] | (thebit << remainBit);
					}
				}
			}
		}
		sb.append(BASE32[bits]);
		return data32;
	}
	
//	public static String encryptionWithCheckBits (int uid, int score, String base32_reftable)
//	{
//		// C1234567890123D
//		// 0 - 9 mean the data
//		// C for uid Check Digit
//		// D for score Check Digit
//		String code = encryption(uid, score);
//		int checkDigitOffset1 = uid % TOTAL_VALUE_BASE32;
//		int checkDigitOffset2 = ((score + seed3) % TOTAL_VALUE_BASE32);
//		StringBuffer sb = new StringBuffer();
//		sb.append(base32_reftable.charAt(checkDigitOffset1));
//		sb.append(code);
//		sb.append(base32_reftable.charAt(checkDigitOffset2));
//		
//		return sb.toString();
//	}
//	
//	public static int[] decryptionWithCheckBits (String code, String base32_reftable)
//	{
//		// C1234567890123D
//		// 0 - 9 mean the data
//		// C for uid Check Digit
//		// D for score Check Digit
//		int[] result = null;
//		if (code.length() == TOTAL_CODE_WITH_CHECKBITS)
//		{
//			String pureCode = code.substring(1, 1 + TOTAL_CODE);
//			result = decryption(pureCode);
//			if (result.length == 2 && result[0] >= 0 && result[1] >= 0)
//			{
//				int checkDigitOffset1 = result[0] % TOTAL_VALUE_BASE32;
//				int checkDigitOffset2 = (result[1] + seed3) % TOTAL_VALUE_BASE32;
//				char checkDigit1 = code.charAt(0);
//				char checkDigit2 = code.charAt(TOTAL_CODE_WITH_CHECKBITS - 1);
//
//				if (!(checkDigit1 == base32_reftable.charAt(checkDigitOffset1) &&
//						checkDigit2 == base32_reftable.charAt(checkDigitOffset2)))
//				{
//					result = null;
//				}
//			} else 
//			{
//				result = null;
//			}
//		}
//
//		if (result == null)
//		{
//			result = new int[2];
//			result[0] = -1;
//			result[1] = -1;
//		}
//		return result;
//	}

}
