/**
* Licensed Materials - Property of IBM* and/or HCL**
* IBM UrbanCode Deploy
* (c) Copyright IBM Corporation 2002, 2017. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*
* * Trademark of International Business Machines
* ** Trademark of HCL Technologies Limited
*/

package com.urbancode.air.plugin.f5

import iControl.CommonAddressPort
import iControl.CommonEnabledState
import iControl.CommonStatisticType
import iControl.LocalLBSessionStatus

public class F5PoolMember extends F5Node{
    def nodePort = []
    def poolName

    // Constructor for F5PoolMemberclass
    public F5PoolMember(Map<String,String> props) {
        super(props)
        this.poolName = getPoolName()
        this.nodePort = text2LongArray(props['nodePort'])

        if (nodePort.size() != nodeName.size()) {
            throw new RuntimeException("[Error] The specified number of Node Names (${nodeName.size()}) does not " +
            "equal the specified number of Node Ports (${nodePort.size()}). These counts must match.")
        }
    }

    // Retrieves the pool name and parses the text into a useable format
    public String[] getPoolName() {
        if (!poolName) {
            if (getMajorVersion() < 11 || !folder) {
                poolName = props['poolName'].split('\n').join(',').split(',').findAll{
                it && it.trim().size() > 0} as String[]
                poolName.each {it.trim().replaceAll("\\s","")}
            }
            else {
                poolName = props['poolName']?.trim().split('\n').join(',').split(',').findAll{
                it && it.trim().size() > 0} as String[]
                poolName = poolName.collect{getFolderName() + it.trim().replaceAll("\\s","")}
            }
        }
        return poolName
    }

    // Check if a given node is a pool member (true) or not a pool member (false)
    public int isNodeAPoolMember() {
        def knownPoolMembers = [][]
        def tempKnown
        def unknownPoolMembers = [][]
        def tempUnknown
        def result
        def poolIndex = 0
        def nodeIndex

        println "Is Node a Pool Member..."
        for (poolMembers in getConnection().getLocalLBPool().get_member_v2(poolName)) {
            tempKnown = []
            tempUnknown = []
            nodeIndex = 0
            for (node in nodeName) {
                if (poolMembers.contains(new iControl.CommonAddressPort(
                    getFolderName() + node, nodePort[nodeIndex]))) {
                    tempKnown.add(poolName[poolIndex] + ":" + node + ":" + nodePort[nodeIndex])
                } else {
                    tempUnknown.add(poolName[poolIndex] + ":" + node + ":" + nodePort[nodeIndex])
                }
                nodeIndex++
            }

            knownPoolMembers.add(tempKnown)
            unknownPoolMembers.add(tempUnknown)

            poolIndex++
        }

        result = checkListsForEnabled2D(knownPoolMembers, unknownPoolMembers)
        return result
    }

    // Checks if the pool member is enabled, disabled, or mixed
    public int isPoolMemberEnabled() {
        def result
        def enabledList = [][]
        def tempEnabledList
        def disabledList = [][]
        def tempDisabledList

        if (isNodeAPoolMember() != POSITIVE) {
            throw new Exception("Pool member not found. Must add the nodes to a pool.")
        }

        def poolIndex = 0
        def nodeIndex
        println "Is Pool Member enabled..."
        for (poolStatus in getConnection().getLocalLBPool().get_member_session_status(
            poolName,
            commonAddressPortHelperPoolMember())) {
            tempEnabledList = []
            tempDisabledList = []
            nodeIndex = 0
            for (poolMemberStatus in poolStatus){
                if (poolMemberStatus == iControl.LocalLBSessionStatus.SESSION_STATUS_ENABLED) {
                    tempEnabledList.add(poolName[poolIndex] +
                    ":" + nodeName[nodeIndex] +
                    ":" + nodePort[nodeIndex])
                } else {
                    tempDisabledList.add(poolName[poolIndex] +
                    ":" + nodeName[nodeIndex] +
                    ":" + nodePort[nodeIndex])
                }
                nodeIndex++
            }
            enabledList.add(tempEnabledList)
            disabledList.add(tempDisabledList)
            poolIndex++
        }

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

        return result
    }

