package com.lvrenyang.qrcode;

import java.io.UnsupportedEncodingException;

/**
 * QRUtil
 * @author Kazuhiko Arase 
 */
class QRUtil {

	private QRUtil() {
	}

	public static String getJISEncoding() {
        return "SJIS";
	}
	
	public static int[] getPatternPosition(int typeNumber) {
		return PATTERN_POSITION_TABLE[typeNumber - 1];
	}
	
	private static final int[][] PATTERN_POSITION_TABLE ={
		{},
		{6, 18},
		{6, 22},
		{6, 26},
		{6, 30},
		{6, 34},
		{6, 22, 38},
		{6, 24, 42},
		{6, 26, 46},
		{6, 28, 50},
		{6, 30, 54},		
		{6, 32, 58},
		{6, 34, 62},
		{6, 26, 46, 66},
		{6, 26, 48, 70},
		{6, 26, 50, 74},
		{6, 30, 54, 78},
		{6, 30, 56, 82},
		{6, 30, 58, 86},
		{6, 34, 62, 90},
		{6, 28, 50, 72, 94},
		{6, 26, 50, 74, 98},
		{6, 30, 54, 78, 102},
		{6, 28, 54, 80, 106},
		{6, 32, 58, 84, 110},
		{6, 30, 58, 86, 114},
		{6, 34, 62, 90, 118},
		{6, 26, 50, 74, 98, 122},
		{6, 30, 54, 78, 102, 126},
		{6, 26, 52, 78, 104, 130},
		{6, 30, 56, 82, 108, 134},
		{6, 34, 60, 86, 112, 138},
		{6, 30, 58, 86, 114, 142},
		{6, 34, 62, 90, 118, 146},
		{6, 30, 54, 78, 102, 126, 150},
		{6, 24, 50, 76, 102, 128, 154},
		{6, 28, 54, 80, 106, 132, 158},
		{6, 32, 58, 84, 110, 136, 162},
		{6, 26, 54, 82, 110, 138, 166},
		{6, 30, 58, 86, 114, 142, 170}
	};
    
    private static int[][][] MAX_LENGTH = {
        { {41,  25,  17,  10},  {34,  20,  14,  8},   {27,  16,  11,  7},  {17,  10,  7,   4} },
        { {77,  47,  32,  20},  {63,  38,  26,  16},  {48,  29,  20,  12}, {34,  20,  14,  8} },
        { {127, 77,  53,  32},  {101, 61,  42,  26},  {77,  47,  32,  20}, {58,  35,  24,  15} },
        { {187, 114, 78,  48},  {149, 90,  62,  38},  {111, 67,  46,  28}, {82,  50,  34,  21} },
        { {255, 154, 106, 65},  {202, 122, 84,  52},  {144, 87,  60,  37}, {106, 64,  44,  27} },
        { {322, 195, 134, 82},  {255, 154, 106, 65},  {178, 108, 74,  45}, {139, 84,  58,  36} },
        { {370, 224, 154, 95},  {293, 178, 122, 75},  {207, 125, 86,  53}, {154, 93,  64,  39} },
        { {461, 279, 192, 118}, {365, 221, 152, 93},  {259, 157, 108, 66}, {202, 122, 84,  52} },
        { {552, 335, 230, 141}, {432, 262, 180, 111}, {312, 189, 130, 80}, {235, 143, 98,  60} },
        { {652, 395, 271, 167}, {513, 311, 213, 131}, {364, 221, 151, 93}, {288, 174, 119, 74} }
    };

    public static int getMaxLength(int typeNumber, int mode, int errorCorrectLevel) {

        int t = typeNumber - 1;
        int e = 0;
        int m = 0;

        switch(errorCorrectLevel) {
        case ErrorCorrectLevel.L : e = 0; break;
        case ErrorCorrectLevel.M : e = 1; break;
        case ErrorCorrectLevel.Q : e = 2; break;
        case ErrorCorrectLevel.H : e = 3; break;
        default :
            throw new IllegalArgumentException("e:" + errorCorrectLevel);
        }

        switch(mode) {
        case Mode.MODE_NUMBER    : m = 0; break;
        case Mode.MODE_ALPHA_NUM : m = 1; break;
        case Mode.MODE_8BIT_BYTE : m = 2; break;
        case Mode.MODE_KANJI     : m = 3; break;
        default :
            throw new IllegalArgumentException("m:" + mode);
        }

        return MAX_LENGTH[t][e][m];
    }


	/**
	 * エラー訂正多項式を取得する。
	 */
	public static Polynomial getErrorCorrectPolynomial(int errorCorrectLength) {
			
		Polynomial a = new Polynomial(new int[]{1});

		for (int i = 0; i < errorCorrectLength; i++) {
			a = a.multiply(new Polynomial(new int[]{1, QRMath.gexp(i) }) );
		}

		return a;
	}
		
	/**
	 * 指定されたパターンのマスクを取得する。
	 */
	public static boolean getMask(int maskPattern, int i, int j) {
		
		switch (maskPattern) {
			
		case MaskPattern.PATTERN000 : return (i + j) % 2 == 0;
		case MaskPattern.PATTERN001 : return i % 2 == 0;
		case MaskPattern.PATTERN010 : return j % 3 == 0;
		case MaskPattern.PATTERN011 : return (i + j) % 3 == 0;
		case MaskPattern.PATTERN100 : return (i / 2 + j / 3) % 2 == 0;
		case MaskPattern.PATTERN101 : return (i * j) % 2 + (i * j) % 3 == 0;
		case MaskPattern.PATTERN110 : return ( (i * j) % 2 + (i * j) % 3) % 2 == 0;
		case MaskPattern.PATTERN111 : return ( (i * j) % 3 + (i + j) % 2) % 2 == 0;

		default :
			throw new IllegalArgumentException("mask:" + maskPattern);
		}
	}	

