// 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.resource;

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

import javax.naming.ConfigurationException;

import org.apache.log4j.Logger;

import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckVirtualMachineAnswer;
import com.cloud.agent.api.CheckVirtualMachineCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.HostVmStateReportEntry;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.ShutdownCommand;
import com.cloud.agent.api.StartAnswer;
import com.cloud.agent.api.StartCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand;
import com.cloud.agent.api.StartupStorageCommand;
import com.cloud.agent.api.StopAnswer;
import com.cloud.agent.api.StopCommand;
import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.agent.manager.SimulatorManager;
import com.cloud.agent.manager.SimulatorManager.AgentType;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.Networks.RouterPrivateIpStrategy;
import com.cloud.simulator.MockVMVO;
import com.cloud.storage.Storage.StorageResourceType;
import com.cloud.storage.template.TemplateProp;
import com.cloud.utils.Pair;
import com.cloud.vm.VirtualMachine.PowerState;
import com.cloud.vm.VirtualMachine.State;

public class AgentRoutingResource extends AgentStorageResource {
    private static final Logger s_logger = Logger.getLogger(AgentRoutingResource.class);

    protected Map<String, State> _vms = new HashMap<String, State>();
    private Map<String, Pair<Long, Long>> _runningVms = new HashMap<String, Pair<Long, Long>>();
    long usedCpu = 0;
    long usedMem = 0;
    long totalCpu;
    long totalMem;
    protected String _mountParent;

    public AgentRoutingResource(long instanceId, AgentType agentType, SimulatorManager simMgr, String hostGuid) {
        super(instanceId, agentType, simMgr, hostGuid);
    }

    public AgentRoutingResource() {
        setType(Host.Type.Routing);
    }

    @Override
    public Answer executeRequest(Command cmd) {
        try {
            if (cmd instanceof StartCommand) {
                return execute((StartCommand)cmd);
            } else if (cmd instanceof StopCommand) {
                return execute((StopCommand)cmd);
            } else if (cmd instanceof CheckVirtualMachineCommand) {
                return execute((CheckVirtualMachineCommand)cmd);
            } else if (cmd instanceof ReadyCommand) {
                return new ReadyAnswer((ReadyCommand)cmd);
            } else if (cmd instanceof ShutdownCommand) {
                return execute((ShutdownCommand)cmd);
            } else {
                return _simMgr.simulate(cmd, hostGuid);
            }
        } catch (IllegalArgumentException e) {
            return new Answer(cmd, false, e.getMessage());
        }
    }

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

    @Override
    public PingCommand getCurrentStatus(long id) {
        if (isStopped()) {
            return null;
        }
        synchronized (_vms) {
            if (_vms.size() == 0) {
                //load vms state from database
                _vms.putAll(_simMgr.getVmStates(hostGuid));
            }
        }
        final HashMap<String, State> newStates = sync();
        HashMap<String, Pair<Long, Long>> nwGrpStates = _simMgr.syncNetworkGroups(hostGuid);
        return new PingRoutingWithNwGroupsCommand(getType(), id, newStates, getHostVmStateReport(), nwGrpStates);
    }