    // Checks if the pool member is online, offline, or mixed
    public int isPoolMemberOnline() {
        def result
        def enabledList = [][]
        def tempEnabledList
        def disabledList = [][]
        def tempDisabledList

        if (isNodeAPoolMember() != POSITIVE) {
            throw new Exception("Pool member not found. Must add the nodes to a pool.")
        }

        def poolIndex = 0
        def nodeIndex
        println "Is Pool Member online..."
        for (poolStatus in getConnection().getLocalLBPool().get_member_monitor_status(
            poolName,
            commonAddressPortHelperPoolMember())) {
            tempEnabledList = []
            tempDisabledList = []
            nodeIndex = 0
            for (poolMemberStatus in poolStatus){
                if (poolMemberStatus == iControl.LocalLBMonitorStatus.MONITOR_STATUS_UP) {
                    tempEnabledList.add(poolName[poolIndex] +
                    ":" + nodeName[nodeIndex] +
                    ":" + nodePort[nodeIndex])
                } else {
                    tempDisabledList.add(poolName[poolIndex] +
                    ":" + nodeName[nodeIndex] +
                    ":" + nodePort[nodeIndex])
                }
                nodeIndex++
            }
            enabledList.add(tempEnabledList)
            disabledList.add(tempDisabledList)
            poolIndex++
        }

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

        return result
    }

