package cn.freemud.utils;

import org.bouncycastle.util.encoders.UrlBase64;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class SignedUtils {

	private static final String HMAC_SHA256 = "HmacSHA256";
	private static final String KEY = "234324";

	private static SignedUtils instance;

	private Mac mac;

	public static SignedUtils getInstance() {
		if (instance == null) {
			try {
				instance = new SignedUtils();
			} catch (Exception e) {
			}
		}
		return instance;
	}

	private SignedUtils() throws Exception {
		byte[] secretyKeyBytes = KEY.getBytes("UTF-8");
		SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256);
		mac = Mac.getInstance(HMAC_SHA256);
		mac.init(secretKeySpec);
	}

	public String sign(Map<String, String> params) {
		SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(params);
		String paramStr = canonicalize(sortedParamMap);
		String hmac = hmac(paramStr);
		return percentEncodeRfc3986(hmac);
	}

	private String hmac(String stringToSign) {
		String signature = null;
		byte[] data;
		byte[] rawHmac;
		try {
			data = stringToSign.getBytes("UTF-8");
			rawHmac = mac.doFinal(data);
			signature = new String(UrlBase64.encode(rawHmac));
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("UTF-8" + " is unsupported!", e);
		}
		return signature;
	}

	private String canonicalize(SortedMap<String, String> sortedParamMap) {
		if (sortedParamMap.isEmpty()) {
			return "";
		}
		StringBuffer buffer = new StringBuffer();
		Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry<String, String> kvpair = iter.next();
			buffer.append(percentEncodeRfc3986(kvpair.getKey()));
			buffer.append("=");
			buffer.append(percentEncodeRfc3986(kvpair.getValue()));
			if (iter.hasNext()) {
				buffer.append("&");
			}
		}
		return buffer.toString();
	}

	private String percentEncodeRfc3986(String s) {
		String out;
		try {
			out = URLEncoder.encode(s, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
		} catch (UnsupportedEncodingException e) {
			out = s;
		}
		return out;
	}
}