#!/usr/bin/python3

try:  # Python2
    import queue
    from urllib2 import *
except Exception:  # Python3
    import queue as Queue
    from urllib.request import *
    from urllib.error import *
import threading
import sys
from time import sleep
from pprint import pprint

"""
GAObjClass is a class destined to send Google Analytics Information.

cid - unique number per user.
command - the Event Category rubric appears on site. type: TEXT
action - the Event Action rubric appears on site - type: TEXT
label - the Event Label rubric - type: TEXT
value - the event value metric - type: INTEGER

QUOTAS:
1 single payload - up to 8192Bytes
batched:
A maximum of 20 hits can be specified per request.
The total size of all hit payloads cannot be greater than 16K bytes.
No single hit payload can be greater than 8K bytes.
"""
url_single = 'https://www.google-analytics.com/collect'  # sending single event
url_batched = 'https://www.google-analytics.com/batch'  # sending batched events
url_debug = 'https://www.google-analytics.com/debug/collect'  # verifying hit is valid
# url_conn = 'http://172.217.2.196'  # testing internet connection to this address(google-analytics server)
url_conn = 'http://google.com'


# ..................................................................class GA_ObjClass................................................................
class GA_ObjClass:
    def __init__(self, cid, trackerID, appName, appVer):
        self.cid = cid
        self.trackerID = trackerID
        self.appName = appName
        self.appVer = appVer
        self.payload = ''
        self.payload = GA_ObjClass.generate_payload(self)
        self.size = sys.getsizeof(self.payload)

    def generate_payload(self):
        self.payload += 'v=1&t=event&tid=' + str(self.trackerID)
        self.payload += '&cid=' + str(self.cid)
        self.payload += '&an=' + str(self.appName)
        self.payload += '&av=' + str(self.appVer)
        return self.payload


# ..................................................................class GA_EVENT_ObjClass................................................................
class GA_EVENT_ObjClass(GA_ObjClass):
    def __init__(self, cid, trackerID, command, action, label, value, appName, appVer):
        GA_ObjClass.__init__(self, cid, trackerID, appName, appVer)
        self.command = command
        self.action = action
        self.label = label
        self.value = value
        self.payload = self.generate_payload()
        self.size = sys.getsizeof(self.payload)

    def generate_payload(self):
        self.payload += '&ec=' + str(self.command)
        self.payload += '&ea=' + str(self.action)
        self.payload += '&el=' + str(self.label)
        self.payload += '&ev=' + str(self.value)
        return self.payload


# ..................................................................class GA_EXCEPTION_ObjClass................................................................
# ExceptionFatal - BOOLEAN
class GA_EXCEPTION_ObjClass(GA_ObjClass):
    def __init__(self, cid, trackerID, ExceptionName, ExceptionFatal, appName, appVer):
        GA_ObjClass.__init__(self, cid, trackerID, appName, appVer)
        self.ExceptionName = ExceptionName
        self.ExceptionFatal = ExceptionFatal
        self.payload = self.generate_payload()

    def generate_payload(self):
        self.payload += '&exd=' + str(self.ExceptionName)
        self.payload += '&exf=' + str(self.ExceptionFatal)
        return self.payload


# ..................................................................class GA_TESTING_ObjClass................................................................
class GA_TESTING_ObjClass(GA_ObjClass):
    def __init__(self, cid, trackerID, TRexMode, TestName, SetupName, appName, ActionNumber, appVer, TestType, Mppspc,
                 GoldenMin, GoldenMax):
        GA_ObjClass.__init__(self, cid, trackerID, appName, appVer)
        self.ActionNumber = ActionNumber
        self.TRexMode = TRexMode
        self.TestName = TestName
        self.SetupName = SetupName
        self.TestType = TestType
        self.Mppspc = Mppspc
        self.GoldenMin = GoldenMin
        self.GoldenMax = GoldenMax
        self.payload = self.generate_payload()
        self.size = sys.getsizeof(self.payload)

    def generate_payload(self):
        self.payload += '&ec=TRexTests'
        self.payload += '&ea=' + str(self.ActionNumber)
        self.payload += '&cd2=' + str(self.TRexMode)
        self.payload += '&cd1=' + str(self.TestName)
        self.payload += '&cd3=' + str(self.SetupName)
        self.payload += '&cd4=' + str(self.TestType)
        self.payload += '&cm1=' + str(self.Mppspc)
        self.payload += '&cm2=' + str(self.GoldenMin)
        self.payload += '&cm3=' + str(self.GoldenMax)
        return self.payload

        # .....................................................................class ga_Thread.................................................................