    @Override
    public StartupCommand[] initialize() {
        synchronized (_vms) {
            _vms.clear();
        }
        Map<String, State> changes = _simMgr.getVmStates(this.hostGuid);
        Map<String, MockVMVO> vmsMaps = _simMgr.getVms(this.hostGuid);
        totalCpu = agentHost.getCpuCount() * agentHost.getCpuSpeed();
        totalMem = agentHost.getMemorySize();
        for (Map.Entry<String, MockVMVO> entry : vmsMaps.entrySet()) {
            MockVMVO vm = entry.getValue();
            usedCpu += vm.getCpu();
            usedMem += vm.getMemory();
            _runningVms.put(entry.getKey(), new Pair<Long, Long>(Long.valueOf(vm.getCpu()), vm.getMemory()));
        }

        List<Object> info = getHostInfo();

        StartupRoutingCommand cmd =
            new StartupRoutingCommand((Integer)info.get(0), (Long)info.get(1), (Long)info.get(2), (Long)info.get(4), (String)info.get(3), HypervisorType.Simulator,
                RouterPrivateIpStrategy.HostLocal);
        cmd.setStateChanges(changes);

        Map<String, String> hostDetails = new HashMap<String, String>();
        hostDetails.put(RouterPrivateIpStrategy.class.getCanonicalName(), RouterPrivateIpStrategy.DcGlobal.toString());

        cmd.setHostDetails(hostDetails);
        cmd.setAgentTag("agent-simulator");
        cmd.setPrivateIpAddress(agentHost.getPrivateIpAddress());
        cmd.setPrivateNetmask(agentHost.getPrivateNetMask());
        cmd.setPrivateMacAddress(agentHost.getPrivateMacAddress());
        cmd.setStorageIpAddress(agentHost.getStorageIpAddress());
        cmd.setStorageNetmask(agentHost.getStorageNetMask());
        cmd.setStorageMacAddress(agentHost.getStorageMacAddress());
        cmd.setStorageIpAddressDeux(agentHost.getStorageIpAddress());
        cmd.setStorageNetmaskDeux(agentHost.getStorageNetMask());
        cmd.setStorageMacAddressDeux(agentHost.getStorageIpAddress());

        cmd.setName(agentHost.getName());
        cmd.setGuid(agentHost.getGuid());
        cmd.setVersion(agentHost.getVersion());
        cmd.setAgentTag("agent-simulator");
        cmd.setDataCenter(String.valueOf(agentHost.getDataCenterId()));
        cmd.setPod(String.valueOf(agentHost.getPodId()));
        cmd.setCluster(String.valueOf(agentHost.getClusterId()));

        StartupStorageCommand ssCmd = initializeLocalSR();

        return new StartupCommand[] {cmd, ssCmd};
    }

    private StartupStorageCommand initializeLocalSR() {
        Map<String, TemplateProp> tInfo = new HashMap<String, TemplateProp>();

        StoragePoolInfo poolInfo = _simMgr.getLocalStorage(hostGuid);

        StartupStorageCommand cmd = new StartupStorageCommand(poolInfo.getHostPath(), poolInfo.getPoolType(), poolInfo.getCapacityBytes(), tInfo);

        cmd.setPoolInfo(poolInfo);
        cmd.setGuid(agentHost.getGuid());
        cmd.setResourceType(StorageResourceType.STORAGE_POOL);
        return cmd;
    }

    protected synchronized Answer execute(StartCommand cmd) throws IllegalArgumentException {
        VirtualMachineTO vmSpec = cmd.getVirtualMachine();
        String vmName = vmSpec.getName();
        if (this.totalCpu < (vmSpec.getCpus() * vmSpec.getMaxSpeed() + this.usedCpu) || this.totalMem < (vmSpec.getMaxRam() + this.usedMem)) {
            return new StartAnswer(cmd, "Not enough resource to start the vm");
        }
        State state = State.Stopped;
        synchronized (_vms) {
            _vms.put(vmName, State.Starting);
        }

        try {
            Answer result = _simMgr.simulate(cmd, hostGuid);
            if (!result.getResult()) {
                return new StartAnswer(cmd, result.getDetails());
            }

            this.usedCpu += vmSpec.getCpus() * vmSpec.getMaxSpeed();
            this.usedMem += vmSpec.getMaxRam();
            _runningVms.put(vmName, new Pair<Long, Long>(Long.valueOf(vmSpec.getCpus() * vmSpec.getMaxSpeed()), vmSpec.getMaxRam()));
            state = State.Running;

        } finally {
            synchronized (_vms) {
                _vms.put(vmName, state);
            }
        }

        return new StartAnswer(cmd);

    }

    protected synchronized StopAnswer execute(StopCommand cmd) {

        StopAnswer answer = null;
        String vmName = cmd.getVmName();

        State state = null;
        synchronized (_vms) {
            state = _vms.get(vmName);
            _vms.put(vmName, State.Stopping);
        }
        try {
            Answer result = _simMgr.simulate(cmd, hostGuid);

            if (!result.getResult()) {
                return new StopAnswer(cmd, result.getDetails(), false);
            }

            answer = new StopAnswer(cmd, null, true);
            Pair<Long, Long> data = _runningVms.get(vmName);
            if (data != null) {
                this.usedCpu -= data.first();
                this.usedMem -= data.second();
            }
            state = State.Stopped;

        } finally {
            synchronized (_vms) {
                _vms.put(vmName, state);
            }
        }

        return answer;
    }

