// 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.cloudstack.network.topology;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import com.cloud.agent.api.Command;
import com.cloud.agent.api.PvlanSetupCommand;
import com.cloud.agent.manager.Commands;
import com.cloud.dc.DataCenter;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Network;
import com.cloud.network.PublicIpAddress;
import com.cloud.network.VpnUser;
import com.cloud.network.router.VirtualRouter;
import com.cloud.network.rules.AdvancedVpnRules;
import com.cloud.network.rules.DhcpEntryRules;
import com.cloud.network.rules.DhcpPvlanRules;
import com.cloud.network.rules.NetworkAclsRules;
import com.cloud.network.rules.NicPlugInOutRules;
import com.cloud.network.rules.PrivateGatewayRules;
import com.cloud.network.rules.StaticRoutesRules;
import com.cloud.network.rules.UserdataPwdRules;
import com.cloud.network.rules.VpcIpAssociationRules;
import com.cloud.network.vpc.NetworkACLItem;
import com.cloud.network.vpc.PrivateIpAddress;
import com.cloud.network.vpc.PrivateIpVO;
import com.cloud.network.vpc.StaticRouteProfile;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.NicProfile;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineProfile;

@Component
public class AdvancedNetworkVisitor extends BasicNetworkVisitor {

    private static final Logger s_logger = Logger.getLogger(AdvancedNetworkVisitor.class);

    @Override
    public boolean visit(final UserdataPwdRules userdata) throws ResourceUnavailableException {
        final VirtualRouter router = userdata.getRouter();

        final Commands commands = new Commands(Command.OnError.Stop);
        final VirtualMachineProfile profile = userdata.getProfile();
        final NicVO nicVo = userdata.getNicVo();
        final UserVmVO userVM = userdata.getUserVM();

        _commandSetupHelper.createPasswordCommand(router, profile, nicVo, commands);
        _commandSetupHelper.createVmDataCommand(router, userVM, nicVo, userVM.getDetail("SSH.PublicKey"), commands);

        return _networkGeneralHelper.sendCommandsToRouter(router, commands);
    }

    @Override
    public boolean visit(final DhcpEntryRules dhcp) throws ResourceUnavailableException {
        final VirtualRouter router = dhcp.getRouter();

        final Commands commands = new Commands(Command.OnError.Stop);
        final NicVO nicVo = dhcp.getNicVo();
        final UserVmVO userVM = dhcp.getUserVM();

        _commandSetupHelper.createDhcpEntryCommand(router, userVM, nicVo, commands);

        return _networkGeneralHelper.sendCommandsToRouter(router, commands);
    }

    @Override
    public boolean visit(final NicPlugInOutRules nicPlugInOutRules) throws ResourceUnavailableException {
        final VirtualRouter router = nicPlugInOutRules.getRouter();

        final Commands commands = nicPlugInOutRules.getNetUsageCommands();

        if (commands.size() > 0) {
            return _networkGeneralHelper.sendCommandsToRouter(router, commands);
        }
        return true;
    }

    @Override
    public boolean visit(final NetworkAclsRules acls) throws ResourceUnavailableException {
        final VirtualRouter router = acls.getRouter();
        final Network network = acls.getNetwork();

        final Commands commands = new Commands(Command.OnError.Continue);
        final List<? extends NetworkACLItem> rules = acls.getRules();
        _commandSetupHelper.createNetworkACLsCommands(rules, router, commands, network.getId(), acls.isPrivateGateway());

        return _networkGeneralHelper.sendCommandsToRouter(router, commands);
    }

    @Override
    public boolean visit(final VpcIpAssociationRules vpcip) throws ResourceUnavailableException {
        final VirtualRouter router = vpcip.getRouter();

        final Commands cmds = new Commands(Command.OnError.Continue);
        final Map<String, String> vlanMacAddress = vpcip.getVlanMacAddress();
        final List<PublicIpAddress> ipsToSend = vpcip.getIpsToSend();

        if (!ipsToSend.isEmpty()) {
            _commandSetupHelper.createVpcAssociatePublicIPCommands(router, ipsToSend, cmds, vlanMacAddress);
            return _networkGeneralHelper.sendCommandsToRouter(router, cmds);
        } else {
            return true;
        }
    }

