# $Id: cosm.py 638 2013-09-10 19:49:45Z mwall $
# Copyright 2013 Matthew Wall
"""weewx module that uploads data to cosm

   Put this file into the weewx user directory, then add this to weewx.conf:

    [StdRESTful]
        ...
        [[COSM]]
            token = TOKEN
            feed = FEED_ID
            station = station_name
            driver = user.cosm.COSM
"""

import httplib
import socket
import syslog
import time
import urllib
import urllib2

import weewx
import weewx.restful

try:
    import cjson as json
    # XXX: maintain compatibility w/ json module
    setattr(json, 'dumps', json.encode)
    setattr(json, 'loads', json.decode)
except Exception, e:
    try:
        import simplejson as json
    except Exception, e:
        import json

def logdbg(msg):
    syslog.syslog(syslog.LOG_DEBUG, 'cosm: %s' % msg)

def loginf(msg):
    syslog.syslog(syslog.LOG_INFO, 'cosm: %s' % msg)

def logerr(msg):
    syslog.syslog(syslog.LOG_ERR, 'cosm: %s' % msg)

class COSM(weewx.restful.REST):
    """Upload to a cosm server.
    
    For details about cosm, see http://cosm.com
    """

    # Types and formats of the data to be published:
    _formats = {'barometer'   : 'barometer %.3f',        # inHg
                'outTemp'     : 'temperature_out %.1f',  # F
                'outHumidity' : 'humidity_out %03.0f',   # %
#                'inTemp'      : 'temperature_in %.1f',  # F
#                'inHumidity'  : 'humidity_in %03.0f',   # %
                'windSpeed'   : 'windSpeed %.2f',        # mph
                'windDir'     : 'windDir %03.0f',        # compass degree
                'windGust'    : 'windGust %.2f',         # mph
                'dewpoint'    : 'dewpoint %.1f',         # F
                'rain24'      : 'rain24 %.2f',           # in
                'hourRain'    : 'hourRain %.2f',         # in
                'dayRain'     : 'dayRain %.2f',          # in
                'radiation'   : 'radiation %.2f',        # W/m^2
                'UV'          : 'UV %.2f'}               # number

    def __init__(self, site, **kwargs):
        """Initialize for a given upload site.

        token: unique token
        [Required]

        feed: the cosm feed
        [Required]

        station: station identifier
        [Optional. Default is None]

        url: URL of the server
        [Optional. Default is the cosm site]

        max_tries: maximum number of tries before giving up
        [Optional. Default is 3]

        timeout: timeout in seconds
        [Optional. Default is 60]

        skip_upload: debugging option to display data but do not upload
        [Optional. Default is False]
        """
        
        self.site       = site
        self.feed       = kwargs['feed']
        self.station    = kwargs.get('station', None)
        self.server_url = kwargs.get('url', 'http://api.cosm.com/v2/feeds')
        self.token      = kwargs['token']
        self.max_tries  = int(kwargs.get('max_tries', 3))
        self.timeout    = int(kwargs.get('timeout', 60))
        self.skip_upload= kwargs.get('skip_upload', False)

    def postData(self, archive, time_ts):
        """Post using the COSM protocol.

        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time.
        """

        _url = self.getURL()
        _data = self.getData(archive, time_ts)
        if _data is None:
            logdbg('nothing to upload')
            raise weewx.restful.SkippedPost, 'nothing to upload'
        logdbg('url: %s' % _url)
        logdbg('data: %s' % _data)
        if self.skip_upload:
            raise weewx.restful.SkippedPost, 'skip_upload=True'

        for _count in range(self.max_tries):
            # Now use an HTTP PUT to post the data.
            try:
                _request = urllib2.Request(_url)
                _request.add_header("User-Agent",
                                    "weewx/%s" % weewx.__version__)
                _request.add_header("X-PachubeApiKey", self.token)
                _request.get_method = lambda: 'PUT'
                _response = urllib2.urlopen(_request, _data, self.timeout)
            except (urllib2.URLError, socket.error, httplib.BadStatusLine), e:
                # Unsuccessful. Log it and go around again for another try
                logerr('Failed upload attempt %d to %s: %s' %
                       (_count+1, self.site, e,))
            else:
                # No exception thrown, but must still check for errors.
                logdbg('response: %s' % '\n'.join(_response))
                for line in _response:
                    if line != '':
                        logerr('Failed upload to %s: %s' %
                               (self.site, '\n'.join(_response)))
                        raise FailedPost, line
                return
        else:
            # This is executed only if the loop terminates normally, meaning
            # the upload failed max_tries times. Log it.
            logerr('Failed upload to %s' % self.site)
            raise IOError, 'Failed upload to %s after %d tries' % (
                self.site, self.max_tries)

    def getURL(self):
        return '%s/%s' % (self.server_url, self.feed)
        
    def getData(self, archive, time_ts):
        """Format a single archive record for posting to COSM as JSON data.
        
        archive: An instance of weewx.archive.Archive
        
        time_ts: The record desired as a unix epoch time.
        """

        record = self.extractRecordFrom(archive, time_ts)
        station = urllib.quote_plus(self.station) \
            if self.station is not None else None
        tstr = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(time_ts))
        streams = {}
        for k in COSM._formats:
            v = record[k]
            if v is not None:
                dskey = '%s_%s' % (station, k) if station is not None else k
                if not dskey in streams:
                    streams[dskey] = {'id':dskey, 'datapoints':[]}
                dp = {'at':tstr, 'value':v}
                streams[dskey]['datapoints'].append(dp)
        if len(streams.keys()) == 0:
            return None
        data = {
            'version':'1.0.0',
            'datastreams':[]
            }
        for k in streams.keys():
            data['datastreams'].append(streams[k])
        data = json.dumps(data)
        return data