    protected CheckVirtualMachineAnswer execute(final CheckVirtualMachineCommand cmd) {
        final String vmName = cmd.getVmName();
        CheckVirtualMachineAnswer result = (CheckVirtualMachineAnswer)_simMgr.simulate(cmd, hostGuid);
        State state = result.getState();
        if (state == State.Running) {
            synchronized (_vms) {
                _vms.put(vmName, State.Running);
            }
        }
        return result;
    }

    protected List<Object> getHostInfo() {
        ArrayList<Object> info = new ArrayList<Object>();
        long speed = agentHost.getCpuSpeed();
        long cpus = agentHost.getCpuCount();
        long ram = agentHost.getMemorySize();
        long dom0Ram = agentHost.getMemorySize() / 10;

        info.add((int)cpus);
        info.add(speed);
        info.add(ram);
        info.add(agentHost.getCapabilities());
        info.add(dom0Ram);

        return info;
    }

    protected HashMap<String, HostVmStateReportEntry> getHostVmStateReport() {
        HashMap<String, HostVmStateReportEntry> report = new HashMap<String, HostVmStateReportEntry>();

        for (String vmName : _runningVms.keySet()) {
            report.put(vmName, new HostVmStateReportEntry(PowerState.PowerOn, agentHost.getName(), null));
        }

        return report;
    }

    protected HashMap<String, State> sync() {
        Map<String, State> newStates;
        Map<String, State> oldStates = null;

        HashMap<String, State> changes = new HashMap<String, State>();

        synchronized (_vms) {
            oldStates = new HashMap<String, State>(_vms.size());
            oldStates.putAll(_vms);
            newStates = new HashMap<String, State>(_vms.size());
            newStates.putAll(_vms);

            for (Map.Entry<String, State> entry : newStates.entrySet()) {
                String vm = entry.getKey();

                State newState = entry.getValue();
                State oldState = oldStates.remove(vm);

                if (s_logger.isTraceEnabled()) {
                    s_logger.trace("VM " + vm + ": has state " + newState + " and we have state " + (oldState != null ? oldState.toString() : "null"));
                }

                if (oldState == null) {
                    _vms.put(vm, newState);
                    changes.put(vm, newState);
                } else if (oldState == State.Starting) {
                    if (newState == State.Running) {
                        _vms.put(vm, newState);
                    } else if (newState == State.Stopped) {
                        s_logger.debug("Ignoring vm " + vm + " because of a lag in starting the vm.");
                    }
                } else if (oldState == State.Stopping) {
                    if (newState == State.Stopped) {
                        _vms.put(vm, newState);
                    } else if (newState == State.Running) {
                        s_logger.debug("Ignoring vm " + vm + " because of a lag in stopping the vm. ");
                    }
                } else if (oldState != newState) {
                    _vms.put(vm, newState);
                    changes.put(vm, newState);
                }
            }

            for (Map.Entry<String, State> entry : oldStates.entrySet()) {
                String vm = entry.getKey();
                State oldState = entry.getValue();

                if (s_logger.isTraceEnabled()) {
                    s_logger.trace("VM " + vm + " is now missing from simulator agent so reporting stopped");
                }

                if (oldState == State.Stopping) {
                    s_logger.debug("Ignoring VM " + vm + " in transition state stopping.");
                    _vms.remove(vm);
                } else if (oldState == State.Starting) {
                    s_logger.debug("Ignoring VM " + vm + " in transition state starting.");
                } else if (oldState == State.Stopped) {
                    _vms.remove(vm);
                } else {
                    changes.put(entry.getKey(), State.Stopped);
                }
            }
        }

        return changes;
    }

    private Answer execute(ShutdownCommand cmd) {
        this.stopped = true;
        return new Answer(cmd);
    }

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        if (!super.configure(name, params)) {
            s_logger.warn("Base class was unable to configure");
            return false;
        }
        return true;
    }
}
