text: # -*- coding: utf-8 -*-
  
  from datetime import datetime
  from operator import itemgetter
  from pyga import utils
  from pyga import exceptions
  try:
      from urlparse import urlparse
      from urllib import unquote_plus
  except ImportError as e:
      from urllib.parse import urlparse
      from urllib.parse import unquote_plus
  
  
  __author__ = "Arun KR (kra3) <the1.arun@gmail.com>"
  __license__ = "Simplified BSD"
  
  
  class Campaign(object):
      '''
      A representation of Campaign
  
      Properties:
      _type -- See TYPE_* constants, will be mapped to "__utmz" parameter.
      creation_time --  Time of the creation of this campaign, will be mapped to "__utmz" parameter.
      response_count -- Response Count, will be mapped to "__utmz" parameter.
          Is also used to determine whether the campaign is new or repeated,
          which will be mapped to "utmcn" and "utmcr" parameters.
      id -- Campaign ID, a.k.a. "utm_id" query parameter for ga.js
             Will be mapped to "__utmz" parameter.
      source -- Source, a.k.a. "utm_source" query parameter for ga.js.
                Will be mapped to "utmcsr" key in "__utmz" parameter.
      g_click_id -- Google AdWords Click ID, a.k.a. "gclid" query parameter for ga.js.
                    Will be mapped to "utmgclid" key in "__utmz" parameter.
      d_click_id -- DoubleClick (?) Click ID. Will be mapped to "utmdclid" key in "__utmz" parameter.
      name --  Name, a.k.a. "utm_campaign" query parameter for ga.js.
               Will be mapped to "utmccn" key in "__utmz" parameter.
      medium -- Medium, a.k.a. "utm_medium" query parameter for ga.js.
                Will be mapped to "utmcmd" key in "__utmz" parameter.
      term -- Terms/Keywords, a.k.a. "utm_term" query parameter for ga.js.
              Will be mapped to "utmctr" key in "__utmz" parameter.
      content -- Ad Content Description, a.k.a. "utm_content" query parameter for ga.js.
                 Will be mapped to "utmcct" key in "__utmz" parameter.
  
      '''
  
      TYPE_DIRECT = 'direct'
      TYPE_ORGANIC = 'organic'
      TYPE_REFERRAL = 'referral'
  
      CAMPAIGN_DELIMITER = '|'
  
      UTMZ_PARAM_MAP = {
          'utmcid': 'id',
          'utmcsr': 'source',
          'utmgclid': 'g_click_id',
          'utmdclid': 'd_click_id',
          'utmccn': 'name',
          'utmcmd': 'medium',
          'utmctr': 'term',
          'utmcct': 'content',
      }
  
      def __init__(self, typ):
          self._type = None
          self.creation_time = None
          self.response_count = 0
          self.id = None
          self.source = None
          self.g_click_id = None
          self.d_click_id = None
          self.name = None
          self.medium = None
          self.term = None
          self.content = None
  
          if typ:
              if typ not in ('direct', 'organic', 'referral'):
                  raise ValueError('Campaign type has to be one of the Campaign::TYPE_* constant values.')
  
              self._type = typ
              if typ == Campaign.TYPE_DIRECT:
                  self.name = '(direct)'
                  self.source = '(direct)'
                  self.medium = '(none)'
              elif typ == Campaign.TYPE_REFERRAL:
                  self.name = '(referral)'
                  self.medium = 'referral'
              elif typ == Campaign.TYPE_ORGANIC:
                  self.name = '(organic)'
                  self.medium = 'organic'
              else:
                  self._type = None
  
          self.creation_time = datetime.utcnow()
  
      def validate(self):
          if not self.source:
              raise exceptions.ValidationError('Campaigns need to have at least the "source" attribute defined.')
  
      @staticmethod
      def create_from_referrer(url):
          obj = Campaign(Campaign.TYPE_REFERRAL)
          parse_rslt = urlparse(url)
          obj.source = parse_rslt.netloc
          obj.content = parse_rslt.path
          return obj
  
      def extract_from_utmz(self, utmz):
          parts = utmz.split('.', 4)
  
          if len(parts) != 5:
              raise ValueError('The given "__utmz" cookie value is invalid.')
  
          self.creation_time = utils.convert_ga_timestamp(parts[1])
          self.response_count = int(parts[3])
          params = parts[4].split(Campaign.CAMPAIGN_DELIMITER)
  
          for param in params:
              key, val = param.split('=')
  
              try:
                  setattr(self, self.UTMZ_PARAM_MAP[key], unquote_plus(val))
              except KeyError:
                  continue
  
          return self
  
  
  class CustomVariable(object):
      '''
      Represent a Custom Variable
  
      Properties:
      index -- Is the slot, you have 5 slots
      name -- Name given to custom variable
      value -- Value for the variable
      scope -- Scope can be any one of 1, 2 or 3.
  
      WATCH OUT: It's a known issue that GA will not decode URL-encoded
      characters in custom variable names and values properly, so spaces
      will show up as "%20" in the interface etc. (applicable to name & value)
      http://www.google.com/support/forum/p/Google%20Analytics/thread?tid=2cdb3ec0be32e078
  
      '''
  
      SCOPE_VISITOR = 1
      SCOPE_SESSION = 2
      SCOPE_PAGE = 3
  
      def __init__(self, index=None, name=None, value=None, scope=3):
          self.index = index
          self.name = name
          self.value = value
          self.scope = CustomVariable.SCOPE_PAGE
          if scope:
              self.scope = scope
  
      def __setattr__(self, name, value):
          if name == 'scope':
              if value and value not in range(1, 4):
                  raise ValueError('Custom Variable scope has to be one of the 1,2 or 3')
  
          if name == 'index':
              # Custom Variables are limited to five slots officially, but there seems to be a
              # trick to allow for more of them which we could investigate at a later time (see
              # http://analyticsimpact.com/2010/05/24/get-more-than-5-custom-variables-in-google-analytics/
              if value and (value < 0 or value > 5):
                  raise ValueError('Custom Variable index has to be between 1 and 5.')
  
          object.__setattr__(self, name, value)
  
      def validate(self):
          '''
          According to the GA documentation, there is a limit to the combined size of
          name and value of 64 bytes after URL encoding,
          see http://code.google.com/apis/analytics/docs/tracking/gaTrackingCustomVariables.html#varTypes
          and http://xahlee.org/js/google_analytics_tracker_2010-07-01_expanded.js line 563
          This limit was increased to 128 bytes BEFORE encoding with the 2012-01 release of ga.js however,
          see http://code.google.com/apis/analytics/community/gajs_changelog.html
          '''
          if len('%s%s' % (self.name, self.value)) > 128:
              raise exceptions.ValidationError('Custom Variable combined name and value length must not be larger than 128 bytes.')
  
  
  class Event(object):
      '''
      Represents an Event
      https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide
  
      Properties:
      category -- The general event category
      action -- The action for the event
      label -- An optional descriptor for the event
      value -- An optional value associated with the event. You can see your
               event values in the Overview, Categories, and Actions reports,
               where they are listed by event or aggregated across events,
               depending upon your report view.
      noninteraction -- By default, event hits will impact a visitor's bounce rate.
                        By setting this parameter to true, this event hit
                        will not be used in bounce rate calculations.
                        (default False)
      '''
  
      def __init__(self, category=None, action=None, label=None, value=None, noninteraction=False):
          self.category = category
          self.action = action
          self.label = label
          self.value = value
          self.noninteraction = bool(noninteraction)
  
          if self.noninteraction and not self.value:
              self.value = 0
  
      def validate(self):
          if not(self.category and self.action):
              raise exceptions.ValidationError('Events, at least need to have a category and action defined.')
  
  
  class Item(object):
      '''
      Represents an Item in Transaction
  
      Properties:
      order_id -- Order ID, will be mapped to "utmtid" parameter
      sku -- Product Code. This is the sku code for a given product, will be mapped to "utmipc" parameter
      name -- Product Name, will be mapped to "utmipn" parameter
      variation -- Variations on an item, will be mapped to "utmiva" parameter
      price -- Unit Price. Value is set to numbers only, will be mapped to "utmipr" parameter
      quantity -- Unit Quantity, will be mapped to "utmiqt" parameter
  
      '''
  
      def __init__(self):
          self.order_id = None
          self.sku = None
          self.name = None
          self.variation = None
          self.price = None
          self.quantity = 1
  
      def validate(self):
          if not self.sku:
              raise exceptions.ValidationError('sku/product is a required parameter')
  
  
  class Page(object):
      '''
      Contains all parameters needed for tracking a page
  
      Properties:
      path -- Page request URI, will be mapped to "utmp" parameter
      title -- Page title, will be mapped to "utmdt" parameter
      charset -- Charset encoding, will be mapped to "utmcs" parameter
      referrer -- Referer URL, will be mapped to "utmr" parameter
      load_time -- Page load time in milliseconds, will be encoded into "utme" parameter.
  
      '''
      REFERRER_INTERNAL = '0'
  
      def __init__(self, path):
          self.path = None
          self.title = None
          self.charset = None
          self.referrer = None
          self.load_time = None
  
          if path:
              self.path = path
  
      def __setattr__(self, name, value):
          if name == 'path':
              if value and value != '':
                  if value[0] != '/':
                      raise ValueError('The page path should always start with a slash ("/").')
          elif name == 'load_time':
              if value and not isinstance(value, int):
                  raise ValueError('Page load time must be specified in integer milliseconds.')
  
          object.__setattr__(self, name, value)
  
  
  class Session(object):
      '''
      You should serialize this object and store it in the user session to keep it
      persistent between requests (similar to the "__umtb" cookie of the GA Javascript client).
  
      Properties:
      session_id -- A unique per-session ID, will be mapped to "utmhid" parameter
      track_count -- The amount of pageviews that were tracked within this session so far,
                     will be part of the "__utmb" cookie parameter.
                     Will get incremented automatically upon each request
      start_time -- Timestamp of the start of this new session, will be part of the "__utmb" cookie parameter
  
      '''
      def __init__(self):
          self.session_id = utils.get_32bit_random_num()
          self.track_count = 0
          self.start_time = datetime.utcnow()
  
      @staticmethod
      def generate_session_id():
          return utils.get_32bit_random_num()
  
      def extract_from_utmb(self, utmb):
          '''
          Will extract information for the "trackCount" and "startTime"
          properties from the given "__utmb" cookie value.
          '''
          parts = utmb.split('.')
          if len(parts) != 4:
              raise ValueError('The given "__utmb" cookie value is invalid.')
  
          self.track_count = int(parts[1])
          self.start_time = utils.convert_ga_timestamp(parts[3])
  
          return self
  
  
  class SocialInteraction(object):
      '''
  
      Properties:
      action -- Required. A string representing the social action being tracked,
                will be mapped to "utmsa" parameter
      network -- Required. A string representing the social network being tracked,
                 will be mapped to "utmsn" parameter
      target -- Optional. A string representing the URL (or resource) which receives the action.
  
      '''
  
      def __init__(self, action=None, network=None, target=None):
          self.action = action
          self.network = network
          self.target = target
  
      def validate(self):
          if not(self.action and self.network):
              raise exceptions.ValidationError('Social interactions need to have at least the "network" and "action" attributes defined.')
  
  
  class Transaction(object):
      '''
      Represents parameters for a Transaction call
  
      Properties:
      order_id -- Order ID, will be mapped to "utmtid" parameter
      affiliation -- Affiliation, Will be mapped to "utmtst" parameter
      total -- Total Cost, will be mapped to "utmtto" parameter
      tax -- Tax Cost, will be mapped to "utmttx" parameter
      shipping -- Shipping Cost, values as for unit and price, will be mapped to "utmtsp" parameter
      city -- Billing City, will be mapped to "utmtci" parameter
      state -- Billing Region, will be mapped to "utmtrg" parameter
      country -- Billing Country, will be mapped to "utmtco" parameter
      items -- @entity.Items in a transaction
  
      '''
      def __init__(self):
          self.items = []
          self.order_id = None
          self.affiliation = None
          self.total = None
          self.tax = None
          self.shipping = None
          self.city = None
          self.state = None
          self.country = None
  
      def __setattr__(self, name, value):
          if name == 'order_id':
              for itm in self.items:
                  itm.order_id = value
          object.__setattr__(self, name, value)
  
      def validate(self):
          if len(self.items) == 0:
              raise exceptions.ValidationError('Transaction need to consist of at least one item')
  
      def add_item(self, item):
          ''' item of type entities.Item '''
          if isinstance(item, Item):
              item.order_id = self.order_id
              self.items.append(item)
  
  
  class Visitor(object):
      '''
      You should serialize this object and store it in the user database to keep it
      persistent for the same user permanently (similar to the "__umta" cookie of
      the GA Javascript client).
  
      Properties:
      unique_id -- Unique user ID, will be part of the "__utma" cookie parameter
      first_visit_time -- Time of the very first visit of this user, will be part of the "__utma" cookie parameter
      previous_visit_time -- Time of the previous visit of this user, will be part of the "__utma" cookie parameter
      current_visit_time -- Time of the current visit of this user, will be part of the "__utma" cookie parameter
      visit_count -- Amount of total visits by this user, will be part of the "__utma" cookie parameter
      ip_address -- IP Address of the end user, will be mapped to "utmip" parameter and "X-Forwarded-For" request header
      user_agent -- User agent string of the end user, will be mapped to "User-Agent" request header
      locale -- Locale string (country part optional) will be mapped to "utmul" parameter
      flash_version -- Visitor's Flash version, will be maped to "utmfl" parameter
      java_enabled -- Visitor's Java support, will be mapped to "utmje" parameter
      screen_colour_depth -- Visitor's screen color depth, will be mapped to "utmsc" parameter
      screen_resolution -- Visitor's screen resolution, will be mapped to "utmsr" parameter
      '''
      def __init__(self):
          now = datetime.utcnow()
  
          self.unique_id = None
          self.first_visit_time = now
          self.previous_visit_time = now
          self.current_visit_time = now
          self.visit_count = 1
          self.ip_address = None
          self.user_agent = None
          self.locale = None
          self.flash_version = None
          self.java_enabled = None
          self.screen_colour_depth = None
          self.screen_resolution = None
  
      def __setattr__(self, name, value):
          if name == 'unique_id':
              if value and (value < 0 or value > 0x7fffffff):
                  raise ValueError('Visitor unique ID has to be a 32-bit integer between 0 and 0x7fffffff')
          object.__setattr__(self, name, value)
  
      def __getattribute__(self, name):
          if name == 'unique_id':
              tmp = object.__getattribute__(self, name)
              if tmp is None:
                  self.unique_id = self.generate_unique_id()
          return object.__getattribute__(self, name)
  
      def __getstate__(self):
          state = self.__dict__
          if state.get('user_agent') is None:
              state['unique_id'] = self.generate_unique_id()
  
          return state
  
      def extract_from_utma(self, utma):
          '''
          Will extract information for the "unique_id", "first_visit_time", "previous_visit_time",
          "current_visit_time" and "visit_count" properties from the given "__utma" cookie value.
          '''
          parts = utma.split('.')
          if len(parts) != 6:
              raise ValueError('The given "__utma" cookie value is invalid.')
  
          self.unique_id = int(parts[1])
          self.first_visit_time = utils.convert_ga_timestamp(parts[2])
          self.previous_visit_time = utils.convert_ga_timestamp(parts[3])
          self.current_visit_time = utils.convert_ga_timestamp(parts[4])
          self.visit_count = int(parts[5])
  
          return self
  
      def extract_from_server_meta(self, meta):
          '''
          Will extract information for the "ip_address", "user_agent" and "locale"
          properties from the given WSGI REQUEST META variable or equivalent.
          '''
          if 'REMOTE_ADDR' in meta and meta['REMOTE_ADDR']:
              ip = None
              for key in ('HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'):
                  if key in meta and not ip:
                      ips = meta.get(key, '').split(',')
                      ip = ips[-1].strip()
                      if not utils.is_valid_ip(ip):
                          ip = ''
                      if utils.is_private_ip(ip):
                          ip = ''
              if ip:
                  self.ip_address = ip
  
          if 'HTTP_USER_AGENT' in meta and meta['HTTP_USER_AGENT']:
              self.user_agent = meta['HTTP_USER_AGENT']
  
          if 'HTTP_ACCEPT_LANGUAGE' in meta and meta['HTTP_ACCEPT_LANGUAGE']:
              user_locals = []
              matched_locales = utils.validate_locale(meta['HTTP_ACCEPT_LANGUAGE'])
              if matched_locales:
                  lang_lst = map((lambda x: x.replace('-', '_')), (i[1] for i in matched_locales))
                  quality_lst = map((lambda x: x and x or 1), (float(i[4] and i[4] or '0') for i in matched_locales))
                  lang_quality_map = map((lambda x, y: (x, y)), lang_lst, quality_lst)
                  user_locals = [x[0] for x in sorted(lang_quality_map, key=itemgetter(1), reverse=True)]
  
              if user_locals:
                  self.locale = user_locals[0]
  
          return self
  
      def generate_hash(self):
          '''Generates a hashed value from user-specific properties.'''
          tmpstr = "%s%s%s" % (self.user_agent, self.screen_resolution, self.screen_colour_depth)
          return utils.generate_hash(tmpstr)
  
      def generate_unique_id(self):
          '''Generates a unique user ID from the current user-specific properties.'''
          return ((utils.get_32bit_random_num() ^ self.generate_hash()) & 0x7fffffff)
  
      def add_session(self, session):
          '''
          Updates the "previousVisitTime", "currentVisitTime" and "visitCount"
          fields based on the given session object.
          '''
          start_time = session.start_time
          if start_time != self.current_visit_time:
              self.previous_visit_time = self.current_visit_time
              self.current_visit_time = start_time
              self.visit_count = self.visit_count + 1
  