package zmo.technology.lite.core;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSONObject;

public class ZeroRequestGuider {

	private Connection connection;
	private HttpServletRequest request;
	private HttpServletResponse response;
	private List<String> paths;
	private Map<String, Object> stagings;

	private ZeroRequestGuider(HttpServletRequest request, HttpServletResponse response,
			List<String> paths) {
		super();
		this.request = request;
		this.response = response;
		this.paths = paths;
		this.stagings = new ConcurrentHashMap<>();
	}

	public static ZeroRequestGuider build(HttpServletRequest request, HttpServletResponse response,
			List<String> paths) {
		return new ZeroRequestGuider(request, response, paths);
	}

	public static ZeroRequestGuider build() {
		return new ZeroRequestGuider(null, null, null);
	}

	public void setConnection(Connection connection) {
		this.connection = connection;
	}

	public Connection getConnection() throws SQLException {
		return connection;
	}

	public HttpServletRequest getRequest() {
		return request;
	}

	public HttpServletResponse getResponse() {
		return response;
	}

	public List<String> getPaths() {
		return paths;
	}

	private Map<String, String> headers;

	public Map<String, String> getHeaders() {
		if (headers == null) {
			headers = new HashMap<>();
			Enumeration<String> enumeration = request.getHeaderNames();
			while (enumeration.hasMoreElements()) {
				String headerName = enumeration.nextElement();
				headers.put(headerName.toLowerCase(), request.getHeader(headerName));
			}
		}
		return headers;
	}

	private byte[] rawData;

	public byte[] getRawData() throws IOException {
		if (rawData == null) {
			ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = 0;
			while ((len = request.getInputStream().read(buffer, 0, 1024)) > 0) {
				swapStream.write(buffer, 0, len);
			}
			rawData = swapStream.toByteArray();
		}
		return rawData;
	}

	private JSONObject rawJSONObject;

	public JSONObject getRawJSONObject() throws IOException {
		if (rawJSONObject == null) {
			byte[] jsonBytes = getRawData();

			if (jsonBytes.length > 0 && jsonBytes[0] != '{')
				throw new IOException("invalid raw body");

			rawJSONObject = jsonBytes.length > 0 ? JSONObject.parseObject(new String(jsonBytes)) : new JSONObject();
		}
		return rawJSONObject;
	}

	private Map<String, String> queryParams;

	public Map<String, String> getQueryParams() throws IOException {
		if (queryParams == null) {
			queryParams = new HashMap<>();
			for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet())
				if (entry.getValue().length > 0)
					queryParams.put(entry.getKey(), entry.getValue()[0]);

			String[] params = new String(getRawData()).split("&");
			for (String param : params) {
				String[] keyValue = param.split("=");
				if (keyValue.length == 2)
					queryParams.put(keyValue[0], keyValue[1]);
			}
		}
		return queryParams;
	}

	public void responseJSONHeader() {
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
	}

	public void responseWrite(String content) throws IOException {
		if (!lockWrite)
			response.getWriter().write(content);
		lockWrite();
	}

	private boolean lockWrite = false;

	public void lockWrite() {
		if (!lockWrite)
			lockWrite = !lockWrite;
	}

	public boolean isLockWrite() {
		return lockWrite;
	}

	public void write(ZeroResponse<?> resp) throws IOException {
		responseWrite(resp.toJSONString());
	}

	public void removeStaging(String key) {
		this.stagings.remove(key);
	}

	public void addStaging(String key, Object stagingObject) {
		this.stagings.put(key, stagingObject);
	}

	public void addJSONStaging(String key, Object stagingObject) {
		this.stagings.put(key, JSONObject.toJSONString(stagingObject));
	}

	public Object fetchStaging(String key) {
		return this.stagings.get(key);
	}

	@SuppressWarnings("unchecked")
	public <T> T fetchStaging(String key, Class<T> stagingClass) {
		return (T) (this.stagings.get(key));
	}

	public <T> T fetchJSONStaging(String key, Class<T> stagingClass) {
		return JSONObject.parseObject(this.stagings.get(key).toString(), stagingClass);
	}
}