"""

Google analytics thread manager:

will report and empty queue of google analytics items to GA server, every Timeout(parameter given on initialization)
will perform connectivity check every timeout*10 seconds

"""


class ga_Thread(threading.Thread):
    def __init__(self, threadID, gManager):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.gManager = gManager

    def run(self):
        keepAliveCounter = 0
        # sys.stdout.write('thread started \n')
        # sys.stdout.flush()
        while True:
            if(keepAliveCounter == 10):
                keepAliveCounter = 0
                if(self.gManager.internet_on() == True):
                    self.gManager.connectedToInternet = 1
                else:
                    self.gManager.connectedToInternet = 0
            sleep(self.gManager.Timeout)
            keepAliveCounter += 1
            if not self.gManager.GA_q.empty():
                self.gManager.threadLock.acquire(1)
                #               sys.stdout.write('lock acquired: reporting to GA \n')
                #               sys.stdout.flush()
                if(self.gManager.connectedToInternet == 1):
                    self.gManager.emptyAndReportQ()
                self.gManager.threadLock.release()


# sys.stdout.write('finished \n')
#               sys.stdout.flush()
# .....................................................................class GAmanager.................................................................
"""

Google ID - specify tracker property, example: UA-75220362-2(when the suffix '2' specifies the analytics property profile)

UserID - unique userID, this will differ between users on GA

appName - s string to determine app name

appVer - a string to determine app version

QueueSize - the size of the queue that holds reported items. once the Queue is full:
            on blocking mode:
                will block program until next submission to GA server, which will make new space
            on non-blocking mode:
                will drop new requests

Timout - the timeout the queue uses between data transmissions. Timeout should be shorter than the time it takes to generate 20 events. MIN VALUE = 11 seconds

User Permission - the user must accept data transmission, use this flag as 1/0 flag, when UserPermission=1 allows data collection
 
BlockingMode - set to 1 if you wish every Google Analytic Object will be submitted and processed, with no drops allowed. 
               this will block the running of the program until every item is processed

*** Restriction - Google's restriction for amount of packages being sent per session per second is: 1 event per second, per session. session length is 30min ***
"""


