/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2014. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/

package com.urbancode.air.plugin.f5

import iControl.CommonEnabledState
import iControl.CommonStatisticType
import iControl.LocalLBAvailabilityStatus
import iControl.LocalLBMonitorRule
import iControl.LocalLBMonitorRuleType
import iControl.LocalLBNodeAddressV2
import iControl.LocalLBSessionStatus

public class F5Node extends F5Server{
    // Static variables that explain the n^2 complex relationship
    // between nodes, pools, and pool members. General names were
    // given to apply to enabled/disabled, available/unavailable, existant/nonexistant
    public static final def NEGATIVE = 0              // Disabled/Unavailable/Nonexistant Results
    public static final def POSITIVE_AND_NEGATIVE = 1 // Mixed Results
    public static final def POSITIVE = 2              // Enabled/Available/Existant Results

    def nodeName = []
    def nodeAddress = []
    def monitorArray = []
    def nodeConnection = []

    // Constructor for F5Node class
    public F5Node(Map<String,String> props) {
        super(props)
        this.nodeName = text2StringArray(props['nodeName'])

        if (props['nodeAddress']){
            this.nodeAddress = text2StringArray(props['nodeAddress'])
        }

        if (props['monitors']) {
            this.monitorArray = text2StringArray(props['monitors'])
        }

        if (props['nodeConnection']) {
            this.nodeConnection  = text2LongArray(props['nodeConnection'])
        }
    }

    // Retrieves a text block and parses it into a String array
    // Used for node names, node addresses, and monitors properties
    public String[] text2StringArray(String input) {
        def output
        if (input) {
            output = input.split('\n').join(',').split(',').findAll{
                it && it.trim().size() > 0} as String[]
            output = output.collect {it.trim().replaceAll("\\s","")}
        }

        return output
    }

    // Retrieves a text block and parses the text into Long array
    // Used for node connections and node ports properties
    public Long[] text2LongArray(String input) {
        def output = []
        if (input) {
            def tempStringArray = []
            tempStringArray = input.split('\n').join(',').split(',').findAll{
                it && it.trim().size() > 0} as String[]
            tempStringArray.each {it.trim().replaceAll("\\s","")}

            for (temp in tempStringArray){
                output.add(Long.parseLong(temp.replaceAll("\\s","")))
            }
        }

        return output
    }

    // Create Node(s) in the F5 Server
    public void createNode() {
        if (doesNodeExist() == NEGATIVE) {
            println "Creating ${nodeName}..."
            def nodeStub = getConnection().getLocalLBNodeAddressV2()
            nodeStub.setTimeout(timeout * 1000)
            nodeStub.create(nodeName, nodeAddress, nodeConnection)
            if (doesNodeExist() == POSITIVE) {
                println "Node(s) successfully created!"
            }
            else {
                throw new Exception("Could not create node(s)!")
            }
        } else {
            throw new Exception("Node(s) already exist!")
        }
    }

    // Delete Node(s) in the F5 Server
    public void deleteNode() {
        if (doesNodeExist() == POSITIVE) {
            println "Deleting ${nodeName}..."
            def nodeStub = getConnection().getLocalLBNodeAddressV2()
            nodeStub.setTimeout(timeout * 1000)
            nodeStub.delete_node_address(nodeName)
            if (doesNodeExist() == NEGATIVE) {
                println "Node(s) successfully deleted!"
            }
            else {
                throw new Exception("Could not delete node(s)!")
            }
        } else {
            throw new Exception("Node(s) do not exist!")
        }
    }

    // Updates monitors to node(s)
    public void setNodeMonitors() {
        println "Updating node monitors to ${monitorArray}"
        def monitorRules = []
        def rule
        for (node in nodeName) {
            rule = new iControl.LocalLBMonitorRule(
                LocalLBMonitorRuleType.MONITOR_RULE_TYPE_AND_LIST,
                0,
                monitorArray)
            monitorRules.add(rule)
        }

        getConnection().getLocalLBNodeAddressV2().set_monitor_rule(
                nodeName, monitorRules as iControl.LocalLBMonitorRule[])

        // Five second window for the monitors to become enabled
        def enabled = waitForMonitors(5)
        if (!enabled) {
            throw new Exception("Could not enable node monitors!")
        }
    }

