package com.alogic.remote.call.impl;

import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.alogic.remote.Client;
import com.alogic.remote.Request;
import com.alogic.remote.Response;
import com.alogic.remote.call.Call;
import com.alogic.remote.call.CallException;
import com.alogic.remote.call.Parameters;
import com.alogic.remote.call.Result;
import com.alogic.remote.httpclient.HttpClient;
import com.alogic.remote.util.HttpQuery;
import com.anysoft.selector.FieldList;
import com.anysoft.selector.Selector;
import com.anysoft.util.DefaultProperties;
import com.anysoft.util.Factory;
import com.anysoft.util.IOTools;
import com.anysoft.util.JsonTools;
import com.anysoft.util.Properties;
import com.anysoft.util.PropertiesConstants;
import com.anysoft.util.Settings;
import com.anysoft.util.XmlElementProperties;
import com.anysoft.util.XmlTools;
import com.jayway.jsonpath.spi.JsonProvider;
import com.jayway.jsonpath.spi.JsonProviderFactory;

/**
 * Http Call
 * @author yyduan
 * @version 1.6.8.13 [duanyy 20170427] <br>
 * - 从alogic-remote中迁移过来 <br>
 * 
 * @version 1.6.8.15 [20170511 duanyy] <br>
 * - 增加绝对路径调用功能 <br>
 * 
 * @version 1.6.11.14 [duanyy 20180129] <br>
 * - 增加按相对路径调用的接口 <br>
 * 
 * @version 1.6.11.23 [20180320 duanyy] <br>
 * - 修正一些错误代码 <br>
 */
public class HttpCall implements Call {
	/**
	 * a logger of slf4j
	 */
	protected static final Logger LOG = LoggerFactory.getLogger(Call.class);	
	
	/**
	 * json provider
	 */
	protected static final JsonProvider jsonProvider = JsonProviderFactory.createProvider();
	
	/**
	 * URL中的参数
	 */
	private FieldList queryParameters = null;

	/**
	 * Http entity中的数据
	 */
	private FieldList arguments = null;
	
	/**
	 * id paths
	 */
	protected Map<String,String> idPaths = new HashMap<String,String>();
	
	/**
	 * 调用上下文
	 */
	protected Properties callContext = null;
	
	/**
	 * remote client
	 */
	protected Client client = null;
	
	/**
	 * 服务调用根路径
	 */
	protected String rootPath = "/services";
	
	@Override
	public void close()  {
	}

	@Override
	public void configure(Properties p){
		callContext = new DefaultProperties("default",Settings.get());		
		rootPath = PropertiesConstants.getString(p,"rpc.ketty.root",rootPath);
	}
	
	@Override
	public void configure(Element root, Properties props) {
		Properties p = new XmlElementProperties(root,props);
		
		//queryParameters
		Element qp = XmlTools.getFirstElementByPath(root, "request/query");
		if (qp != null){
			queryParameters = new FieldList();
			queryParameters.configure(qp, p);
		}
		
		//argument data
		Element ad = XmlTools.getFirstElementByPath(root, "request/data");
		if (ad != null){
			arguments = new FieldList();
			arguments.configure(ad, p);
		}
		
		//idpaths
		NodeList ips = XmlTools.getNodeListByPath(root, "response/data/field");
		if (ips != null)
		{
			for (int i = 0 ; i < ips.getLength() ; i ++){
				Node n = ips.item(i);
				if (n.getNodeType() != Node.ELEMENT_NODE){
					continue;
				}
				
				Element e = (Element)n;
				
				String id = e.getAttribute("id");
				String path = e.getAttribute("path");
				
				if (id == null || id.length() <= 0 || path == null || path.length() <= 0){
					continue;
				}
				
				idPaths.put(id, path);
			}
		}
		
		if (client == null){
			Factory<Client> f = new Factory<Client>();
			try {
				client = f.newInstance(root, props, "remote", HttpClient.class.getName());
			}catch (Exception ex){
				LOG.error(String.format("Can not remote client with %s", XmlTools.node2String(root)));
				client = new HttpClient();
				client.configure(root, p);
				LOG.info(String.format("Using default,Current remote client is %s",client.getClass().getName()));
			}
		}
		
		configure(p);
	}

	@Override
	public void report(Element xml) {
		if (xml != null){
			XmlTools.setString(xml, "module", getClass().getName());
		}
	}

	@Override
	public void report(Map<String, Object> json) {
		if (json != null){
			JsonTools.setString(json, "module", getClass().getName());
		}
	}

	@Override
	public Parameters createParameter() {
		return new HttpParameters();
	}

	@Override
	public Result execute(Parameters paras){
		return execute(false,this.rootPath,paras,null,null);
	}

	@Override
	public Result execute(Parameters paras, String sn,String order) {
		return execute(false,this.rootPath,paras,sn,order);
	}

	@Override
	public Result execute(String fullPath, Parameters paras)  {
		return execute(true,fullPath,paras,null,null);
	}
	
	@Override
	public Result execute(String fullPath, Parameters paras, String sn, String order) {
		return execute(true,fullPath,paras,sn,order);
	}	
	
	@Override
	public Result execute(boolean isFullPath,String path, Parameters paras){
		return execute(false,path,paras,null,null);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Result execute(boolean isFullPath,String path, Parameters paras, String sn, String order) {
		HttpQuery query = new HttpQuery(path);
		
		if (paras != null && queryParameters != null){
			Selector[] fields = queryParameters.getFields();
			if (fields != null && fields.length > 0){
				for (Selector s:fields){
					if (!s.isOk()){
						continue;
					}
					String id = s.getId();
					String value = s.select(paras);
					query.param(id, value);
				}
			}
		}
		
		Request request = client.build("post");
		
		try {
			if (StringUtils.isNotEmpty(sn) && StringUtils.isNotEmpty(order)){
				request.setHeader("GlobalSerial", sn);
				request.setHeader("GlobalSerialOrder", order);
			}
			
			Map<String,Object> docRoot = new HashMap<String,Object>();
			
			if (paras != null && arguments != null){
				Selector[] fields = arguments.getFields();
				if (fields != null && fields.length > 0){
					for (Selector s:fields){
						if (!s.isOk()){
							continue;
						}
						String id = s.getId();
						
						Object data = paras.getData(id);
						if (data != null){
							docRoot.put(id, data);
						}
					}
				}
			}
			
			request.setBody(jsonProvider.toJson(docRoot));
			
			Response response = null;
			if (isFullPath){ 
				response = request.execute(query.toString());	
			}else{
				response = request.execute(query.toString(), sn, callContext);
			}
					
			if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
				throw new CallException("core.e1004",
						"Error occurs when invoking service :"
								+ response.getReasonPhrase());
			}
			
			String content = response.asString();	
			Object objectDoc = jsonProvider.parse(content);
			Map<String,Object> doc = null;
			if (objectDoc instanceof Map){
				doc = (Map<String, Object>) jsonProvider.parse(content);
			}else{
				doc = new HashMap<String,Object>();
				doc.put("data", objectDoc);
			}
			return new HttpResult(doc,idPaths);
		}catch (Exception ex){
			throw new CallException("core.e1004","Can not read result from server.", ex);
		}finally{
			IOTools.close(request);
		}
	}
}