# coding: utf-8
# Copyright 2016 Chris Kirkos
#
# 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.
from __future__ import (absolute_import, division, print_function, unicode_literals)

import time

import six

# NOTE: Single slashes have bad edge cases in the parser.
# We will strip them out.
single_slash = '\\'
double_slash = r'\\'
quote = '"'
escaped_quote = r'\"'
newline = '\n'
escaped_newline = r'\n'
equals = '='
escaped_equals = r'\='
comma = ','
escaped_comma = r'\,'
space = ' '
escaped_space = r'\ '
underscore = '_'

def now_nano():
    return int(time.time() * 1e9)

def format_measurement_name(s):
    s = s.replace(single_slash, underscore)
    s = s.replace(comma, escaped_comma)
    s = s.replace(space, escaped_space)
    return s

def format_tag(s):
    s = s.replace(single_slash, underscore)
    s = s.replace(comma, escaped_comma)
    s = s.replace(equals, escaped_equals)
    s = s.replace(space, escaped_space)
    return s

format_field_name = format_tag

def format_field_value(v):
    if isinstance(v, six.string_types):
        v = v.replace(quote, escaped_quote)
        return '"%s"' % v
    # before the int check because bools are ints and ints are not bools
    elif isinstance(v, bool):
        return v and 'T' or 'F'
    elif isinstance(v, six.integer_types):
        return '%di' % v
    elif isinstance(v, float):
        return str(v)
    else:
        raise Exception("Invalid field. value: %s type: %s" % (v, type(v)))

def format_tags(tags):
    return ",".join(("%s=%s" % (format_tag(k), format_tag(v)) for (k, v) in sorted(tags.items())))

def format_fields(fields):
    return ",".join(("%s=%s" % (format_field_name(k), format_field_value(v)) for (k, v) in sorted(fields.items())))

class Measurement(object):
    """
    Field Values:
        Floats - by default, InfluxDB assumes all numerical field values are floats.
        Integers - append an i to the field value to tell InfluxDB to store the number as an integer.
        Strings - double quote string field values
        Booleans - specify TRUE with t, T, true, True, or TRUE. Specify FALSE with f, F, false, False, or FALSE.

    For tag keys, tag values, and field keys always use a backslash character \ to escape:
    commas ,
    equal signs =
    spaces

    For measurements always use a backslash character \ to escape:
    commas ,
    spaces

    For string field values use a backslash character \ to escape:
    double quotes "

    For best performance you should sort tags by key before sending them to the database.

    ref:  https://docs.influxdata.com/influxdb/v1.0/write_protocols/line_protocol_tutorial/
    """
    __slots__ = ['name', 'tags', 'fields', 'time']

    def __init__(self, name, tags=None, fields=None, time=None):
        """
        name: String. Measurement name.
        tags: Dict. String->String
        fields: Dict. String->{Int,Float,String,Bool}
        time: Int. Nanoseconds since epoch.
        """
        self.name = name
        self.tags = tags or {}
        self.fields = fields or {}
        self.time = time or now_nano()

    def __repr__(self):
        return 'Measurement(name="%s", tags=%s, fields=%s, time=%s)' % \
            (self.name, self.tags, self.fields, self.time)

    def __unicode__(self):
        name = format_measurement_name(self.name)
        tags = format_tags(self.tags)
        fields = format_fields(self.fields)
        time = "%s" % self.time

        element0 = ','.join((name, tags)) if tags else name
        element1 = fields
        element2 = time

        return " ".join((element0, element1, element2))

    def __str__(self):
        return self.__unicode__()

    def __bytes__(self):
        return self.__unicode__().encode('utf-8')