    // Enables pool member(s) in a pool(s)
    public void enablePoolMember() {
        if (isPoolMemberEnabled() != POSITIVE) {
            println "Enabling pool member(s)..."
            def pool = getConnection().getLocalLBPool()
            pool.setTimeout(timeout * 1000)
            pool.set_member_session_enabled_state(
                    poolName,
                    commonAddressPortHelperPoolMember(),
                    commonEnabledStateHelperPoolMember(iControl.CommonEnabledState.STATE_ENABLED))

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

    // Disable pool member(s) in pool(s)
    public void disablePoolMember() {
        if (isPoolMemberEnabled() != NEGATIVE) {
            println "Disabling pool member(s)..."
            def pool = getConnection().getLocalLBPool()
            pool.setTimeout(timeout * 1000)
            pool.set_member_session_enabled_state(
                    poolName,
                    commonAddressPortHelperPoolMember(),
                    commonEnabledStateHelperPoolMember(iControl.CommonEnabledState.STATE_DISABLED))
            if (isPoolMemberEnabled() != NEGATIVE) {
                throw new Exception("Could not disable pool member(s)!")
            }
            else {
                println "Done."
            }
        }
        else {
            println "Pool members(s) are already disabled!"
        }
    }

    // Force pool member(s) in pool(s) online
    public void forcePoolMemberOnline() {
        if (isPoolMemberOnline() != POSITIVE) {
            println "Forcing pool member(s) online..."
            def pool = getConnection().getLocalLBPool()
            pool.setTimeout(timeout * 1000)
            pool.set_member_monitor_state(
                    poolName,
                    commonAddressPortHelperPoolMember(),
                    commonEnabledStateHelperPoolMember(iControl.CommonEnabledState.STATE_ENABLED))

            int count = 1
            while (isPoolMemberOnline() != POSITIVE) {
                if (count < timeout) {
                    sleep(1000)
                    println "[Info] Is Pool Member Online check ${count++}..."
                }
                else {
                    println "[Error] Unable to start pool member monitors after ${timeout} seconds."
                    println "[Possible Solution] Increase the Timeout property's value."
                    throw new Exception("Could not force pool member(s) online!")
                }
            }
            println "Done."
        }
        else {
            println "Pool member(s) are already online!"
        }
    }

    // Force pool member(s) in pool(s) offline
    public void forcePoolMemberOffline() {
        if (isPoolMemberOnline() != NEGATIVE) {
            println "Forcing pool member(s) offline..."
            def pool = getConnection().getLocalLBPool()
            pool.setTimeout(timeout * 1000)
            pool.set_member_monitor_state(
                    poolName,
                    commonAddressPortHelperPoolMember(),
                    commonEnabledStateHelperPoolMember(iControl.CommonEnabledState.STATE_DISABLED))
            if (isPoolMemberOnline() != NEGATIVE) {
                throw new Exception("Could not force pool member(s) offline!")
            }
            else {
                println "Done."
            }
        }
        else {
            println "Pool member(s) are already offline!"
        }
    }

    // Adds pool member(s) to pool(s)
    public void addPoolMember() {
        if (isNodeAPoolMember() != NEGATIVE) {
            println "Node(s) are already part of the ${getPoolName()} pool."
            System.exit(1)
        }
        else {
            println "Adding node(s) $nodeName:$nodePort to pool(s) ${getPoolName()}"
            def nodeStub = getConnection().getLocalLBPool()
            nodeStub.setTimeout(timeout * 1000)
            nodeStub.add_member_v2(poolName, commonAddressPortHelperPoolMember())
            if (isNodeAPoolMember() == POSITIVE) {
                println "Node(s) successfully added!"
            }
            else {
                throw new Exception("Could not add node(s) to pool(s)!")
            }
        }
    }

    // Remove pool member(s) from pool(s)
    public void removePoolMember() {
        if (isNodeAPoolMember() == POSITIVE) {
            println "Removing node(s) $nodeName:$nodePort from pool(s) ${getPoolName()}"
            def nodeStub = getConnection().getLocalLBPool()
            nodeStub.setTimeout(timeout * 1000)
            nodeStub.remove_member_v2(poolName, commonAddressPortHelperPoolMember())
            if (isNodeAPoolMember() == NEGATIVE) {
                println "Node(s) successfully removed!"
            }
            else {
                throw new Exception("Could not remove node(s) from pool(s)!")
            }
        }
        else {
            println "Node(s) were not part of the ${getPoolName()} pool."
            System.exit(1)
        }
    }

    // Checks the number of connections to a pool member
    // Used to confirm that a pool member is disabled
    public int getPoolMemberActiveConnections() {
        def Long result = -1

        try {
            // Retrieve the statistics for each pool
            for (pool in getConnection().getLocalLBPool().get_member_statistics(
                poolName,
                commonAddressPortHelperPoolMember())) {
                // Retrieve the statistics for each pool member
                for (memberStatistics in pool.statistics) {
                    // Iterate through each pool member's statistic
                    for (statistic in memberStatistics.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:$nodePort in pool ${getPoolName()}")
        }

        println "Active connection count for node(s) $nodeName:$nodePort in ${getPoolName()} is $result"
        return result
    }

    // Assists in the mass production of CommonEnabledState settings
    // for enabled and disabled pool members
    public iControl.CommonEnabledState[][] commonEnabledStateHelperPoolMember(
        iControl.CommonEnabledState state) {
        def enabledStateOutput = [][]

        for (pool in poolName) {
            enabledStateOutput.add(commonEnabledStateHelperNode(state))
        }

        return enabledStateOutput
    }

    // Assists in the mass production of CommonAddressPorts for checking
    // enabled and disabled pool members
    public iControl.CommonAddressPort[][] commonAddressPortHelperPoolMember() {
        def addressPortOutput = [][]
        def tempAddressPort
        def index

        for (pool in poolName) {
            tempAddressPort = []
            index = 0
            for (node in nodeName) {
                tempAddressPort.add(new iControl.CommonAddressPort(
                    getFolderName() + nodeName[index], nodePort[index]))
                index++
            }
            addressPortOutput.add(tempAddressPort)
        }

        return addressPortOutput
    }

    // Checks 2D enabled and disabled lists to determine if
    // all nodes are enabled, disabled, or mixed
    public int checkListsForEnabled2D( def enabledList2D, def disabledList2D) {
        def result
        def prevResult
        def curResult
        def consistent = true
        def index = 0

        for (i in enabledList2D) {
            curResult = checkListsForEnabled1D(enabledList2D[index], disabledList2D[index])
            if (index > 0 && prevResult != curResult) {
                consistent = false
            }
            prevResult = curResult
            index++
        }

        if (consistent) {
            result = curResult
        } else {
            result = POSITIVE_AND_NEGATIVE
        }

        return result
    }
}