    // Check if node(s) does exist, does not exist, or mixed results
    public int doesNodeExist() {
        def result
        // Retrieve all the known Nodes
        def fullNodeList = getConnection().getLocalLBNodeAddressV2().get_list()
        def knownNodeList = []
        def unknownNodeList = []

        println "Does node exist..."
        for (node in nodeName) {
            if (fullNodeList.contains(getFolderName() + node)) {
                knownNodeList.add(node)
            } else {
                unknownNodeList.add(node)
            }
        }

        // Return output
        result = checkListsForEnabled1D(knownNodeList, unknownNodeList)

        return result
    }

    // Checks if node(s) are enabled, disabled, or mixed results
    public int getNodeEnabledStatus() {
        def result
        def enabledList = []
        def disabledList = []
        def index = 0

        println "Is node enabled..."
        // Iterate through each node's status to check if it's enabled/disabled
        for (nodeStatus in getConnection().getLocalLBNodeAddressV2().get_session_status(nodeName)) {
            if (nodeStatus == iControl.LocalLBSessionStatus.SESSION_STATUS_ENABLED) {
                enabledList.add(nodeName[index])
            } else {
                disabledList.add(nodeName[index])
            }
            index++
        }

        // Return output
        // Result is only true if all nodes are in the enabledList, therefore all nodes are enabled
        result = checkListsForEnabled1D(enabledList, disabledList)

        return result
    }

    // Enables node(s) in the F5 Server
    public void enableNode() {
        if (getNodeEnabledStatus() != POSITIVE) {
            println "Enabling node..."
            def node = getConnection().getLocalLBNodeAddressV2()
            node.setTimeout(timeout * 1000)
            node.set_session_enabled_state(
                nodeName,
                commonEnabledStateHelperNode(iControl.CommonEnabledState.STATE_ENABLED))

            if (getNodeEnabledStatus() != POSITIVE) {
                    throw new Exception("Could not enable node!")
                } else {
                    println "Done."
                }
        } else {
            println "Node is already enabled!"
        }
    }

    // Disables node(s) in the F5 Server
    public void disableNode() {
        if (getNodeEnabledStatus() != NEGATIVE) {
            println "Disabling node..."
            def node = getConnection().getLocalLBNodeAddressV2()
            node.setTimeout(timeout * 1000)
            node.set_session_enabled_state(
                nodeName,
                commonEnabledStateHelperNode(iControl.CommonEnabledState.STATE_DISABLED))

            if (getNodeEnabledStatus() != NEGATIVE) {
                    throw new Exception("Could not disable node!")
            } else {
                    println "Done."
            }
        } else {
            println "Node is already disabled!"
        }
    }

    // Checks if node monitors are enabled, disabled, or mixed
    public int getNodeMonitorStatus() {
        def result
        def enabledList = []
        def disabledList = []
        def index = 0

        println "Are monitors enabled..."
        // Iterate through the nodes to check monitors
        for (nodeMonitorStatus in getConnection().getLocalLBNodeAddressV2().get_monitor_status(
            nodeName)) {
            if (nodeMonitorStatus == iControl.LocalLBMonitorStatus.MONITOR_STATUS_UP) {
                enabledList.add(nodeName[index])
            } else {
                disabledList.add(nodeName[index])
            }
            index++
        }

        // Return output
        // Result is only true if all nodes are in the enabledList, therefore all nodes are enabled
        result = checkListsForEnabled1D(enabledList, disabledList)

        return result
    }

    // Enables node(s) monitors in the F5 Server
    public void enableNodeMonitor(){
        if (getNodeMonitorStatus() != POSITIVE) {
            println "Enable node monitors..."
            getConnection().getLocalLBNodeAddressV2().set_monitor_state(
                nodeName,
                commonEnabledStateHelperNode(iControl.CommonEnabledState.STATE_ENABLED))

            // Five second window for the monitors to become enabled
            def enabled = waitForMonitors(timeout)

            if (!enabled) {
                throw new Exception("Could not enable node monitors!")
            }
        } else {
            println "Node monitors are already enabled!"
        }
    }

