//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 com.cloud.network.resource;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.naming.ConfigurationException;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.cloud.agent.IAgentControl;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.DirectNetworkUsageAnswer;
import com.cloud.agent.api.DirectNetworkUsageCommand;
import com.cloud.agent.api.MaintainAnswer;
import com.cloud.agent.api.MaintainCommand;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.RecurringNetworkUsageAnswer;
import com.cloud.agent.api.RecurringNetworkUsageCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupTrafficMonitorCommand;
import com.cloud.host.Host;
import com.cloud.resource.ServerResource;
import com.cloud.utils.exception.ExecutionException;
import java.net.HttpURLConnection;

public class TrafficSentinelResource implements ServerResource {

    private String _name;
    private String _zoneId;
    private String _ip;
    private String _guid;
    private String _url;
    private String _inclZones;
    private String _exclZones;

    protected Logger logger = LogManager.getLogger(getClass());

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        try {

            _name = name;

            _zoneId = (String)params.get("zone");
            if (_zoneId == null) {
                throw new ConfigurationException("Unable to find zone");
            }

            _ip = (String)params.get("ipaddress");
            if (_ip == null) {
                throw new ConfigurationException("Unable to find IP");
            }

            _guid = (String)params.get("guid");
            if (_guid == null) {
                throw new ConfigurationException("Unable to find the guid");
            }

            _url = (String)params.get("url");
            if (_url == null) {
                throw new ConfigurationException("Unable to find url");
            }

            _inclZones = (String)params.get("inclZones");
            _exclZones = (String)params.get("exclZones");

            return true;
        } catch (Exception e) {
            throw new ConfigurationException(e.getMessage());
        }

    }

    @Override
    public StartupCommand[] initialize() {
        StartupTrafficMonitorCommand cmd = new StartupTrafficMonitorCommand();
        cmd.setName(_name);
        cmd.setDataCenter(_zoneId);
        cmd.setPod("");
        cmd.setPrivateIpAddress(_ip);
        cmd.setStorageIpAddress("");
        cmd.setVersion(TrafficSentinelResource.class.getPackage().getImplementationVersion());
        cmd.setGuid(_guid);
        return new StartupCommand[] {cmd};
    }

    @Override
    public Host.Type getType() {
        return Host.Type.TrafficMonitor;
    }

    @Override
    public String getName() {
        return _name;
    }

    @Override
    public PingCommand getCurrentStatus(final long id) {
        return new PingCommand(Host.Type.TrafficMonitor, id);
    }

    @Override
    public boolean start() {
        return true;
    }

    @Override
    public boolean stop() {
        return true;
    }

    @Override
    public void disconnected() {
        return;
    }

    @Override
    public IAgentControl getAgentControl() {
        return null;
    }

    @Override
    public void setAgentControl(IAgentControl agentControl) {
        return;
    }

    @Override
    public Answer executeRequest(Command cmd) {
        if (cmd instanceof ReadyCommand) {
            return execute((ReadyCommand)cmd);
        } else if (cmd instanceof MaintainCommand) {
            return execute((MaintainCommand)cmd);
        } else if (cmd instanceof DirectNetworkUsageCommand) {
            return execute((DirectNetworkUsageCommand)cmd);
        } else if (cmd instanceof RecurringNetworkUsageCommand) {
            return execute((RecurringNetworkUsageCommand)cmd);
        } else {
            return Answer.createUnsupportedCommandAnswer(cmd);
        }
    }

    private Answer execute(ReadyCommand cmd) {
        return new ReadyAnswer(cmd);
    }

    private synchronized RecurringNetworkUsageAnswer execute(RecurringNetworkUsageCommand cmd) {
        return new RecurringNetworkUsageAnswer(cmd);
    }

    private synchronized DirectNetworkUsageAnswer execute(DirectNetworkUsageCommand cmd) {
        try {
            return getPublicIpBytesSentAndReceived(cmd);
        } catch (ExecutionException e) {
            return new DirectNetworkUsageAnswer(cmd, e);
        }
    }

    private Answer execute(MaintainCommand cmd) {
        return new MaintainAnswer(cmd);
    }

    private DirectNetworkUsageAnswer getPublicIpBytesSentAndReceived(DirectNetworkUsageCommand cmd) throws ExecutionException {
        DirectNetworkUsageAnswer answer = new DirectNetworkUsageAnswer(cmd);

        try {
            //Direct Network Usage
            URL trafficSentinel;
            //Use Global include/exclude zones if there are no per TS zones
            if (_inclZones == null) {
                _inclZones = cmd.getIncludeZones();
            }

            if (_exclZones == null) {
                _exclZones = cmd.getExcludeZones();
            }

            BufferedReader in = null;
            OutputStream os = null;
            try {
                //Query traffic Sentinel using POST method. 3 parts to the connection call and subsequent writing.

                //Part 1 - Connect to the URL of the traffic sentinel's instance.
                trafficSentinel = new URL(_url + "/inmsf/Query");
                String postData = "script="+URLEncoder.encode(getScript(cmd.getPublicIps(), cmd.getStart(), cmd.getEnd()), "UTF-8")+"&authenticate=basic&resultFormat=txt";
                HttpURLConnection con = (HttpURLConnection) trafficSentinel.openConnection();
                con.setRequestMethod("POST");
                con.setRequestProperty("content-length", String.valueOf(postData.length()));
                con.setDoOutput(true);

                //Part 2 - Write Data
                os = con.getOutputStream();
                os.write(postData.getBytes("UTF-8"));

                //Part 3 - Read response of the request
                in = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String inputLine;

                while ((inputLine = in.readLine()) != null) {
                    //Parse the script output
                    StringTokenizer st = new StringTokenizer(inputLine, ",");
                    if (st.countTokens() == 3) {
                        String publicIp = st.nextToken();
                        Long bytesSent = new Long(st.nextToken());
                        Long bytesRcvd = new Long(st.nextToken());
                        long[] bytesSentAndReceived = new long[2];
                        bytesSentAndReceived[0] = bytesSent;
                        bytesSentAndReceived[1] = bytesRcvd;
                        answer.put(publicIp, bytesSentAndReceived);
                    }
                }
            } catch (MalformedURLException e1) {
                logger.info("Invalid Traffic Sentinel URL", e1);
                throw new ExecutionException(e1.getMessage());
            } catch (IOException e) {
                logger.debug("Error in direct network usage accounting", e);
                throw new ExecutionException(e.getMessage());
            } finally {
                if (os != null) {
                    os.close();
                }
                if (in != null) {
                    in.close();
                }
            }
        } catch (Exception e) {
            logger.debug(e);
            throw new ExecutionException(e.getMessage());
        }
        return answer;
    }

    private String getScript(List<String> ips, Date start, Date end) {
        String IpAddresses = "";
        for (int i = 0; i < ips.size(); i++) {
            IpAddresses += ips.get(i);
            if (i != (ips.size() - 1)) {
                // Append comma for all Ips except the last Ip
                IpAddresses += ",";
            }
        }
        String destZoneCondition = "";
        if (_inclZones != null && !_inclZones.isEmpty()) {
            destZoneCondition = " & destinationzone = " + _inclZones;
        }
        if (_exclZones != null && !_exclZones.isEmpty()) {
            destZoneCondition += " & destinationzone != " + _exclZones;
        }

        String srcZoneCondition = "";
        if (_inclZones != null && !_inclZones.isEmpty()) {
            srcZoneCondition = " & sourcezone = " + _inclZones;
        }
        if (_exclZones != null && !_exclZones.isEmpty()) {
            srcZoneCondition += " & sourcezone != " + _exclZones;
        }

        String startDate = getDateString(start);
        String endtDate = getDateString(end);
        StringBuffer sb = new StringBuffer();
        sb.append("var q = Query.topN(\"historytrmx\",");
        sb.append("                 \"ipsource,bytes\",");
        sb.append("                 \"ipsource = " + IpAddresses + destZoneCondition + "\",");
        sb.append("                 \"" + startDate + ", " + endtDate + "\",");
        sb.append("                 \"bytes\",");
        sb.append("                 100000);");
        sb.append("var totalsSent = {};");
        sb.append("var t = q.run(");
        sb.append("  function(row,table) {");
        sb.append("    if(row[0]) {    ");
        sb.append("      totalsSent[row[0]] = row[1];");
        sb.append("    }");
        sb.append("  });");
        sb.append("var q = Query.topN(\"historytrmx\",");
        sb.append("                 \"ipdestination,bytes\",");
        sb.append("                 \"ipdestination = " + IpAddresses + srcZoneCondition + "\",");
        sb.append("                 \"" + startDate + ", " + endtDate + "\",");
        sb.append("                 \"bytes\",");
        sb.append("                 100000);");
        sb.append("var totalsRcvd = {};");
        sb.append("var t = q.run(");
        sb.append("  function(row,table) {");
        sb.append("    if(row[0]) {");
        sb.append("      totalsRcvd[row[0]] = row[1];");
        sb.append("    }");
        sb.append("  });");
        sb.append("for (var addr in totalsSent) {");
        sb.append("    var TS = 0;");
        sb.append("    var TR = 0;");
        sb.append("    if(totalsSent[addr]) TS = totalsSent[addr];");
        sb.append("    if(totalsRcvd[addr]) TR = totalsRcvd[addr];");
        sb.append("    println(addr + \",\" + TS + \",\" + TR);");
        sb.append("}");
        return sb.toString();
    }

    private String getDateString(Date date) {
        DateFormat dfDate = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
        return dfDate.format(date);
    }

    @Override
    public void setName(String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setConfigParams(Map<String, Object> params) {
        // TODO Auto-generated method stub

    }

    @Override
    public Map<String, Object> getConfigParams() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getRunLevel() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void setRunLevel(int level) {
        // TODO Auto-generated method stub

    }
}