	/**
	 * 失点を取得する
	 */
	public static int getLostPoint(QRCode qrCode) {
		
		int moduleCount = qrCode.getModuleCount();
		
		int lostPoint = 0;
		

		// LEVEL1
		
		for (int row = 0; row < moduleCount; row++) {

			for (int col = 0; col < moduleCount; col++) {

				int sameCount = 0;
				boolean dark = qrCode.isDark(row, col);
				
				for (int r = -1; r <= 1; r++) {

					if (row + r < 0 || moduleCount <= row + r) {
						continue;
					}

					for (int c = -1; c <= 1; c++) {

						if (col + c < 0 || moduleCount <= col + c) {
							continue;
						}

						if (r == 0 && c == 0) {
							continue;
						}

						if (dark == qrCode.isDark(row + r, col + c) ) {
							sameCount++;
						}
					}
				}

				if (sameCount > 5) {
					lostPoint += (3 + sameCount - 5);
				}
			}
		}

		// LEVEL2

		for (int row = 0; row < moduleCount - 1; row++) {
			for (int col = 0; col < moduleCount - 1; col++) {
				int count = 0;
				if (qrCode.isDark(row,     col    ) ) count++;
				if (qrCode.isDark(row + 1, col    ) ) count++;
				if (qrCode.isDark(row,     col + 1) ) count++;
				if (qrCode.isDark(row + 1, col + 1) ) count++;
				if (count == 0 || count == 4) {
					lostPoint += 3;
				}
			}
		}

		// LEVEL3

		for (int row = 0; row < moduleCount; row++) {
			for (int col = 0; col < moduleCount - 6; col++) {
				if (qrCode.isDark(row, col)
						&& !qrCode.isDark(row, col + 1)
						&&  qrCode.isDark(row, col + 2)
						&&  qrCode.isDark(row, col + 3)
						&&  qrCode.isDark(row, col + 4)
						&& !qrCode.isDark(row, col + 5)
						&&  qrCode.isDark(row, col + 6) ) {
					lostPoint += 40;
				}
			}
		}

		for (int col = 0; col < moduleCount; col++) {
			for (int row = 0; row < moduleCount - 6; row++) {
				if (qrCode.isDark(row, col)
						&& !qrCode.isDark(row + 1, col)
						&&  qrCode.isDark(row + 2, col)
						&&  qrCode.isDark(row + 3, col)
						&&  qrCode.isDark(row + 4, col)
						&& !qrCode.isDark(row + 5, col)
						&&  qrCode.isDark(row + 6, col) ) {
					lostPoint += 40;
				}
			}
		}

		// LEVEL4
		
		int darkCount = 0;

		for (int col = 0; col < moduleCount; col++) {
			for (int row = 0; row < moduleCount; row++) {
				if (qrCode.isDark(row, col) ) {
					darkCount++;
				}
			}
		}
		
		int ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
		lostPoint += ratio * 10;
		
		return lostPoint;		
	}

	public static int getMode(String s) {
		if (isAlphaNum(s) ) {
			if (isNumber(s) ) {
				return Mode.MODE_NUMBER;
			}
			return Mode.MODE_ALPHA_NUM;
		} else if (isKanji(s) ) {
			return Mode.MODE_KANJI;
		} else {
			return Mode.MODE_8BIT_BYTE;
		}
	}
		
	private static boolean isNumber(String s) {
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			if (!('0' <= c && c <= '9') ) {
				return false;
			}
		}
		return true;
	}

	private static boolean isAlphaNum(String s) {
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			if (!('0' <= c && c <= '9') && !('A' <= c && c <= 'Z') && " $%*+-./:".indexOf(c) == -1) {
				return false;
			}
		}
		return true;
	}

	private static boolean isKanji(String s) {

		try {

			byte[] data = s.getBytes(QRUtil.getJISEncoding() );

			int i = 0;

			while (i + 1 < data.length) {
				
				int c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]);

				if (!(0x8140 <= c && c <= 0x9FFC) && !(0xE040 <= c && c <= 0xEBBF) ) {
					return false;
				}
				
				i += 2;
			}

			if (i < data.length) {
				return false;
			}
			
			return true;

		} catch(UnsupportedEncodingException e) {
			throw new RuntimeException(e.getMessage() );
		}
	}
	
	private static final int G15 = (1 << 10) | (1 << 8) | (1 << 5)
		| (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);

	private static final int G18 = (1 << 12) | (1 << 11) | (1 << 10)
		| (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);

	private static final int G15_MASK = (1 << 14) | (1 << 12) | (1 << 10)
		| (1 << 4) | (1 << 1);

	public static int getBCHTypeInfo(int data) {
		int d = data << 10;
		while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
			d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) ); 	
		}
		return ( (data << 10) | d) ^ G15_MASK;
	}

	public static int getBCHTypeNumber(int data) {
		int d = data << 12;
		while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
			d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) ); 	
		}
		return (data << 12) | d;
	}
	
	private static int getBCHDigit(int data) {

		int digit = 0;

		while (data != 0) {
			digit++;
			data >>>= 1;
		}

		return digit;

	}
}