    // Disables node(s) monitors in the F5 Server
    public void disableNodeMonitor(){
        if (getNodeMonitorStatus() != NEGATIVE) {
            println "Disable node monitors..."
            getConnection().getLocalLBNodeAddressV2().set_monitor_state(
                nodeName,
                commonEnabledStateHelperNode(iControl.CommonEnabledState.STATE_DISABLED))

            if (getNodeMonitorStatus() != NEGATIVE) {
                throw new Exception("Could not disable node monitors!")
            } else {
                println "Done."
            }
        } else {
            println "Node monitors are already disabled!"
        }
    }

    // Wait for monitors to come online
    public boolean waitForMonitors(int seconds) {
        def enabled = false
        for (int i = 0; i < seconds; i++) {
            sleep(1000)
            if (getNodeMonitorStatus() == POSITIVE) {
                enabled = true
                println "Monitors online!"
                break
            } else {
                if (i+1 % 10 == 0){
                    println "Check monitor timeout at ${i+1} seconds"
                }
            }
        }
        return enabled
    }

    // Checks if nodes are all available, unavailable, or mixed
    public int getNodeAvailability() {
        def result
        def availableList = []
        def notAvailableList = []
        def index = 0
        for (node in getConnection().getLocalLBNodeAddressV2().get_object_status(nodeName)) {
            if (node.availability_status ==
                iControl.LocalLBAvailabilityStatus.AVAILABILITY_STATUS_GREEN) {
                availableList.add(nodeName[index])
            } else {
                notAvailableList.add(nodeName[index])
            }
            index++
        }

        // Return Output
        // Result is only true if all nodes are in the availableList, therefore all nodes are available
        result = checkListsForEnabled1D(availableList, notAvailableList)

        return result
    }

    // Checks positive and negative lists to determine if all nodes
    // are enabled/disable, available/unavailable, or existant/nonexistant
    public int checkListsForEnabled1D( def positiveList, def negativeList) {
        def result

        if (!positiveList?.empty && !negativeList?.empty) {
            println "Positive: ${positiveList}"
            println "Negative: ${negativeList}"
            result = POSITIVE_AND_NEGATIVE

        }  else if (positiveList?.empty && negativeList?.empty){
            throw new Exception("Nodes were not found.")

        } else if (positiveList?.empty){
            println "Negative: ${negativeList}"
            result = NEGATIVE

        } else if (negativeList?.empty){
            println "Positive: ${positiveList}"
            result = POSITIVE

        } else {
            throw new Exception("Nodes were not found.")
        }

        return result
    }

    // Checks how many connections are occurring with node(s)
    // Used to confirm that a node is disabled
    public int getNodeActiveConnections() {
        def Long result = -1

        try {
            // Retrieve the statistics of each node
            for (node in getConnection().getLocalLBNodeAddressV2().get_statistics(nodeName)){
                // Iterate through each node address's statistics
                for (nodeStatistics in node.statistics) {
                    // Iterate through the node address's statistics
                    for (statistic in nodeStatistics.statistics) {
                        if (statistic.type ==
                            iControl.CommonStatisticType.STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS) {
                            if (result == -1) {
                                result =  statistic.value.low
                            } else {
                                result =  statistic.value.low + result
                            }
                            break
                        }
                    }
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace()
        }

        if (result < 0) {
            throw new Exception("Could not locate node(s) $nodeName")
        }

        println "Active connection count for nodes $nodeName is $result"
        return result
    }

    // Assists in the mass production of CommonEnabledState settings
    // for enabling and disabling nodes
    public iControl.CommonEnabledState[] commonEnabledStateHelperNode(
        iControl.CommonEnabledState state) {
        def enabledStateOutput = []
        for (node in nodeName) {
            enabledStateOutput.add(state)
        }
        enabledStateOutput as iControl.CommonEnabledState[]
        return enabledStateOutput
    }
}
