/*
 * Copyright (c) 2017 - 2019, SmartDeviceLink Consortium, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of the SmartDeviceLink Consortium, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from this 
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.smartdevicelink.proxy.rpc;

import android.support.annotation.NonNull;
import android.util.Log;

import com.smartdevicelink.marshal.JsonRPCMarshaller;
import com.smartdevicelink.protocol.enums.FunctionID;
import com.smartdevicelink.proxy.RPCNotification;
import com.smartdevicelink.proxy.RPCStruct;
import com.smartdevicelink.proxy.rpc.enums.FileType;
import com.smartdevicelink.proxy.rpc.enums.RequestType;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Hashtable;
import java.util.List;

/**
 * An asynchronous request from the system for specific data from the device or the cloud or response to a request from the device or cloud. Binary data can be included in hybrid part of message for some requests (such as Authentication request responses)
 * 
 * <p><b>Parameter List</b></p>
 * <table border="1" rules="all">
 * 		<tr>
 * 			<th>Name</th>
 * 			<th>Type</th>
 * 			<th>Description</th>
 *                 <th>Reg.</th>
 *               <th>Notes</th>
 * 			<th>Version</th>
 * 		</tr>
 * 		<tr>
 * 			<td>requestType</td>
 * 			<td>RequestType</td>
 * 			<td>The type of system request.</td>
 *                 <td>Y</td>
 *                 <td></td>
 * 			<td>SmartDeviceLink 2.3.2 </td>
 * 		</tr>
 * 		</tr>
 * 		<tr>
 * 			<td>requestSubType</td>
 * 			<td>String</td>
 * 			<td>This parameter is filled for supporting OEM proprietary data exchanges.</td>
 *                 <td>N</td>
 *                 <td>Max Length: 255</td>
 * 			<td>SmartDeviceLink 5.0</td>
 * 		</tr>
 * 		<tr>
 * 			<td>url</td>
 * 			<td>Array of Strings</td>
 * 			<td>Optional URL for HTTP requests.If blank, the binary data shall be forwarded to the app.If not blank, the binary data shall be forwarded to the url with a provided timeout in seconds.</td>
 *                 <td>N</td>
 *                 <td>maxlength: 1000; minsize:1;  maxsize: 100</td>
 * 			<td>SmartDeviceLink 2.3.2 </td>
 * 		</tr>
 * 		<tr>
 * 			<td>timeout</td>
 * 			<td>Integer</td>
 * 			<td>Optional timeout for HTTP requests;Required if a URL is provided</td>
 *                 <td>N</td>
 *                 <td>minvalue:0; maxvalue: 2000000000</td>
 * 			<td>SmartDeviceLink </td>
 * 		</tr>
 * 		<tr>
 * 			<td>fileType</td>
 * 			<td>FileType</td>
 * 			<td>Optional file type (meant for HTTP file requests).</td>
 *                 <td>N</td>
 *                 <td></td>
 * 			<td>SmartDeviceLink 2.3.2 </td>
 * 		</tr>
 * 		<tr>
 * 			<td>offset</td>
 * 			<td>Float</td>
 * 			<td>Optional offset in bytes for resuming partial data chunks</td>
 *                 <td>N</td>
 *                 <td>minvalue:0; maxvalue:100000000000</td>
 * 			<td>SmartDeviceLink 2.3.2 </td>
 * 		</tr>
 * 		<tr>
 * 			<td>length</td>
 * 			<td>Float</td>
 * 			<td>Optional length in bytes for resuming partial data chunks</td>
 *                 <td>N</td>
 *                 <td>minvalue: 0; maxvalue:100000000000</td>
 * 			<td>SmartDeviceLink 2.3.2 </td>
 * 		</tr>
 *  </table>
 * @since SmartDeviceLink 2.3.2
 */
public class OnSystemRequest extends RPCNotification {
	public static final String KEY_URL_V1 = "URL";
    public static final String KEY_URL = "url";
	public static final String KEY_TIMEOUT_V1 = "Timeout";
    public static final String KEY_TIMEOUT = "timeout";
	public static final String KEY_HEADERS = "headers";
	public static final String KEY_BODY = "body";
	public static final String KEY_FILE_TYPE = "fileType";
	public static final String KEY_REQUEST_TYPE = "requestType";
	public static final String KEY_REQUEST_SUB_TYPE = "requestSubType";
	public static final String KEY_DATA = "data";
	public static final String KEY_OFFSET = "offset";
	public static final String KEY_LENGTH = "length";
	
	private String body;
	private Headers headers;
	
	/**
	 * Constructs a new OnSystemsRequest object
	 */
    public OnSystemRequest() {
        super(FunctionID.ON_SYSTEM_REQUEST.toString());
    }

    public OnSystemRequest(Hashtable<String, Object> hash) {
        this(hash, (byte[]) hash.get(RPCStruct.KEY_BULK_DATA));
    }
    
    public OnSystemRequest(Hashtable<String, Object> hash, byte[] bulkData){
        super(hash);
        setBulkData(bulkData);
    }

    /**
	 * Constructs a new OnSystemsRequest object
     */
    public OnSystemRequest(@NonNull RequestType requestType) {
        this();
        setRequestType(requestType);
    }
    
