import iControl.Interfaces
import iControl.LocalLBLBMethod
import iControl.CommonIPPortDefinition
import iControl.LocalLBMonitorRule
import iControl.LocalLBMonitorRuleType
import iControl.LocalLBPoolMonitorAssociation
import iControl.CommonEnabledState
import iControl.LocalLBServiceDownAction

public class F5Helper {

    def props
    def serverIp
    def long serverPort
    def username
    def password
    def timeout
    def private poolName
    def nodeAddress
    def long nodePort
    def private folder
    def private interfaces
    def private version
    def serverUrlArray
    def serverHostAndPort

    public F5Helper(Map<String,String> props) {
        this.props = props
        this.serverUrlArray = props['serverURL']?.split('://') as String[]
        this.serverHostAndPort = serverUrlArray[1]?.split(':') as String[]
        this.serverIp = serverHostAndPort[0]
        if (serverHostAndPort.length > 1) {
            //port specified
            this.serverPort = Long.parseLong(serverHostAndPort[1])
        }
        else if (serverUrlArray[0].toString().length() >= 5) {
            //https default
            println("No server port specifed.  Defaulting to 443")
            this.serverPort = Long.parseLong("443")
        }
        else {
            //http default
            println("No port specifed.  Defaulting to 80")
            this.serverPort = Long.parseLong("80")
        }
        this.username = props['username']
        this.password = props['password']
        this.timeout = Integer.parseInt(props['timeout'])
        this.nodeAddress = props['nodeAddress']
        if (props['nodePort']) {
            this.nodePort = Long.parseLong(props['nodePort'])
        }
    }

    public Interfaces getConnection() {
        if (!interfaces){
            interfaces = new Interfaces()
            com.urbancode.air.XTrustProvider.install()
            println("Connecting to server $serverIp:$serverPort using timeout of $timeout second(s)")

            if (!interfaces.initialize(serverIp, serverPort, username, password)) throw new Exception("Could not connect!")
        }
        return interfaces
    }

    public int getMajorVersion() {
        if(!version) {
            def rawVersion = getConnection().getSystemSystemInfo().get_version()
            println "F5 version: $rawVersion"
            if (rawVersion && rawVersion.length() > 9) {
                rawVersion = rawVersion[0..(rawVersion.indexOf('.') - 1) ]
                version = Integer.parseInt(rawVersion.trim()[8..-1])
            }
            else {
                throw new Exception("Unknown version $rawVersion")
            }
        }

        return version
    }

    public String getFolderName() {
        if (!folder && props['partition']) {
            folder = props['partition'].trim().replaceAll('\\\\', '/')
            if (getMajorVersion() > 10) {
                if (!folder.startsWith('/')) {
                    folder = '/' + folder
                }
                while (folder[1] == '/') {
                    folder = folder[1..-1]
                }
                if (folder[-1] != '/') {
                    folder = folder + '/'
                }
            }
        }
        return folder
    }

    // for version 11 and up we need to prepend the folder path to the pool name unless the path starts with /Common
    public String getPoolName() {
        if (!poolName) {
            if (getMajorVersion() < 11 || !folder) {
                poolName = props['poolName']?.trim()
            }
            else {
                poolName = getFolderName() + props['poolName']?.trim()
            }
        }
        return poolName

    }

    public boolean isNodeEnabled() {
        def result = false

        for (member in getConnection().getLocalLBPoolMember().get_session_enabled_state(getPoolName())[0]) {
            if (member.member.address.equals(nodeAddress) && member.member.port == nodePort) {
                result = member.session_state == iControl.CommonEnabledState.STATE_ENABLED
                break
            }
        }
        println "Current state of node $nodeAddress:$nodePort in pool ${getPoolName()} is ${result?'Enabled':'Disabled'}"
        return result
    }

    public void setActiveFolder() {
        if (getFolderName()) {
            if (getMajorVersion() > 10) {
                println "Setting current folder to ${getFolderName()}"
                getConnection().getSystemSession().set_active_folder(getFolderName())
            }
            else {
                println "Setting active partition to ${getFolderName()}"
                getConnection().getManagementPartition().set_active_partition(getFolderName())
            }
        }
    }