    @Override
    public boolean visit(final PrivateGatewayRules privateGW) throws ResourceUnavailableException {
        final VirtualRouter router = privateGW.getRouter();
        final NicProfile nicProfile = privateGW.getNicProfile();

        final boolean isAddOperation = privateGW.isAddOperation();

        if (router.getState() == State.Running) {

            final PrivateIpVO ipVO = privateGW.retrivePrivateIP(this);
            final Network network = privateGW.retrievePrivateNetwork(this);

            final String netmask = NetUtils.getCidrNetmask(network.getCidr());
            final PrivateIpAddress ip = new PrivateIpAddress(ipVO, network.getBroadcastUri().toString(), network.getGateway(), netmask, nicProfile.getMacAddress());

            final List<PrivateIpAddress> privateIps = new ArrayList<PrivateIpAddress>(1);
            privateIps.add(ip);

            final Commands cmds = new Commands(Command.OnError.Stop);
            _commandSetupHelper.createVpcAssociatePrivateIPCommands(router, privateIps, cmds, isAddOperation);

            try {
                if (_networkGeneralHelper.sendCommandsToRouter(router, cmds)) {
                    s_logger.debug("Successfully applied ip association for ip " + ip + " in vpc network " + network);
                    return true;
                } else {
                    s_logger.warn("Failed to associate ip address " + ip + " in vpc network " + network);
                    return false;
                }
            } catch (final Exception ex) {
                s_logger.warn("Failed to send  " + (isAddOperation ? "add " : "delete ") + " private network " + network + " commands to rotuer ");
                return false;
            }
        } else if (router.getState() == State.Stopped || router.getState() == State.Stopping) {
            s_logger.debug("Router " + router.getInstanceName() + " is in " + router.getState() + ", so not sending setup private network command to the backend");
        } else {
            s_logger.warn("Unable to setup private gateway, virtual router " + router + " is not in the right state " + router.getState());

            throw new ResourceUnavailableException("Unable to setup Private gateway on the backend," + " virtual router " + router + " is not in the right state",
                    DataCenter.class, router.getDataCenterId());
        }
        return true;
    }

    @Override
    public boolean visit(final DhcpPvlanRules dhcp) throws ResourceUnavailableException {
        final VirtualRouter router = dhcp.getRouter();
        final PvlanSetupCommand setupCommand = dhcp.getSetupCommand();

        // In fact we send command to the host of router, we're not programming
        // router but the host
        final Commands cmds = new Commands(Command.OnError.Stop);
        cmds.addCommand(setupCommand);

        try {
            return _networkGeneralHelper.sendCommandsToRouter(router, cmds);
        } catch (final ResourceUnavailableException e) {
            s_logger.warn("Timed Out", e);
            return false;
        }
    }

    @Override
    public boolean visit(final StaticRoutesRules staticRoutesRules) throws ResourceUnavailableException {
        final VirtualRouter router = staticRoutesRules.getRouter();
        final List<StaticRouteProfile> staticRoutes = staticRoutesRules.getStaticRoutes();

        final Commands cmds = new Commands(Command.OnError.Continue);
        _commandSetupHelper.createStaticRouteCommands(staticRoutes, router, cmds);

        return _networkGeneralHelper.sendCommandsToRouter(router, cmds);
    }

    @Override
    public boolean visit(final AdvancedVpnRules vpnRules) throws ResourceUnavailableException {
        final VirtualRouter router = vpnRules.getRouter();
        final List<? extends VpnUser> users = vpnRules.getUsers();

        final Commands cmds = new Commands(Command.OnError.Continue);
        _commandSetupHelper.createApplyVpnUsersCommand(users, router, cmds);

        // Currently we receive just one answer from the agent. In the future we
        // have to parse individual answers and set
        // results accordingly
        return _networkGeneralHelper.sendCommandsToRouter(router, cmds);
    }
}