    private void handleBulkData(byte[] bulkData){
        if(bulkData == null){
            return;
        }
        
        JSONObject httpJson;
        String tempBody = null;
        Headers tempHeaders = null;
        if(RequestType.PROPRIETARY.equals(this.getRequestType())){
        	try{
            	JSONObject bulkJson = new JSONObject(new String(bulkData));
           
            	httpJson = bulkJson.getJSONObject("HTTPRequest");
            	tempBody = getBody(httpJson);
            	tempHeaders = getHeaders(httpJson);
        	}catch(JSONException e){
            	Log.e("OnSystemRequest", "HTTPRequest in bulk data was malformed.");
            	e.printStackTrace();
        	}catch(NullPointerException e){
            	Log.e("OnSystemRequest", "Invalid HTTPRequest object in bulk data.");
            	e.printStackTrace();
        	}
        }else if(RequestType.HTTP.equals(this.getRequestType())){
        	tempHeaders = new Headers();
        	tempHeaders.setContentType("application/json");
        	tempHeaders.setConnectTimeout(7);
        	tempHeaders.setDoOutput(true);
        	tempHeaders.setDoInput(true);
        	tempHeaders.setUseCaches(false);
        	tempHeaders.setRequestMethod("POST");
        	tempHeaders.setReadTimeout(7);
        	tempHeaders.setInstanceFollowRedirects(false);
        	tempHeaders.setCharset("utf-8");
        	tempHeaders.setContentLength(bulkData.length); 
        }
        
        this.body = tempBody;
        this.headers = tempHeaders;
    }
    
    private String getBody(JSONObject httpJson){
        String result = null;
        
        try{
            result = httpJson.getString("body");
        }catch(JSONException e){
            Log.e("OnSystemRequest", "\"body\" key doesn't exist in bulk data.");
            e.printStackTrace();
        }
        
        return result;
    }
    
    private Headers getHeaders(JSONObject httpJson){
        Headers result = null;
        
        try{
            JSONObject httpHeadersJson = httpJson.getJSONObject("headers");
            Hashtable<String, Object> httpHeadersHash = JsonRPCMarshaller.deserializeJSONObject(httpHeadersJson);
            result = new Headers(httpHeadersHash);
        }catch(JSONException e){
            Log.e("OnSystemRequest", "\"headers\" key doesn't exist in bulk data.");
            e.printStackTrace();
        }
        
        return result;
    }
    
    @Deprecated
    public void setBinData(byte[] aptData) {
        setBulkData(aptData);
    }
    
    @Deprecated
    public byte[] getBinData() {
        return getBulkData();
    }
    
    @Override
    public void setBulkData(byte[] bulkData){
        super.setBulkData(bulkData);
        handleBulkData(bulkData);
    }
    
    
    @SuppressWarnings("unchecked")
    public List<String> getLegacyData() {
        return (List<String>) getObject(String.class, KEY_DATA);
    }

    public String getBody(){            	
        return this.body;
    }
    
    public void setBody(String body) {
        this.body = body;
    }
    
    public void setHeaders(Headers header) {
        this.headers = header;
    }

    public Headers getHeader() {
    	return this.headers;
    }

    public RequestType getRequestType() {
        return (RequestType) getObject(RequestType.class, KEY_REQUEST_TYPE);
    }

    public void setRequestType(@NonNull RequestType requestType) {
        setParameters(KEY_REQUEST_TYPE, requestType);
    }

    public String getRequestSubType() {
        return getString(KEY_REQUEST_SUB_TYPE);
    }

    public void setRequestSubType(String requestSubType) {
        setParameters(KEY_REQUEST_SUB_TYPE, requestSubType);
    }

    public String getUrl() {
        Object o = getParameters(KEY_URL);
        if (o == null)
        {
        	//try again for gen 1.1
        	o = getParameters(KEY_URL_V1);
        }
        if (o == null)
        	return null;
        
        if (o instanceof String) {
            return (String) o;
        }
        return null;
    }

    public void setUrl(String url) {
        setParameters(KEY_URL, url);
    }

    public FileType getFileType() {
        return (FileType) getObject(FileType.class, KEY_FILE_TYPE);
    }

    public void setFileType(FileType fileType) {
        setParameters(KEY_FILE_TYPE, fileType);
    }

    /**
     * @deprecated as of SmartDeviceLink 4.0
     * @param offset  of the data attached
     */
    public void setOffset(Integer offset) {
    	if(offset == null){
    		setOffset((Long)null);
    	}else{
    		setOffset(offset.longValue());
    	}
    }
    
    public Long getOffset() {
        final Object o = getParameters(KEY_OFFSET);
        
        if (o == null){
        	return null;
        }
        
        if (o instanceof Integer) {
            return ((Integer) o).longValue();
        }else if(o instanceof Long){
        	return (Long) o;
        }
        return null;
    }

    public void setOffset(Long offset) {
        setParameters(KEY_OFFSET, offset);
    }
    
    public Integer getTimeout() {
        Object o = getParameters(KEY_TIMEOUT);
        
        if (o == null){
        	 o = getParameters(KEY_TIMEOUT_V1);
        	 if (o == null) return null;
        }
        
        if (o instanceof Integer) {
            return (Integer) o;
        }
        return null;
    }

    public void setTimeout(Integer timeout) {
        setParameters(KEY_TIMEOUT, timeout);
    }    
    
    public Long getLength() {
        final Object o = getParameters(KEY_LENGTH);
        if (o == null){
        	return null;
        }
        		
        if (o instanceof Integer) {
            return ((Integer) o).longValue();
        }else if(o instanceof Long){
        	return (Long) o;
        }
        return null;
    }

    /**
     * @deprecated as of SmartDeviceLink 4.0
     * @param length of the data attached
     */
    public void setLength(Integer length) {
    	if(length == null){
    		setLength((Long)null);
    	}else{
    		setLength(length.longValue());
    	}
    }
    
    public void setLength(Long length) {
        setParameters(KEY_LENGTH, length);
    }
}