package com.akdeniz.googleplaycrawler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.crypto.Cipher;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;

import com.akdeniz.googleplaycrawler.GooglePlay.AndroidBuildProto;
import com.akdeniz.googleplaycrawler.GooglePlay.AndroidCheckinProto;
import com.akdeniz.googleplaycrawler.GooglePlay.AndroidCheckinRequest;
import com.akdeniz.googleplaycrawler.GooglePlay.DeviceConfigurationProto;
import com.akdeniz.googleplaycrawler.misc.Base64;
import com.akdeniz.googleplaycrawler.misc.DummyX509TrustManager;

/**
 *
 * @author akdeniz
 *
 */
public class Utils {

    private static final String GOOGLE_PUBLIC_KEY = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3"
	    + "iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pKRI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0Q"
	    + "RNVz34kMJR3P/LgHax/6rmf5AAAAAwEAAQ==";

    /**
     * Parses key-value response into map.
     */
    public static Map<String, String> parseResponse(String response) {

	Map<String, String> keyValueMap = new HashMap<String, String>();
	StringTokenizer st = new StringTokenizer(response, "\n\r");

	while (st.hasMoreTokens()) {
	    String[] keyValue = st.nextToken().split("=");
	    keyValueMap.put(keyValue[0], keyValue[1]);
	}

	return keyValueMap;
    }

    private static PublicKey createKey(byte[] keyByteArray) throws Exception {

	int modulusLength = readInt(keyByteArray, 0);
	byte[] modulusByteArray = new byte[modulusLength];
	System.arraycopy(keyByteArray, 4, modulusByteArray, 0, modulusLength);
	BigInteger modulus = new BigInteger(1, modulusByteArray);

	int exponentLength = readInt(keyByteArray, modulusLength + 4);
	byte[] exponentByteArray = new byte[exponentLength];
	System.arraycopy(keyByteArray, modulusLength + 8, exponentByteArray, 0, exponentLength);
	BigInteger publicExponent = new BigInteger(1, exponentByteArray);

	return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
    }

    /**
     * Encrypts given string with Google Public Key.
     *
     */
    public static String encryptString(String str2Encrypt) throws Exception {

	byte[] keyByteArray = Base64.decode(GOOGLE_PUBLIC_KEY, Base64.DEFAULT);

	byte[] header = new byte[5];
	byte[] digest = MessageDigest.getInstance("SHA-1").digest(keyByteArray);
	header[0] = 0;
	System.arraycopy(digest, 0, header, 1, 4);

	PublicKey publicKey = createKey(keyByteArray);

	Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
	byte[] bytes2Encrypt = str2Encrypt.getBytes("UTF-8");
	int len = ((bytes2Encrypt.length - 1) / 86) + 1;
	byte[] cryptedBytes = new byte[len * 133];

	for (int j = 0; j < len; j++) {
	    cipher.init(1, publicKey);
	    byte[] arrayOfByte4 = cipher.doFinal(bytes2Encrypt, j * 86, (bytes2Encrypt.length - j * 86));
	    System.arraycopy(header, 0, cryptedBytes, j * 133, header.length);
	    System.arraycopy(arrayOfByte4, 0, cryptedBytes, j * 133 + header.length, arrayOfByte4.length);
	}
	return Base64.encodeToString(cryptedBytes, 10);
    }

    private static int readInt(byte[] data, int offset) {
	return (0xFF & data[offset]) << 24 | (0xFF & data[(offset + 1)]) << 16 | (0xFF & data[(offset + 2)]) << 8
		| (0xFF & data[(offset + 3)]);
    }

    /**
     * Reads all contents of the input stream.
     *
     */
    public static byte[] readAll(InputStream inputStream) throws IOException {

	ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
	byte[] buffer = new byte[1024];

	int k = 0;
	for (; (k = inputStream.read(buffer)) != -1;) {
	    outputStream.write(buffer, 0, k);
	}

	return outputStream.toByteArray();
    }

    public static String bytesToHex(byte[] bytes) {
	final char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	char[] hexChars = new char[bytes.length * 2];
	int v;
	for (int j = 0; j < bytes.length; j++) {
	    v = bytes[j] & 0xFF;
	    hexChars[j * 2] = hexArray[v >>> 4];
	    hexChars[j * 2 + 1] = hexArray[v & 0x0F];
	}
	return new String(hexChars);
    }

    public static byte[] hexToBytes(String s) {
	int len = s.length();
	byte[] data = new byte[len / 2];
	for (int i = 0; i < len; i += 2) {
	    data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
	}
	return data;
    }

