/*******************************************************************************
 * Copyright 2013-2016 alladin-IT GmbH
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package at.alladin.rmbt.client.helper;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;

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

import at.alladin.rmbt.client.Ping;
import at.alladin.rmbt.client.RMBTTestParameter;
import at.alladin.rmbt.client.SpeedItem;
import at.alladin.rmbt.client.TotalTestResult;
import at.alladin.rmbt.client.ndt.UiServicesAdapter;
import at.alladin.rmbt.client.v2.task.TaskDesc;
import at.alladin.rmbt.client.v2.task.service.TestMeasurement;
import at.alladin.rmbt.client.v2.task.service.TestMeasurement.TrafficDirection;
import at.alladin.rmbt.util.model.shared.exception.ErrorStatus;

public class ControlServerConnection
{
    
    // url to make request
    private URL hostUrl;
    
    private boolean testEncryption;
    
    private String testToken = "";
    private String testId = "";
    private String testUuid = "";
    
    private long testTime = 0;
    
    private String testHost = "";
    private int testPort = 0;
    private String remoteIp = "";
    private String serverName;
    private String provider;
    
    private int testDuration = 0;
    private int testNumThreads = 0;
    private int testNumPings = 0;
    
    private String clientUUID = "";
    
    private URL resultURL;
	private URL resultQoSURI;
    
    private String errorMsg = null;
    
    private boolean hasError = false;
    
    private Set<ErrorStatus> lastErrorList;
    
    public TaskDesc udpTaskDesc;
    public TaskDesc dnsTaskDesc;
    public TaskDesc ntpTaskDesc;
    public TaskDesc httpTaskDesc;
    public TaskDesc tcpTaskDesc;
    
    private JSONObject lastTestResult;
    
    public List<TaskDesc> v2TaskDesc;
    
    private long startTimeMillis = 0;
    private long startTimeNs = 0;
    
    private static URL getUrl(final boolean encryption, final String host, final String pathPrefix, final int port,
            final String path)
    {
        try
        {
            final String protocol = encryption ? "https" : "http";
            final int defaultPort = encryption ? 443 : 80;
            final String totalPath = (pathPrefix != null ? pathPrefix : "") + Config.RMBT_CONTROL_PATH + path;
            
            if (defaultPort == port)
                return new URL(protocol, host, totalPath);
            else
                return new URL(protocol, host, port, totalPath);
        }
        catch (final MalformedURLException e)
        {
            return null;
        }
    }
    
    /**
     * requests the parameters for the v2 tests
     * @param host
     * @param pathPrefix
     * @param port
     * @param encryption
     * @param geoInfo
     * @param uuid
     * @param clientType
     * @param clientName
     * @param clientVersion
     * @param additionalValues
     * @return
     */
    public String requestQoSTestParameters(final String host, final String pathPrefix, final int port,
            final boolean encryption, final ArrayList<String> geoInfo, final String uuid, final String clientType,
            final String clientName, final String clientVersion, final JSONObject additionalValues)
    {
    	resetErrors();
        clientUUID = uuid;
        
        hostUrl = getUrl(encryption, host, pathPrefix, port, Config.RMBT_CONTROL_V2_TESTS);
        
        System.out.println("Connection to " + hostUrl);
        
        final JSONObject regData = new JSONObject();
        
        try
        {
            regData.put("uuid", uuid);
            regData.put("client", clientName);
            regData.put("version", Config.RMBT_VERSION_NUMBER);
            regData.put("type", clientType);
            regData.put("softwareVersion", clientVersion);
            regData.put("softwareRevision", RevisionHelper.getVerboseRevision());
            regData.put("language", Locale.getDefault().getLanguage());
            regData.put("timezone", TimeZone.getDefault().getID());
            regData.put("time", System.currentTimeMillis());
            
            if (geoInfo != null)
            {
                final JSONObject locData = new JSONObject();
                locData.put("time", geoInfo.get(0));
                locData.put("lat", geoInfo.get(1));
                locData.put("long", geoInfo.get(2));
                locData.put("accuracy", geoInfo.get(3));
                locData.put("altitude", geoInfo.get(4));
                locData.put("bearing", geoInfo.get(5));
                locData.put("speed", geoInfo.get(6));
                locData.put("provider", geoInfo.get(7));
                
                regData.accumulate("location", locData);
            }
            
            addToJSONObject(regData, additionalValues);
            
        }
        catch (final JSONException e1)
        {
            hasError = true;
            errorMsg = "Error gernerating request";
            // e1.printStackTrace();
        }
        
        // getting JSON string from URL
        final JSONObject response = JSONParser.sendJSONToUrl(hostUrl, regData);
        
        if (response != null)
            try
            {
                final JSONArray errorList = response.getJSONArray("error");
                
                checkHasErrors(response);
                
                // System.out.println(response.toString(4));
                
                if (errorList.length() == 0)
                {
                	
                	int testPort = 5233;
                	
                	Map<String, Object> testParams = null;
                	testParams = JSONParser.toMap(response.getJSONObject("objectives"));
                	
                	v2TaskDesc = new ArrayList<TaskDesc>();
                	
                	for (Entry<String, Object> e : testParams.entrySet()) {
                		List<HashMap<String, Object>> paramList = (List<HashMap<String, Object>>) e.getValue();
                		for (HashMap<String, Object> params : paramList) {
                			TaskDesc taskDesc = new TaskDesc(testHost, testPort, encryption, testToken, 0, 1, 0, System.nanoTime(), params, e.getKey());
            				v2TaskDesc.add(taskDesc);
                		}
                	}                    
                }
                else
                {
                    hasError = true;
                    for (int i = 0; i < errorList.length(); i++)
                    {
                        if (i > 0)
                            errorMsg += "\n";
                        errorMsg += errorList.getString(i);
                    }
                }
                
                // }
            }
            catch (final JSONException e)
            {
                hasError = true;
                errorMsg = "Error parsing server response";
                e.printStackTrace();
            }
        else
        {
            hasError = true;
            errorMsg = "No response";
        }
        
        return errorMsg;
    }
    
    private void resetErrors() {
    	lastErrorList = null;
    }
    
    private boolean checkHasErrors(JSONObject response) throws JSONException {
    	final JSONArray errorList = response.getJSONArray("error");
    	final JSONArray errorFlags = response.optJSONArray("error_flags");
    	if (errorFlags != null && errorFlags.length() > 0) {
    		lastErrorList = new HashSet<ErrorStatus>();
    		for (int i = 0; i < errorFlags.length(); i++) {
    			lastErrorList.add(ErrorStatus.valueOf(errorFlags.getString(i)));
    		}
    	}
    	return errorList.length() > 0;
    }
    
    public String requestNewTestConnection(final String host, final String pathPrefix, final int port,
            final boolean encryption, final ArrayList<String> geoInfo, final String uuid, final String clientType,
            final String clientName, final String clientVersion, final JSONObject additionalValues)
    {
    	resetErrors();
        String errorMsg = null;
        // url to make request to
        
        clientUUID = uuid;
        
        hostUrl = getUrl(encryption, host, pathPrefix, port, Config.RMBT_CONTROL_MAIN_URL);
        
        System.out.println("Connection to " + hostUrl);
        
        final JSONObject regData = new JSONObject();
        
        try
        {
            regData.put("uuid", uuid);
            regData.put("client", clientName);
            regData.put("version", Config.RMBT_VERSION_NUMBER);
            regData.put("type", clientType);
            regData.put("softwareVersion", clientVersion);
            regData.put("softwareRevision", RevisionHelper.getVerboseRevision());
            regData.put("language", Locale.getDefault().getLanguage());
            regData.put("timezone", TimeZone.getDefault().getID());
            startTimeMillis = System.currentTimeMillis();
            regData.put("time", startTimeMillis);
            startTimeNs = System.nanoTime();
            
            if (geoInfo != null)
            {
                final JSONObject locData = new JSONObject();
                locData.put("time", geoInfo.get(0));
                locData.put("lat", geoInfo.get(1));
                locData.put("long", geoInfo.get(2));
                locData.put("accuracy", geoInfo.get(3));
                locData.put("altitude", geoInfo.get(4));
                locData.put("bearing", geoInfo.get(5));
                locData.put("speed", geoInfo.get(6));
                locData.put("provider", geoInfo.get(7));
                
                regData.accumulate("location", locData);
            }
            
            addToJSONObject(regData, additionalValues);
            
        }
        catch (final JSONException e1)
        {
            errorMsg = "Error gernerating request";
            // e1.printStackTrace();
        }
        
        // getting JSON string from URL
        final JSONObject response = JSONParser.sendJSONToUrl(hostUrl, regData);
        
        if (response != null)
            try
            {
                final JSONArray errorList = response.getJSONArray("error");
                
                // System.out.println(response.toString(4));
                checkHasErrors(response);
                
                if (errorList.length() == 0)
                {
                    
                    clientUUID = response.optString("uuid", clientUUID);
                    
                    testToken = response.getString("test_token");
                    
                    testId = response.getString("test_id");
                    testUuid = response.getString("test_uuid");
                    
                    testTime = System.currentTimeMillis() + 1000 * response.getLong("test_wait");
                    
                    testHost = response.getString("test_server_address");
                    testPort = response.getInt("test_server_port");
                    testEncryption = response.getBoolean("test_server_encryption");
                    serverName = response.optString("test_server_name", null);
                    provider = response.optString("provider", null);
                    
                    testDuration = response.getInt("test_duration");
                    testNumThreads = response.getInt("test_numthreads");
                    testNumPings = response.optInt("test_numpings", 10); // pings default to 10
                    
                    remoteIp = response.getString("client_remote_ip");
                                        
                    resultURL = new URL(response.getString("result_url"));
                    resultQoSURI = new URL(response.getString("result_qos_url"));
                }
                else
                {
                    errorMsg = "";
                    for (int i = 0; i < errorList.length(); i++)
                    {
                        if (i > 0)
                            errorMsg += "\n";
                        errorMsg += errorList.getString(i);
                    }
                }
                
                // }
            }
            catch (final JSONException e)
            {
                errorMsg = "Error parsing server response";
                e.printStackTrace();
            }
            catch (final MalformedURLException e)
            {
                errorMsg = "Error parsing server response";
                e.printStackTrace();
            }
        else
            errorMsg = "No response";
        return errorMsg;
    }
    
    public String sendTestResult(TotalTestResult result, JSONObject additionalValues)
    {
        String errorMsg = null;
        lastTestResult = null;
        
        if (resultURL != null)
        {
            
            final JSONObject testData = new JSONObject();
            
            try
            {
                testData.put("client_uuid", clientUUID);
                testData.put("client_name", Config.RMBT_CLIENT_NAME);
                testData.put("client_version", Config.RMBT_VERSION_NUMBER);
                testData.put("client_language", Locale.getDefault().getLanguage());
                
                testData.put("time", System.currentTimeMillis());
                
                testData.put("test_token", testToken);
                
                testData.put("test_port_remote", result.port_remote);
                testData.put("test_bytes_download", result.bytes_download);
                testData.put("test_bytes_upload", result.bytes_upload);
                testData.put("test_total_bytes_download", result.totalDownBytes);
                testData.put("test_total_bytes_upload", result.totalUpBytes);
                testData.put("test_encryption", result.encryption);
                testData.put("test_ip_local", result.ip_local.getHostAddress());
                testData.put("test_ip_server", result.ip_server.getHostAddress());
                testData.put("test_nsec_download", result.nsec_download);
                testData.put("test_nsec_upload", result.nsec_upload);
                testData.put("test_num_threads", result.num_threads);
                testData.put("test_speed_download", (long) Math.floor(result.speed_download + 0.5d));
                testData.put("test_speed_upload", (long) Math.floor(result.speed_upload + 0.5d));
                testData.put("test_ping_shortest", result.ping_shortest);
               
                //dz todo - add interface values
                
                // total bytes on interface
                testData.put("test_if_bytes_download", result.getTotalTrafficMeasurement(TrafficDirection.RX));
                testData.put("test_if_bytes_upload", result.getTotalTrafficMeasurement(TrafficDirection.TX)); 
                // bytes during download test
                testData.put("testdl_if_bytes_download", result.getTrafficByTestPart(TestStatus.DOWN, TrafficDirection.RX));
                testData.put("testdl_if_bytes_upload", result.getTrafficByTestPart(TestStatus.DOWN, TrafficDirection.TX));
                // bytes during upload test
                testData.put("testul_if_bytes_download", result.getTrafficByTestPart(TestStatus.UP, TrafficDirection.RX));
                testData.put("testul_if_bytes_upload", result.getTrafficByTestPart(TestStatus.UP, TrafficDirection.TX));
                
                //relative timestamps:
                TestMeasurement dlMeasurement = result.getTestMeasurementByTestPart(TestStatus.DOWN);
                if (dlMeasurement != null) {
                    testData.put("time_dl_ns", dlMeasurement.getTimeStampStart() - startTimeNs);
                }
                TestMeasurement ulMeasurement = result.getTestMeasurementByTestPart(TestStatus.UP);
                if (ulMeasurement != null) {
                    testData.put("time_ul_ns", ulMeasurement.getTimeStampStart() - startTimeNs);	
                }                	
                
                
                final JSONArray pingData = new JSONArray();
                
                if (result.pings != null && !result.pings.isEmpty())
                {
                    for (final Ping ping : result.pings)
                    {
                        final JSONObject pingItem = new JSONObject();
                        pingItem.put("value", ping.client);
                        pingItem.put("value_server", ping.server);
                        pingItem.put("time_ns", ping.timeNs - startTimeNs);
                        pingData.put(pingItem);
                    }
                }
                
                testData.put("pings", pingData);
                
                JSONArray speedDetail = new JSONArray();
                
                if (result.speedItems != null)
                {
                    for (SpeedItem item : result.speedItems) {
                        speedDetail.put(item.toJSON());
                    }
                }
                
                testData.put("speed_detail", speedDetail);
                
                addToJSONObject(testData, additionalValues);
                
                // System.out.println(testData.toString(4));
            }
            catch (final JSONException e1)
            {
                errorMsg = "Error gernerating request";
                e1.printStackTrace();
            }
            
            // getting JSON string from URL
            final JSONObject response = JSONParser.sendJSONToUrl(resultURL, testData);
            
            if (response != null)
                try
                {
                    final JSONArray errorList = response.getJSONArray("error");
                    
                    // System.out.println(response.toString(4));
                    
                    if (errorList.length() == 0)
                    {
                        lastTestResult = testData;
                        // System.out.println("All is fine");
                        
                    }
                    else
                    {
                        for (int i = 0; i < errorList.length(); i++)
                        {
                            if (i > 0)
                                errorMsg += "\n";
                            errorMsg += errorList.getString(i);
                        }
                    }
                    
                    // }
                }
                catch (final JSONException e)
                {
                    errorMsg = "Error parsing server response";
                    e.printStackTrace();
                }
        }
        else
            errorMsg = "No URL to send the Data to.";
        
        return errorMsg;
    }
    
    /**
     * 
     * @param result
     * @param qosTestResult
     * @return
     */
    public String sendQoSResult(final TotalTestResult result, final JSONArray qosTestResult)
    {
        String errorMsg = null;
        System.out.println("sending qos results to " + resultQoSURI);
        if (resultQoSURI != null)
        {
            
            final JSONObject testData = new JSONObject();
            
            try
            {
                testData.put("client_uuid", clientUUID);
                testData.put("client_name", Config.RMBT_CLIENT_NAME);
                testData.put("client_version", Config.RMBT_VERSION_NUMBER);
                testData.put("client_language", Locale.getDefault().getLanguage());
                
                testData.put("time", System.currentTimeMillis());
                
                testData.put("test_token", testToken);
                
               	testData.put("qos_result", qosTestResult);               
            }
            catch (final JSONException e1)
            {
                errorMsg = "Error gernerating request";
                e1.printStackTrace();
            }
            
            // getting JSON string from URL
            final JSONObject response = JSONParser.sendJSONToUrl(resultQoSURI, testData);
            
            if (response != null)
                try
                {
                    final JSONArray errorList = response.getJSONArray("error");
                    
                    // System.out.println(response.toString(4));
                    
                    if (errorList.length() == 0)
                    {
                        
                        // System.out.println("All is fine");
                        
                    }
                    else
                    {
                        for (int i = 0; i < errorList.length(); i++)
                        {
                            if (i > 0)
                                errorMsg += "\n";
                            errorMsg += errorList.getString(i);
                        }
                    }
                    
                    // }
                }
                catch (final JSONException e)
                {
                    errorMsg = "Error parsing server response";
                    e.printStackTrace();
                }
        }
        else
            errorMsg = "No URL to send the Data to.";
        
        return errorMsg;
    }
    
    public void sendNDTResult(final String host, final String pathPrefix, final int port, final boolean encryption,
            final String clientUUID, final UiServicesAdapter data, final String testUuid)
    {
        hostUrl = getUrl(encryption, host, pathPrefix, port, Config.RMBT_CONTROL_MAIN_URL);
        this.clientUUID = clientUUID;
        sendNDTResult(data, testUuid);
    }
    
    public void sendNDTResult(final UiServicesAdapter data, final String testUuid)
    {
        final JSONObject testData = new JSONObject();
        
        try
        {
            testData.put("client_uuid", clientUUID);
            testData.put("client_language", Locale.getDefault().getLanguage());
            if (testUuid != null)
                testData.put("test_uuid", testUuid);
            else
                testData.put("test_uuid", this.testUuid);
            testData.put("s2cspd", data.s2cspd);
            testData.put("c2sspd", data.c2sspd);
            testData.put("avgrtt", data.avgrtt);
            testData.put("main", data.sbMain.toString());
            testData.put("stat", data.sbStat.toString());
            testData.put("diag", data.sbDiag.toString());
            testData.put("time_ns", data.getStartTimeNs() - startTimeNs);
            testData.put("time_end_ns", data.getStopTimeNs() - startTimeNs);
            
            JSONParser.sendJSONToUrl(hostUrl.toURI().resolve(Config.RMBT_CONTROL_NDT_RESULT_URL).toURL(), testData);
            
            System.out.println(testData);
        }
        catch (final JSONException e)
        {
            e.printStackTrace();
        }
        catch (MalformedURLException e)
        {
            e.printStackTrace();
        }
        catch (URISyntaxException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static void addToJSONObject(final JSONObject data, final JSONObject additionalValues) throws JSONException
    {
        if (additionalValues != null && additionalValues.length() > 0)
        {
            final JSONArray attr = additionalValues.names();
            for (int i = 0; i < attr.length(); i++)
                data.put(attr.getString(i), additionalValues.get(attr.getString(i)));
        }
    }
    
    public String getRemoteIp()
    {
        return remoteIp;
    }
    
    public String getClientUUID()
    {
        return clientUUID;
    }
    
    public String getServerName()
    {
        return serverName;
    }
    
    public String getProvider()
    {
        return provider;
    }
    
    public long getTestTime()
    {
        return testTime;
    }
    
    /**
     * this time stamp is only a relative timestamp (see: {@link System#nanoTime()})
     * @return
     */
    public long getStartTimeNs() {
    	return startTimeNs;
    }
    
    /**
     * this is the starting (= timestamp of the test request) UNIX timestamp (see: {@link System#currentTimeMillis()})
     * @return
     */
    public long getStartTimeMillis() {
    	return startTimeMillis;
    }
    
    public String getTestId()
    {
        return testId;
    }
    
    public String getTestUuid()
    {
        return testUuid;
    }
    
    public JSONObject getLastTestResult() {
    	return lastTestResult;
    }
    
    public RMBTTestParameter getTestParameter(RMBTTestParameter overrideParams)
    {
        String host = testHost;
        int port = testPort;
        boolean encryption = testEncryption;
        int duration = testDuration;
        int numThreads = testNumThreads;
        int numPings = testNumPings;
        
        if (overrideParams != null)
        {
            if (overrideParams.getHost() != null && overrideParams.getPort() > 0)
            {
                host = overrideParams.getHost();
                encryption = overrideParams.isEncryption();
                port = overrideParams.getPort();
            }
            if (overrideParams.getDuration() > 0)
                duration = overrideParams.getDuration();
            if (overrideParams.getNumThreads() > 0)
                numThreads = overrideParams.getNumThreads();
        }
        return new RMBTTestParameter(host, port, encryption, testToken, duration, numThreads, numPings, testTime);
    }

	public Set<ErrorStatus> getLastErrorList() {
		return lastErrorList;
	}
}