    public int getNodeActiveConnections() {
        def Long result = -1

        // we need to set the current parition/folder if there is one or we can't get the statistics
        setActiveFolder()

        try {
            for (member in getConnection().getLocalLBPoolMember().get_statistics(
            [getPoolName()] as String[],
            [
                new iControl.CommonIPPortDefinition(nodeAddress, nodePort)] as iControl.CommonIPPortDefinition[][])[0]
            .statistics[0].statistics) {
                if (member.type == iControl.CommonStatisticType.STATISTIC_SERVER_SIDE_CURRENT_CONNECTIONS) {
                    result =  member.value.low
                    break
                }
            }
        }
        catch (Exception e) {}

        if (result < 0) {
            throw new Exception("Could not locate node $nodeAddress:$nodePort in pool ${getPoolName()}")
        }

        println "Active connection count for node $nodeAddress:$nodePort is $result"
        return result
    }

    public void disableNodeInPool() {
        if (isNodeEnabled()) {
            println "Disabling node..."
            def poolMember = getConnection().getLocalLBPoolMember()
            def pool = getConnection().getLocalLBNodeAddress()
            def nodeName = pool.get_screen_name([nodeAddress] as String[])
            poolMember.setTimeout(timeout * 1000)
            setActiveFolder()
            poolMember.set_session_enabled_state(
                    [getPoolName()] as String[],
                    [
                        new iControl.LocalLBPoolMemberMemberSessionState(
                        new iControl.CommonIPPortDefinition(nodeName[0], nodePort),
                        iControl.CommonEnabledState.STATE_DISABLED)
                    ] as iControl.LocalLBPoolMemberMemberSessionState[][])
            if(isNodeEnabled()) {
                throw new Exception("Could not disable node!")
            }
            else {
                println "Done."
            }
        }
        else {
            println "Node is already disabled!"
        }
    }

    public void enableNodeInPool() {
        if (!isNodeEnabled()) {
            println "Enabling node..."
            def poolMember = getConnection().getLocalLBPoolMember()
            def pool = getConnection().getLocalLBNodeAddress()
            def nodeName = pool.get_screen_name([nodeAddress] as String[])
            poolMember.setTimeout(timeout * 1000)
            setActiveFolder()
            poolMember.set_session_enabled_state(
                    [getPoolName()] as String[],
                    [
                        new iControl.LocalLBPoolMemberMemberSessionState(
                        new iControl.CommonIPPortDefinition(nodeName[0], nodePort),
                        iControl.CommonEnabledState.STATE_ENABLED)
                    ] as iControl.LocalLBPoolMemberMemberSessionState[][])
            if(!isNodeEnabled()) {
                throw new Exception("Could not enable node!")
            }
            else {
                println "Done."
            }
        }
        else {
            println "Node is already enabled!"
        }
    }

    public boolean isNodeInPool() {
        def result = false
        for (member in getConnection().getLocalLBPoolMember().get_session_enabled_state(getPoolName())[0]) {
            if (member.member.address.equals(nodeAddress) && member.member.port == nodePort){
                result = true
                break
            }
        }

        return result
    }

    public void addNodeToPool() {
        if (isNodeInPool()) {
            println "Node $nodeAddress:$nodePort is already part of the ${getPoolName()} pool."
        }
        else {
            println "Adding node $nodeAddress:$nodePort to pool ${getPoolName()}"
            def node = new iControl.CommonIPPortDefinition(nodeAddress, nodePort)
            def nodeArray = new iControl.CommonIPPortDefinition[1][1]
            nodeArray[0][0] = node
            getConnection().getLocalLBPool().add_member([getPoolName()] as String[], nodeArray)
            if (isNodeInPool()) {
                println "Node successfully added!"
            }
            else {
                throw new Exception("Could not add node to pool!")
            }
        }
    }

    public void removeNodeFromPool() {
        if (isNodeInPool()) {
            println "Removing node $nodeAddress:$nodePort from pool ${getPoolName()}"
            def node = new iControl.CommonIPPortDefinition(nodeAddress, nodePort)
            def nodeArray = new iControl.CommonIPPortDefinition[1][1]
            nodeArray[0][0] = node
            def poolStub = getConnection().getLocalLBPool()
            poolStub.setTimeout(timeout * 1000)
            poolStub.remove_member([getPoolName()] as String[], nodeArray)
            if (!isNodeInPool()) {
                println "Node successfully removed!"
            }
            else {
                throw new Exception("Could not remove node from pool!")
            }
        }
        else {
            println "Node $nodeAddress:$nodePort is not part of the ${getPoolName()} pool."
        }
    }