    public static Scheme getMockedScheme() throws NoSuchAlgorithmException, KeyManagementException {
	SSLContext sslcontext = SSLContext.getInstance("TLS");

	sslcontext.init(null, new TrustManager[] { new DummyX509TrustManager() }, null);
	SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
	Scheme https = new Scheme("https", 443, sf);

	return https;
    }

    /**
     * Generates android checkin request with properties of "Galaxy S3".
     *
     * <a href=
     * "http://www.glbenchmark.com/phonedetails.jsp?benchmark=glpro25&D=Samsung+GT-I9300+Galaxy+S+III&testgroup=system"
     * > http://www.glbenchmark.com/phonedetails.jsp?benchmark=glpro25&D=Samsung
     * +GT-I9300+Galaxy+S+III&testgroup=system </a>
     */
    public static AndroidCheckinRequest generateAndroidCheckinRequest() {

	return AndroidCheckinRequest
		.newBuilder()
		.setId(0)
		.setCheckin(
			AndroidCheckinProto
				.newBuilder()
				.setBuild(
					AndroidBuildProto.newBuilder()
						.setId("google/angler/angler:6.0.1/MTC19T/2741993:user/release-keys")
						.setProduct("angler").setCarrier("Google").setRadio("angler-03.61")
						.setBootloader("angler-03.51").setClient("android-google")
						.setTimestamp(new Date().getTime() / 1000).setGoogleServices(16).setDevice("angler")
						.setSdkVersion(23).setModel("angler").setManufacturer("Huawei")
						.setBuildProduct("angler").setOtaInstalled(false)).setLastCheckinMsec(0)
				.setCellOperator("310260").setSimOperator("310260").setRoaming("mobile-notroaming")
				.setUserNumber(0)).setLocale("en_US").setTimeZone("Europe/Amsterdam").setVersion(3)
		.setDeviceConfiguration(getDeviceConfigurationProto()).setFragment(0).build();
    }

