// 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.
//
// Automatically generated by addcopyright.py at 01/29/2013
// Apache License, Version 2.0 (the "License"); you may not use this
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// Automatically generated by addcopyright.py at 04/03/2012

package com.cloud.baremetal.networkservice;

import com.cloud.agent.api.SecurityGroupRuleAnswer;
import com.cloud.agent.api.SecurityGroupRulesCmd;
import com.cloud.agent.api.SecurityGroupRulesCmd.IpPortAndProto;
import com.cloud.baremetal.networkservice.schema.SecurityGroupRule;
import com.cloud.baremetal.networkservice.schema.SecurityGroupVmRuleSet;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import java.io.StringWriter;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class SecurityGroupHttpClient {
    protected Logger logger = LogManager.getLogger(getClass());
    private static final String ARG_NAME = "args";
    private static final String COMMAND = "command";
    private JAXBContext context;
    private int port;
    private static HttpClient httpClient;
    static {
        MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManager();
        httpClient = new HttpClient(connman);
        httpClient.setConnectionTimeout(5000);
    }

    private enum OpConstant {
        setRules, echo,
    }

    public SecurityGroupHttpClient() {
        try {
            context = JAXBContext.newInstance(SecurityGroupRule.class, SecurityGroupVmRuleSet.class);
            port = 9988;
        } catch (Exception e) {
            throw new CloudRuntimeException(
                    "Unable to create JAXBContext for security group", e);
        }
    }

    private List<SecurityGroupRule> generateRules(final List<IpPortAndProto> ipps) {
        List<SecurityGroupRule> rules = new ArrayList<SecurityGroupRule>(
                ipps.size());
        for (SecurityGroupRulesCmd.IpPortAndProto ipp : ipps) {
            SecurityGroupRule r = new SecurityGroupRule();
            r.setProtocol(ipp.getProto());
            r.setStartPort(ipp.getStartPort());
            r.setEndPort(ipp.getEndPort());
            for (String cidr : ipp.getAllowedCidrs()) {
                r.getIp().add(cidr);
            }
            rules.add(r);
        }
        return rules;
    }

    public HashMap<String, Pair<Long, Long>> sync(String vmName, Long vmId, String agentIp) {
        HashMap<String, Pair<Long, Long>> states = new HashMap<String, Pair<Long, Long>>();
        PostMethod post = new PostMethod(String.format("http://%s:%s/", agentIp, getPort()));
        try {
            post.addRequestHeader("command", "sync");
            if (httpClient.executeMethod(post) != 200) {
                logger.debug(String.format("echoing baremetal security group agent on %s got error: %s", agentIp, post.getResponseBodyAsString()));
            } else {
                String res = post.getResponseBodyAsString();
                // res = ';'.join([vmName, vmId, seqno])
                String[] rulelogs = res.split(",");
                if (rulelogs.length != 6) {
                    logger.debug(String.format("host[%s] returns invalid security group sync document[%s], reset rules", agentIp, res));
                    states.put(vmName, new Pair<Long, Long>(vmId, -1L));
                    return states;
                }
                Pair<Long, Long> p = new Pair<Long, Long>(Long.valueOf(rulelogs[1]), Long.valueOf(rulelogs[5]));
                states.put(rulelogs[0], p);
                return states;
            }
        } catch (SocketTimeoutException se) {
            logger.warn(String.format("unable to sync security group rules on host[%s], %s", agentIp, se.getMessage()));
        } catch (Exception e) {
            logger.warn(String.format("unable to sync security group rules on host[%s]", agentIp), e);
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
        return states;
    }


    public boolean echo(String agentIp, long l, long m) {
        boolean ret = false;
        int count = 1;
        while (true) {
            try {
                Thread.sleep(m);
                count++;
            } catch (InterruptedException e1) {
                logger.warn("", e1);
                break;
            }
            PostMethod post = new PostMethod(String.format("http://%s:%s/", agentIp, getPort()));
            try {
                post.addRequestHeader("command", "echo");
                if (httpClient.executeMethod(post) != 200) {
                    logger.debug(String.format("echoing baremetal security group agent on %s got error: %s", agentIp, post.getResponseBodyAsString()));
                } else {
                    ret = true;
                }
                break;
            } catch (Exception e) {
                if (count*m >= l) {
                    logger.debug(String.format("ping security group agent on vm[%s] timeout after %s minutes, starting vm failed, count=%s", agentIp, TimeUnit.MILLISECONDS.toSeconds(l), count));
                    break;
                } else {
                    logger.debug(String.format("Having pinged security group agent on vm[%s] %s times, continue to wait...", agentIp, count));
                }
            } finally {
                if (post != null) {
                    post.releaseConnection();
                }
            }
        }
        return ret;
    }

    public SecurityGroupRuleAnswer call(String agentIp, SecurityGroupRulesCmd cmd) {
        PostMethod post = new PostMethod(String.format(
                "http://%s:%s", agentIp, getPort()));
        try {
            SecurityGroupVmRuleSet rset = new SecurityGroupVmRuleSet();
            rset.getEgressRules().addAll(generateRules(cmd.getEgressRuleSet()));
            rset.getIngressRules().addAll(
                    generateRules(cmd.getIngressRuleSet()));
            rset.setVmName(cmd.getVmName());
            rset.setVmIp(cmd.getGuestIp());
            rset.setVmMac(cmd.getGuestMac());
            rset.setVmId(cmd.getVmId());
            rset.setSignature(cmd.getSignature());
            rset.setSequenceNumber(cmd.getSeqNum());
            Marshaller marshaller = context.createMarshaller();
            StringWriter writer = new StringWriter();
            marshaller.marshal(rset, writer);
            String xmlContents = writer.toString();
            logger.debug(xmlContents);

            post.addRequestHeader("command", "set_rules");
            StringRequestEntity entity = new StringRequestEntity(xmlContents);
            post.setRequestEntity(entity);
            if (httpClient.executeMethod(post) != 200) {
                return new SecurityGroupRuleAnswer(cmd, false,
                        post.getResponseBodyAsString());
            } else {
                return new SecurityGroupRuleAnswer(cmd);
            }
        } catch (Exception e) {
            return new SecurityGroupRuleAnswer(cmd, false, e.getMessage());
        } finally {
            if (post != null) {
                post.releaseConnection();
            }
        }
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}
