/*
 * 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 org.apache.brooklyn.location.jclouds.networking;

import java.util.Set;

import org.apache.brooklyn.location.jclouds.JcloudsLocation;
import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.Strings;
import org.jclouds.aws.ec2.AWSEC2Api;
import org.jclouds.aws.util.AWSUtils;
import org.jclouds.compute.domain.SecurityGroup;
import org.jclouds.compute.extensions.SecurityGroupExtension;
import org.jclouds.net.domain.IpPermission;
import org.jclouds.rest.ApiContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.Beta;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;

/** WIP to apply a security group to a jclouds endpoint.
 * <p>
 * sections of this code have been used but overall it is not yet in a working state,
 * but merged May 2014 to make it easier to pick up if and when needed.
 * (if not needed after several months this may simply be removed.) */
@Beta
public class SecurityGroupTool {

    private static final Logger log = LoggerFactory.getLogger(SecurityGroupTool.class);
    
    protected final JcloudsLocation location;
    protected final SecurityGroupDefinition sgDef;

    public SecurityGroupTool(JcloudsLocation location, SecurityGroupDefinition sgDef) {
        this.location = Preconditions.checkNotNull(location);
        this.sgDef = Preconditions.checkNotNull(sgDef);
    }
    
    public String getName() {
        return sgDef.getName();
    }
    
    public void apply() {
        Optional<SecurityGroupExtension> sgExtO = location.getComputeService().getSecurityGroupExtension();
        if (!sgExtO.isPresent()) {
            throw new IllegalStateException("Advanced networking not supported in this location ("+location+")");
        }
        SecurityGroupExtension sgExt = sgExtO.get();
        
        SecurityGroup sg = findSecurityGroupWithName(sgExt, getName());
        if (sg==null) {
            // TODO initialize the location
            org.jclouds.domain.Location sgLoc = null;

            // TODO record that we created it
            // create it
            try {
                // FIXME this will always fail for providers which need a location, until we set it above
                // https://github.com/brooklyncentral/brooklyn/pull/1343#discussion_r12275188
                sg = sgExt.createSecurityGroup(getName(), sgLoc);
            } catch (Exception e) {
                Exceptions.propagateIfFatal(e);
                // check if someone else already created it
                sg = findSecurityGroupWithName(sgExt, getName());
                if (sg==null) {
                    // no - so propagate error
                    throw Exceptions.propagate(e);
                } else {
                    log.debug("Looks like parallel thread created security group "+getName()+"; ignoring error in our thread ("+e+") as we now have an SG");
                }
            }
        }
        
        if (sg==null)
            throw new IllegalStateException("Unable to find or create security group ID for "+getName());

        addPermissions(sgExt, sg);
    }
    
    protected SecurityGroup findSecurityGroupWithName(SecurityGroupExtension sgExt, String name) {
        Set<SecurityGroup> groups = sgExt.listSecurityGroups();
        // jclouds appends this sometimes so for portability let's add this
        String nameAlt = name.startsWith("jclouds#") ? Strings.removeFromStart(name, "jclouds#") : "jclouds#"+name;
        for (SecurityGroup g: groups) {
            if (name.equals(g.getName())) return g;
            if (nameAlt.equals(g.getName())) return g;
        }
        return null;
    }

    protected void addPermissions(SecurityGroupExtension sgExt, SecurityGroup sg) {

        Object api = ((ApiContext<?>)location.getComputeService().getContext().unwrap()).getApi();
        if (api instanceof AWSEC2Api) {
            // optimization for AWS where rules can be added all at once, and it cuts down Req Limit Exceeded problems!
            String region = AWSUtils.getRegionFromLocationOrNull(sg.getLocation());
            String id = sg.getProviderId();
            
            ((AWSEC2Api)api).getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region, id, sgDef.getPermissions());
            
        } else {
            for (IpPermission p: sgDef.getPermissions()) {
                sgExt.addIpPermission(p, sg);
            }
        }
    }
    
    
    // TODO remove this method once we've confirmed the above works nicely (this is an early attempt)
    protected void applyOldEc2(AWSEC2Api client) {
        String region = location.getConfig(JcloudsLocationConfig.CLOUD_REGION_ID);
        if (region==null) {
            // TODO where does the default come from?
            log.warn("No region set for "+location+"; assuming EC2");
            region = "us-east-1"; 
        }
        
        Set<org.jclouds.ec2.domain.SecurityGroup> groups = client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, getName());
        String id = null;
        if (groups.isEmpty()) {
            // create it
            try {
                id = client.getSecurityGroupApi().get().createSecurityGroupInRegionAndReturnId(region , getName(), "Brooklyn-managed security group "+getName());
            } catch (Exception e) {
                Exceptions.propagateIfFatal(e);
                // check if someone else already created it!
                groups = client.getSecurityGroupApi().get().describeSecurityGroupsInRegion(region, getName());
                if (groups.isEmpty()) {
                    // no - so propagate error
                    throw Exceptions.propagate(e);
                } else {
                    log.debug("Looks like parallel thread created security group "+getName()+"; ignoring error in our thread ("+e+") as we now have an SG");
                }
            }
        }
        if (!groups.isEmpty()) {
            if (groups.size()>1)
                log.warn("Multiple security groups matching '"+getName()+"' (using the first): "+groups);
            id = groups.iterator().next().getId();
        }
        if (id==null)
            throw new IllegalStateException("Unable to find or create security group ID for "+getName());

        client.getSecurityGroupApi().get().authorizeSecurityGroupIngressInRegion(region, id, sgDef.getPermissions());
    }

}