    public static DeviceConfigurationProto getDeviceConfigurationProto() {
	return DeviceConfigurationProto
		.newBuilder()
		.setTouchScreen(3)
		.setKeyboard(1)
		.setNavigation(1)
		.setScreenLayout(2)
		.setHasHardKeyboard(false)
		.setHasFiveWayNavigation(false)
		.setScreenDensity(560)
		.setGlEsVersion(196609)
		.addAllSystemSharedLibrary(
			Arrays.asList("android.test.runner", "com.android.future.usb.accessory", "com.android.location.provider",
				"com.android.media.remotedisplay", "com.android.mediadrm.signer", "com.android.nfc_extras", "com.google.android.camera.experimental2015",
				"com.google.android.dialer.support", "com.google.android.maps", "com.google.android.media.effects",
				"com.google.widevine.software.drm", "javax.obex"))
		.addAllSystemAvailableFeature(
			Arrays.asList("android.hardware.audio.low_latency", "android.hardware.audio.output", "android.hardware.audio.pro", "android.hardware.microphone",
				"android.hardware.output", "android.hardware.bluetooth", "android.hardware.bluetooth_le", "android.hardware.camera",
				"android.hardware.camera.any", "android.hardware.camera.autofocus", "android.hardware.camera.flash", "android.hardware.camera.front",
				"android.hardware.camera.level.full", "android.hardware.camera.capability.manual_sensor", "android.hardware.camera.capability.manual_post_processing", "android.hardware.camera.capability.raw",
				"android.hardware.consumerir", "android.hardware.ethernet", "android.hardware.fingerprint", "android.hardware.location",
				"android.hardware.location.network", "android.hardware.location.gps", "android.hardware.microphone", "android.hardware.nfc",
				"android.hardware.nfc.hce", "android.hardware.sensor.accelerometer", "android.hardware.sensor.barometer", "android.hardware.sensor.compass",
				"android.hardware.sensor.gyroscope", "android.hardware.sensor.hifi_sensors", "android.hardware.sensor.light", "android.hardware.sensor.proximity",
				"android.hardware.sensor.stepcounter", "android.hardware.sensor.stepdetector", "android.hardware.screen.landscape", "android.hardware.screen.portrait",
				"android.hardware.telephony", "android.hardware.telephony.cdma", "android.hardware.telephony.gsm", "android.hardware.faketouch",
				"android.hardware.touchscreen", "android.hardware.touchscreen.multitouch", "android.hardware.touchscreen.multitouch.distinct", "android.hardware.touchscreen.multitouch.jazzhand",
				"android.hardware.usb.host", "android.hardware.usb.accessory", "android.hardware.wifi", "android.hardware.wifi.direct",
				"android.software.app_widgets", "android.software.backup", "android.software.connectionservice", "android.software.device_admin",
				"android.software.home_screen", "android.software.input_methods", "android.software.live_wallpaper", "android.software.managed_users",
				"android.software.midi", "android.software.print", "android.software.sip", "android.software.sip.voip",
				"android.software.verified_boot", "android.software.voice_recognizers", "android.software.webview", "com.google.android.feature.GOOGLE_BUILD",
				"com.google.android.feature.GOOGLE_EXPERIENCE", "com.google.android.feature.EXCHANGE_6_2", "com.nxp.mifare"))
		.addAllNativePlatform(Arrays.asList("x86_64", "x86", "arm64-v8a", "armeabi-v7a", "armeabi"))
		.setScreenWidth(1440)
		.setScreenHeight(2560)
		.addAllSystemSupportedLocale(
			Arrays.asList("af", "af_ZA", "am", "am_ET", "ar", "ar_EG", "bg", "bg_BG", "ca", "ca_ES", "cs", "cs_CZ",
				"da", "da_DK", "de", "de_AT", "de_CH", "de_DE", "de_LI", "el", "el_GR", "en", "en_AU", "en_CA",
				"en_GB", "en_NZ", "en_SG", "en_US", "es", "es_ES", "es_US", "fa", "fa_IR", "fi", "fi_FI", "fr",
				"fr_BE", "fr_CA", "fr_CH", "fr_FR", "hi", "hi_IN", "hr", "hr_HR", "hu", "hu_HU", "in", "in_ID",
				"it", "it_CH", "it_IT", "iw", "iw_IL", "ja", "ja_JP", "ko", "ko_KR", "lt", "lt_LT", "lv",
				"lv_LV", "ms", "ms_MY", "nb", "nb_NO", "nl", "nl_BE", "nl_NL", "pl", "pl_PL", "pt", "pt_BR",
				"pt_PT", "rm", "rm_CH", "ro", "ro_RO", "ru", "ru_RU", "sk", "sk_SK", "sl", "sl_SI", "sr",
				"sr_RS", "sv", "sv_SE", "sw", "sw_TZ", "th", "th_TH", "tl", "tl_PH", "tr", "tr_TR", "ug",
				"ug_CN", "uk", "uk_UA", "vi", "vi_VN", "zh_CN", "zh_TW", "zu", "zu_ZA"))
		.addAllGlExtension(
			Arrays.asList("GL_EXT_debug_marker", "GL_EXT_discard_framebuffer", "GL_EXT_multi_draw_arrays",
				"GL_EXT_shader_texture_lod", "GL_EXT_texture_format_BGRA8888",
				"GL_IMG_multisampled_render_to_texture", "GL_IMG_program_binary", "GL_IMG_read_format",
				"GL_IMG_shader_binary", "GL_IMG_texture_compression_pvrtc", "GL_IMG_texture_format_BGRA8888",
				"GL_IMG_texture_npot", "GL_IMG_vertex_array_object", "GL_OES_EGL_image",
				"GL_OES_EGL_image_external", "GL_OES_blend_equation_separate", "GL_OES_blend_func_separate",
				"GL_OES_blend_subtract", "GL_OES_byte_coordinates", "GL_OES_compressed_ETC1_RGB8_texture",
				"GL_OES_compressed_paletted_texture", "GL_OES_depth24", "GL_OES_depth_texture",
				"GL_OES_draw_texture", "GL_OES_egl_sync", "GL_OES_element_index_uint",
				"GL_OES_extended_matrix_palette", "GL_OES_fixed_point", "GL_OES_fragment_precision_high",
				"GL_OES_framebuffer_object", "GL_OES_get_program_binary", "GL_OES_mapbuffer",
				"GL_OES_matrix_get", "GL_OES_matrix_palette", "GL_OES_packed_depth_stencil",
				"GL_OES_point_size_array", "GL_OES_point_sprite", "GL_OES_query_matrix", "GL_OES_read_format",
				"GL_OES_required_internalformat", "GL_OES_rgb8_rgba8", "GL_OES_single_precision",
				"GL_OES_standard_derivatives", "GL_OES_stencil8", "GL_OES_stencil_wrap",
				"GL_OES_texture_cube_map", "GL_OES_texture_env_crossbar", "GL_OES_texture_float",
				"GL_OES_texture_half_float", "GL_OES_texture_mirrored_repeat", "GL_OES_vertex_array_object",
				"GL_OES_vertex_half_float")).build();
    }
}