class GAmanager:
    def __init__(self, GoogleID, UserID, appName, appVer, QueueSize, Timeout, UserPermission, BlockingMode):
        self.UserID = UserID
        self.GoogleID = GoogleID
        self.QueueSize = QueueSize
        self.Timeout = Timeout
        self.appName = appName
        self.appVer = appVer
        self.UserPermission = UserPermission
        self.GA_q = queue.Queue(QueueSize)
        self.thread = ga_Thread(UserID, self)
        self.threadLock = threading.Lock()
        self.BlockingMode = BlockingMode
        self.connectedToInternet = 0
        if(self.internet_on() == True):
            #           sys.stdout.write('internet connection active \n')
            #           sys.stdout.flush()
            self.connectedToInternet = 1
        else:
            self.connectedToInternet = 0

    def gaAddAction(self, Event, action, label, value):
        self.gaAddObject(
            GA_EVENT_ObjClass(self.UserID, self.GoogleID, Event, action, label, value, self.appName, self.appVer))

    def gaAddException(self, ExceptionName, ExceptionFatal):
        self.gaAddObject(
            GA_EXCEPTION_ObjClass(self.UserID, self.GoogleID, ExceptionName, ExceptionFatal, self.appName, self.appVer))

    def gaAddObject(self, Object):
        if(self.BlockingMode == 1):
            while(self.GA_q.full()):
                sleep(self.Timeout)
            #               sys.stdout.write('blocking mode=1 \n queue full - sleeping for timeout \n') # within Timout, the thread will empty part of the queue
            #               sys.stdout.flush()
        lockState = self.threadLock.acquire(self.BlockingMode)
        if(lockState == 1):
            #           sys.stdout.write('got lock, adding item \n')
            #           sys.stdout.flush()
            try:
                self.GA_q.put_nowait(Object)
            #               sys.stdout.write('got lock, item added \n')
            #               sys.stdout.flush()
            except queue.Full:
                #               sys.stdout.write('Queue full \n')
                #               sys.stdout.flush()
                pass
            self.threadLock.release()

    def emptyQueueToList(self, obj_list):
        items = 0
        while((not self.GA_q.empty()) and(items < 20)):
            obj_list.append(self.GA_q.get_nowait().payload)
            items += 1
        #           print items
        return obj_list

    def reportBatched(self, batched):
        req = Request(url_batched, data=batched.encode('ascii'))
        urlopen(req)

    #       pprint(r.json())

    def emptyAndReportQ(self):
        obj_list = []
        obj_list = self.emptyQueueToList(obj_list)
        if(len(obj_list) == 0):
            return
        batched = '\n'.join(obj_list)
        #       print sys.getsizeof(batched)
        #       print batched # - for debug
        self.reportBatched(batched)

    def printSelf(self):
        print('remaining in queue:')
        while not self.GA_q.empty():
            obj = self.GA_q.get_nowait()
            print((obj.payload))

    def internet_on(self):
        try:
            urlopen(url_conn, timeout=10)
            return True
        except URLError as err:
            pass
        return False

    def activate(self):
        if(self.UserPermission == 1):
            self.thread.start()


# .....................................................................class GAmanager_Regression.................................................................
"""
            *-*-*-*-Google Analytics Regression Manager-*-*-*-*
            attributes:
GoogleID - the tracker ID that Google uses in order to track the activity of a property. for regression use: 'UA-75220362-4'
AnalyticsUserID - text value  - used by Google to differ between 2 users sending data.(will not be presented on reports). use only as a way to differ between different users
TRexMode - text - will be presented on analysis. put here TRexMode
appName - text - will be presented on analysis. put here appName as string describing app name
appVer - text - will be presented on analysis. put here the appVer
QueueSize - integer - determines the queue size. the queue will hold pending request before submission. RECOMMENDED VALUE: 20
Timeout - integer(seconds) - the timeout in seconds between automated reports when activating reporting thread
UserPermission - boolean(1/0) - required in order to send packets, should be 1.
BlockingMode - boolean(1/0) - required when each tracked event is critical and program should halt until the event is reported
SetupName - text - will be presented on analysis. put here setup name as string.
"""


class GAmanager_Regression(GAmanager):
    def __init__(self, GoogleID, AnalyticsUserID, appName, appVer,
                 QueueSize, Timeout, UserPermission, BlockingMode):
        GAmanager.__init__(self, GoogleID, AnalyticsUserID, appName, appVer,
                           QueueSize, Timeout, UserPermission, BlockingMode)
        self.GoogleID = GoogleID
        self.AnalyticsUserID = AnalyticsUserID

    def gaAddTestQuery(self, TestName, TRexMode, SetupName, ActionNumber, TestType, Mppspc, GoldenMin, GoldenMax):
        self.gaAddObject(
            GA_TESTING_ObjClass(self.AnalyticsUserID, self.GoogleID, TRexMode, TestName, SetupName, self.appName,
                                ActionNumber, self.appVer, TestType, Mppspc, GoldenMin, GoldenMax))
