/**
 *  Pollster - The SmartThings Polling Daemon.
 *
 *  Pollster works behind the scenes and periodically calls 'poll' or
 *  'refresh' commands for selected devices. Devices can be arranged into
 *  three polling groups with configurable polling intervals down to 1 minute.
 *
 *  Please visit [https://github.com/statusbits/smartthings] for more
 *  information. 
 *
 *  --------------------------------------------------------------------------
 *  Copyright © 2014 Statusbits.com
 *
 *  Licensed 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.
 *  --------------------------------------------------------------------------
 *
 *  Version 1.5.0 (02/08/2016)
 */

definition(
    name: "Pollster",
    namespace: "statusbits",
    author: "geko@statusbits.com",
    description: "Poll or refresh device status periodically.",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")

preferences {
    section("About") {
        def hrefAbout = [
            url:        "http://statusbits.github.io/smartthings/",
            style:      "embedded",
            title:      "Tap for more information...",
            description:"http://statusbits.github.io/smartthings/",
            required:   false
        ]

        paragraph about()
        //href hrefAbout
    }

    (1..4).each() { n ->
        section("Scheduled Polling Group ${n}") {
            input "group_${n}", "capability.polling", title:"Select devices to be polled", multiple:true, required:false
            input "refresh_${n}", "capability.refresh", title:"Select devices to be refreshed", multiple:true, required:false
            input "interval_${n}", "number", title:"Set polling interval (in minutes)", defaultValue:5
        }
    }

    section("REST API Polling Group") {
        paragraph "Poll these devices via REST API endpoint."
        input "enableRestApi", "bool", title:"Enable REST endpoint", defaultValue:false
        input "restPoll", "capability.polling", title:"Select devices to be polled", multiple:true, required:false
        input "restRefresh", "capability.refresh", title:"Select devices to be refreshed", multiple:true, required:false
    }
}

mappings {
    path("/poll") {
        action: [ GET: "apiPoll" ]
    }
}

def installed() {
    initialize()
}

def updated() {
    initialize()
}

def onAppTouch(event) {
    LOG("onAppTouch(${event.value})")

    watchdog()
    pollingTask1()
    pollingTask2()
    pollingTask3()
    pollingTask4()

    if (settings.restPoll) {
        settings.restPoll*.poll()
    }

    if (settings.restRefresh) {
        settings.restRefresh*.refresh()
    }
}

def onLocation(event) {
    LOG("onLocation(${event.value})")

    watchdog()
}

def pollingTask1() {
    LOG("pollingTask1()")

    state.trun1 = now()

    if (settings.group_1) {
        settings.group_1*.poll()
    }

    if (settings.refresh_1) {
        settings.refresh_1*.refresh()
    }
}

def pollingTask2() {
    LOG("pollingTask2()")

    state.trun2 = now()

    if (settings.group_2) {
        settings.group_2*.poll()
    }

    if (settings.refresh_2) {
        settings.refresh_2*.refresh()
    }
}

def pollingTask3() {
    LOG("pollingTask3()")

    state.trun3 = now()

    if (settings.group_3) {
        settings.group_3*.poll()
    }

    if (settings.refresh_3) {
        settings.refresh_3*.refresh()
    }
}

def pollingTask4() {
    LOG("pollingTask4()")

    state.trun4 = now()

    if (settings.group_4) {
        settings.group_4*.poll()
    }

    if (settings.refresh_4) {
        settings.refresh_4*.refresh()
    }
}

// Handle '.../poll' REST endpoint
def apiPoll() {
    LOG("apiPoll()")

    if (settings.restPoll) {
        settings.restPoll*.poll()
    }

    if (settings.restRefresh) {
        settings.restRefresh*.refresh()
    }

    watchdog()

    return [status:"ok"]
}

def watchdog() {
    LOG("watchdog()")

    (1..4).each() { n ->
        def interval = settings."interval_${n}".toInteger()
        def trun = state."trun${n}"

        if (interval && trun && ((now() - trun) > ((interval + 10) * 10000))) {
            log.warn "Polling task #${n} stalled. Restarting..."
            restart()
            return
        }
    }
}

private def initialize() {
    log.info "Pollster. Version ${version()}. ${copyright()}"
    LOG("initialize() with settings: ${settings}")

    if (settings.enableRestApi && state.accessToken == null) {
        initAccessToken()
    }

    state.trun1 = 0
    state.trun2 = 0
    state.trun3 = 0
    state.trun4 = 0

    Random rand = new Random(now())
    def numTasks = 0
    (1..4).each() { n ->
        def minutes = settings."interval_${n}".toInteger()
        def seconds = rand.nextInt(10)
        def size1 = settings["group_${n}"]?.size() ?: 0
        def size2 = settings["refresh_${n}"]?.size() ?: 0

        safeUnschedule("pollingTask${n}")

        if (minutes > 0 && (size1 + size2) > 0) {
            LOG("Scheduling polling task ${n} to run every ${minutes} minutes.")
            def sched = "${seconds} 0/${minutes} * * * ?"
            schedule(sched, "pollingTask${n}")
            numTasks++
        }
    }

    if (numTasks) {
        subscribe(app, onAppTouch)
        subscribe(location, onLocation)
        subscribe(location, "position", onLocation)
        subscribe(location, "sunrise", onLocation)
        subscribe(location, "sunset", onLocation)
    }

    LOG("state: ${state}")
}

private def initAccessToken() {
    LOG("initAccessToken()")

    try {
        def token = createAccessToken()
        log.info "Created access token: ${token})"
        state.url = "https://graph.api.smartthings.com/api/smartapps/installations/${app.id}/poll?access_token=${token}"
    } catch (e) {
        log.error "Cannot create access token. ${e}"
        state.url = null
        return false
    }

    return true
}

private def safeUnschedule() {
    try {
        unschedule()
    }

    catch(e) {
        log.error ${e}
    }
}

private def safeUnschedule(handler) {
    try {
        unschedule(handler)
    }

    catch(e) {
        log.error ${e}
    }
}

private def restart() {
    //sendNotification("Pollster stalled. Restarting...")
    updated()
}

private def about() {
    def text =
        "Version ${version()}\n${copyright()}\n\n" +
        "You can contribute to the development of this app by making a " +
        "PayPal donation to geko@statusbits.com. We appreciate your support."
}

private def version() {
    return "Version 1.5.0"
}

private def copyright() {
    return "Copyright © 2014 Statusbits.com"
}

private def LOG(message) {
    log.trace message
}
