// 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 java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.zip.DeflaterOutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

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 com.cloud.utils.net.NetUtils;

import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Base64;
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.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.log4j.Logger;

public class SecurityGroupHttpClient {
    private static final Logger logger = Logger.getLogger(SecurityGroupHttpClient.class);
	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(IpPortAndProto[] ipps) {
		List<SecurityGroupRule> rules = new ArrayList<SecurityGroupRule>(
				ipps.length);
		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;
	}
}
