/*******************************************************************************************
 *	Copyright (c) 2016, zzg.zhou(11039850@qq.com)
 * 
 *  Monalisa is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU Lesser General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.

 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU Lesser General Public License for more details.

 *	You should have received a copy of the GNU Lesser General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************************/
package com.tsc9526.monalisa.tools.json;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.stream.JsonWriter;
import com.tsc9526.monalisa.orm.model.Model;
import com.tsc9526.monalisa.tools.clazz.MelpClass;
import com.tsc9526.monalisa.tools.clazz.MelpClass.ClassHelper;
import com.tsc9526.monalisa.tools.clazz.MelpClass.FGS;
import com.tsc9526.monalisa.tools.clazz.MelpLib;
import com.tsc9526.monalisa.tools.converters.Conversion;
import com.tsc9526.monalisa.tools.datatable.DataColumn;
import com.tsc9526.monalisa.tools.datatable.DataMap;
import com.tsc9526.monalisa.tools.datatable.DataTable;
import com.tsc9526.monalisa.tools.datatable.Page;

/**
 * 
 * @author zzg.zhou(11039850@qq.com)
 */
public class MelpJson {
	static{
		MelpLib.tryLoadGson();
	}
	
	private MelpJson(){}
	  
	public static GsonBuilder createGsonBuilder(){
		return new GsonBuilder()
			.registerTypeAdapter(Double.class,  new JsonSerializer<Double>() {   
			    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
			        if(src == src.longValue()){
			            return new JsonPrimitive(src.longValue());      
			        }
			        return new JsonPrimitive(src);
			    }
			})
			.setExclusionStrategies(new ExclusionStrategy() {
				public boolean shouldSkipField(FieldAttributes fa) {
					return fa.getName().startsWith("$");
				}
	
				public boolean shouldSkipClass(Class<?> clazz) {
					return false;
				}
			})
			.setDateFormat(Conversion.DEFAULT_DATETIME_FORMAT);
	}
	
	private static Gson gsonNormal =createGsonBuilder().create();
	private static Gson gsonPretty =createGsonBuilder().setPrettyPrinting().create();
	
	public static Gson getGson(){	
		return gsonNormal;   
	}
	
	/**
	 * Transform object to json string(no pretty print).
	 * 
	 * @param bean : The object transform to json string.
	 * @return json string(no pretty print). 
	 * 
	 * @see #toJsonPretty(Object)
	 */
	public static String toJson(Object bean) {
		return toJson(getGson(),bean);
	}
	
	/**
	 * Transform object to json string (pretty print).
	 * 
	 * @param bean : The object transform to json string.
	 * @return json string(pretty print). 
	 * 
	 * @see #toJson(Object)
	 */
	public static String toJsonPretty(Object bean) {
		return toJson(gsonPretty,bean); 
	}
	
	public static String toJson(Gson gson,Object bean) {
		if(bean==null) {
			return null;
		}
		
		if(bean instanceof Model<?>) {
			JsonObject json=new JsonObject(); 
			
			ClassHelper mc=MelpClass.getClassHelper(bean);
			for (FGS fgs : mc.getFields()) {
				String name=fgs.getFieldName();
				 
				Object v = fgs.getObject(bean);
				if (v != null) {
					JsonElement e=null;
					if(v instanceof JsonElement){
						e=(JsonElement)v;
					}else{
						e=gson.toJsonTree(v);					 
					}				
					json.add(name, e);
				}			 
			}
			return gson.toJson(json);
		}else {
			return gson.toJson(bean);
		}
	}
	
	public static String getString(JsonObject json,String name){
		return getString(json, name,null);
	}
	
	public static String getString(JsonObject json,String name,String defaultValue){
		JsonElement e=json.get(name);
		if(e==null || e.isJsonNull()){
			return defaultValue;
		}else{
			return e.getAsString();
		}
	}
	
	public static int getInt(JsonObject json,String name,int defaultValue){
		JsonElement e=json.get(name);
		if(e==null || e.isJsonNull()){
			return defaultValue;
		}else{
			return e.getAsInt();
		}
	}
	
	public static double getDouble(JsonObject json,String name,double defaultValue){
		JsonElement e=json.get(name);
		if(e==null || e.isJsonNull()){
			return defaultValue;
		}else{
			return e.getAsDouble();
		}
	}
	
	public static DataTable<DataMap> parseToDataTable(JsonArray array){
		DataTable<DataMap> table=new DataTable<DataMap>();
		
		for(int i=0;i<array.size();i++){
			DataMap data=parseToDataMap(array.get(i).getAsJsonObject());
			
			table.add(data);		
		}
		return table;
	}	
	
	public static Page<DataMap> parseToPage(JsonObject json){
		DataTable<DataMap> rows=parseToDataTable(json.get("rows").getAsJsonArray());
		
		long records=json.get("records").getAsLong();
		long size   =json.get("size").getAsLong();
		long page   =json.get("page").getAsLong();
	 
		long offset=(page-1)*size;
		
		return new Page<DataMap>(rows,records,size,offset); 
	}	
	
	public static DataMap parseToDataMap(String json){
		JsonElement je = parseJson(json);
		
		if(je.isJsonObject()){
			return parseToDataMap(je.getAsJsonObject());
		}else if(je.isJsonArray()){
			DataMap ret=new DataMap();
			
			JsonArray array=(JsonArray)je;
    		for(int i=0;i<array.size();i++){
    			JsonElement e=array.get(i);
    			
    			Object v=toObject(e);
    			
    			ret.put(String.valueOf(i),v);
    		}
    		
			return ret;
		}else{
			return null;
		}
	}
	
	public static JsonObject parseJsonAsObject(String json) {
		JsonElement je=parseJson(json);
		if(je!=null) {
			return je.getAsJsonObject();
		}
		
		return null;
	}
	
	public static JsonArray parseJsonAsArray(String json) {
		JsonElement je=parseJson(json);
		if(je!=null) {
			return je.getAsJsonArray();
		}
		
		return null;
	}
	
	public static JsonElement parseJson(String json) {
		if(json==null || json.length()<1){
			throw new RuntimeException("Invalid json string: "+json);
		}
		   
		try{
			return new JsonParser().parse(json);
		}catch(Exception e){
			throw new RuntimeException("Invalid json string: \r\n"+json,e);
		}
	}
 	
	public static DataMap parseToDataMap(JsonObject jsonObject) throws JsonParseException {
		DataMap map = new DataMap();
       
        Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
        for (Map.Entry<String, JsonElement> entry : entrySet) {
        	JsonElement e=entry.getValue();
        	Object v=toObject(e);
        	map.put(entry.getKey(),v);
        }
        return map;
    }
	
	private static Object toObject(JsonElement e){
		if(e==null || e.isJsonNull()){
			return null;
		}else if(e.isJsonPrimitive()){
			return primitive((JsonPrimitive)e);
		}else if(e.isJsonObject()){
			return parseToDataMap(e.getAsJsonObject());
		}else if(e.isJsonArray()){
			List<Object> list=new ArrayList<Object>();
			
			JsonArray array=(JsonArray)e;
    		for(int i=0;i<array.size();i++){
    			JsonElement je=array.get(i);
    			
    			Object v=toObject(je);
    			
    			list.add(v);
    		}
    		return list;
		}else{
			return e;
		}   
	}
	
	public static Object primitive(JsonPrimitive e){
		if(e.isNumber()){
			Number n=e.getAsNumber();
			if(n instanceof LazilyParsedNumber){
				LazilyParsedNumber ln=(LazilyParsedNumber)n;
				String value=ln.toString();
				if(value.indexOf('.')>=0){
					return ln.doubleValue();
				}else{
					return ln.longValue();
				}
			}else{
				return n;
			}
		}else if(e.isBoolean()){
			return e.getAsBoolean();
		}else{
			return e.getAsString();
		}
	}
	
	public static void writeJson(JsonWriter w,DataTable<?> table,boolean close){
		try{
			List<DataColumn> headers=table.getHeaders();
		 	 
			w.beginObject();
			w.name("header");
			
			w.beginArray();
			for(DataColumn c:headers){
				w.value(c.getName());
			}
			w.endArray();
			
			w.name("data");
			w.beginArray();
			for(Object v:table){
				w.beginArray();
				writeValue(w,headers,v);
				w.endArray();
			}
			w.endArray();
			
			w.endObject();
			
			if(close){
				w.close();
			}
		}catch(IOException e){
			throw new RuntimeException(e);
		}
	}
	
	private static void writeValue(JsonWriter w,List<DataColumn> headers,Object v)throws IOException{
		if(v!=null){
			if(v instanceof Map){
				for(DataColumn c:headers){
					Object o=((Map<?,?>)v).get(c.getName());
					doWriteValue(w,o);
				} 
			}else{
				writeValueNoMap(w,headers,v);
			}
		}
	}
	
	private static void writeValueNoMap(JsonWriter w,List<DataColumn> headers,Object v)throws IOException{
		if(v.getClass().isPrimitive() || v.getClass().getName().startsWith("java.")){
			doWriteValue(w,v);
		}else if(v.getClass().isArray()){
			Object[] xs=(Object[])v;
			for(int k=0;k<xs.length;k++){
				doWriteValue(w,xs[k]);
			}
		}else{	
			ClassHelper mc=MelpClass.getClassHelper(v.getClass());
			for(DataColumn c:headers){
				FGS fgs=mc.getField(c.getName());
				Object o=null;
				if(fgs!=null){
					o=fgs.getObject(v);
				}
				 
				doWriteValue(w,o);
			} 
		}
	}
	
	private static void doWriteValue(JsonWriter w,Object obj)throws IOException{
		if(obj instanceof Number){
			w.value((Number)obj);
		}else{
			w.value(obj==null?null:obj.toString());
		}
	}
 	 

}