    public boolean doesPoolExist() {
        def result = false
        setActiveFolder()
        for (pool in getConnection().getLocalLBPool().get_list()) {
            if (getPoolName().equalsIgnoreCase(pool)) {
                result = true
                break
            }
        }

        if (result) {
            println "Pool ${getPoolName()} exists."
        }
        else {
            println "Pool ${getPoolName()} does not exist."
        }

        return result
    }

    public void setPoolLBMethod(String lbMethod) {
        getConnection().getLocalLBPool().set_lb_method([getPoolName()] as String[],
        [
            LocalLBLBMethod.fromString(lbMethod)] as LocalLBLBMethod[])
    }

    public void createPool(String lbMethod) {
        println "Creating ${getPoolName()} with $lbMethod load balancing"
        getConnection().getLocalLBPool().create(
                [getPoolName()] as String[],
                [
                    LocalLBLBMethod.fromString(lbMethod)] as LocalLBLBMethod[],
                new CommonIPPortDefinition[1][0])
        if (doesPoolExist()) {
            println "Pool successfully created!"
        }
        else {
            throw new Exception("Could not create pool!")
        }
    }

    public void removePoolMonitors() {
        getConnection().getLocalLBPool().remove_monitor_association([getPoolName()] as String[])
    }

    public void addPoolMonitors(String[] monitorArray) {
        def monitorRule = new LocalLBMonitorRule(
                LocalLBMonitorRuleType.MONITOR_RULE_TYPE_SINGLE,
                0,
                monitorArray)
        getConnection().getLocalLBPool().set_monitor_association(
                new LocalLBPoolMonitorAssociation(getPoolName(), monitorRule))
    }

    public void setPoolAllowSNATState(String allowSnat) {
        getConnection().getLocalLBPool().set_allow_snat_state(
                [getPoolName()] as String[], [
                    CommonEnabledState.fromString(allowSnat)] as CommonEnabledState[])

    }

    public void setPoolAllowNATState(String allowNat) {
        getConnection().getLocalLBPool().set_allow_nat_state(
                [getPoolName()] as String[], [
                    CommonEnabledState.fromString(allowNat)] as CommonEnabledState[])

    }

    public void setPoolActionOnServiceDown(String serviceDownAction) {
        getConnection().getLocalLBPool().set_action_on_service_down(
                [getPoolName()] as String[],
                [
                    LocalLBServiceDownAction.fromString(serviceDownAction)] as LocalLBServiceDownAction[])

    }

    public void setPoolSlowRampTime(String slowRampTime) {
        getConnection().getLocalLBPool().set_slow_ramp_time(
                [getPoolName()] as String[],
                [
                    Long.parseLong(slowRampTime)] as long[])
    }

    public void setPoolClientIPTOS(String clientIpTos) {
        getConnection().getLocalLBPool().set_client_ip_tos(
                [getPoolName()] as String[],
                [Long.parseLong(clientIpTos)] as long[])
    }

    public void setPoolServerIPTOS(String serverIpTos) {
        getConnection().getLocalLBPool().set_server_ip_tos(
                [getPoolName()] as String[],
                [Long.parseLong(serverIpTos)] as long[])
    }

    public void setPoolClientLinkQOS(String clientLinkQos) {
        getConnection().getLocalLBPool().set_client_link_qos(
                [getPoolName()] as String[],
                [
                    Long.parseLong(clientLinkQos)] as long[])
    }

    public void setPoolServerLinkQOS(String serverLinkQos) {
        getConnection().getLocalLBPool().set_server_link_qos(
                [getPoolName()] as String[],
                [
                    Long.parseLong(serverLinkQos)] as long[])
    }

    public void deletePool() {
        setActiveFolder()
        println "Deleting ${getPoolName()}"
        getConnection().getLocalLBPool().delete_pool(
                [getPoolName()] as String[])
        if (doesPoolExist()) {
            throw new Exception("Could not delete pool!")
        }
        else {
            println "Pool successfully deleted!"
        }
    }
}
