# -*- coding: utf-8 -*-
from __future__ import division, unicode_literals

import gevent
import math
import os
import time
import logging

from lymph.utils import SampleWindow
from lymph.exceptions import RpcError

logger = logging.getLogger(__name__)

UNKNOWN = 'unknown'
RESPONSIVE = 'responsive'
UNRESPONSIVE = 'unresponsive'
CLOSED = 'closed'
IDLE = 'idle'


class Connection(object):
    def __init__(self, server, endpoint, heartbeat_interval=1, timeout=1, idle_timeout=10, unresponsive_disconnect=30, idle_disconnect=60):
        self.server = server
        self.endpoint = endpoint
        self.timeout = timeout
        self.heartbeat_interval = heartbeat_interval
        self.idle_timeout = idle_timeout
        self.unresponsive_disconnect = unresponsive_disconnect
        self.idle_disconnect = idle_disconnect

        now = time.monotonic()
        self.last_seen = 0
        self.idle_since = 0
        self.last_message = now
        self.created_at = now
        self.heartbeat_samples = SampleWindow(100, factor=1000)  # milliseconds
        self.explicit_heartbeat_count = 0
        self.status = UNKNOWN

        self.received_message_count = 0
        self.sent_message_count = 0

        self.heartbeat_loop_greenlet = self.server.container.spawn(self.heartbeat_loop)
        self.live_check_loop_greenlet = self.server.container.spawn(self.live_check_loop)

    def __str__(self):
        return "connection to=%s last_seen=%s" % (self.endpoint, self._dt())

    def _dt(self):
        return time.monotonic() - self.last_seen

    @property
    def phi(self):
        p = self.heartbeat_samples.p(self._dt())
        if p == 0:
            return float('inf')
        return -math.log10(p)

    def set_status(self, status):
        self.status = status

    def heartbeat_loop(self):
        while True:
            start = time.monotonic()
            channel = self.server.ping(self.endpoint)
            try:
                channel.get(timeout=self.heartbeat_interval)
            except RpcError:
                pass
            else:
                self.heartbeat_samples.add(time.monotonic() - start)
                self.explicit_heartbeat_count += 1
            gevent.sleep(self.heartbeat_interval)

    def live_check_loop(self):
        while True:
            if self.last_seen:
                now = time.monotonic()
                if now - self.last_seen >= self.timeout:
                    self.set_status(UNRESPONSIVE)
                elif now - self.last_message >= self.idle_timeout:
                    self.set_status(IDLE)
                    self.idle_since = now
                else:
                    self.set_status(RESPONSIVE)
            roundtrip_stats = 'window (mean rtt = {mean:.1f} ms; stddev rtt = {stddev:.1f})'.format(**self.heartbeat_samples.stats)
            roundtrip_total_stats = 'total (mean rtt = {mean:.1f} ms; stddev rtt = {stddev:.1f})'.format(**self.heartbeat_samples.total.stats)
            logger.debug("pid=%s; %s; %s; phi = %.3f; ping/s = %.2f; status=%s" % (
                os.getpid(),
                roundtrip_stats,
                roundtrip_total_stats,
                self.phi,
                self.explicit_heartbeat_count / max(1, time.monotonic() - self.created_at),
                self.status,
            ))
            gevent.sleep(self.timeout)

    def close(self):
        if self.status == CLOSED:
            return
        self.status = CLOSED
        self.heartbeat_loop_greenlet.kill()
        self.live_check_loop_greenlet.kill()
        self.server.disconnect(self.endpoint)

    def on_recv(self, msg):
        now = time.monotonic()
        if not msg.is_idle_chatter():
            self.last_message = now
        self.received_message_count += 1

    def on_send(self, msg):
        if not msg.is_idle_chatter():
            self.last_message = time.monotonic()
        self.sent_message_count += 1

    def is_alive(self):
        return self.status in (RESPONSIVE, IDLE)

    def stats(self):
        return {
            'endpoint': self.endpoint,
            'rtt': self.heartbeat_samples.stats,
            'phi': self.phi,
            'status': self.status,
            'sent': self.sent_message_count,
            'received': self.received_message_count,
        }

@@ -2288,32 +2288,159 @@
 ue:%0A            
+self.update_status()%0A            self.log_stats()%0A            gevent.sleep(self.timeout)%0A%0A    def update_status(self):%0A        
 if self.last_see
@@ -2442,20 +2442,16 @@
 t_seen:%0A
-    
         
@@ -2469,36 +2469,32 @@
 ime.monotonic()%0A
-    
             if n
@@ -2538,36 +2538,32 @@
                 
-    
 self.set_status(
@@ -2568,36 +2568,32 @@
 s(UNRESPONSIVE)%0A
-    
             elif
@@ -2647,36 +2647,32 @@
                 
- 
-   
 self.set_status(
@@ -2677,20 +2677,16 @@
 s(IDLE)%0A
-    
         
@@ -2723,38 +2723,30 @@
             
- 
-   
 else:%0A
-    
             
@@ -2769,36 +2769,58 @@
 tus(RESPONSIVE)%0A
+%0A
     
+def log_stats(self):%0A
         roundtri
@@ -2838,35 +2838,33 @@
 window (mean rtt
- = 
+=
 %7Bmean:.1f%7D ms; s
@@ -2864,35 +2864,33 @@
 %7D ms; stddev rtt
- = 
+=
 %7Bstddev:.1f%7D)'.f
@@ -2927,36 +2927,32 @@
 .stats)%0A        
- 
-   
 roundtrip_total_
@@ -2975,19 +2975,17 @@
 mean rtt
- = 
+=
 %7Bmean:.1
@@ -3001,19 +3001,17 @@
 ddev rtt
- = 
+=
 %7Bstddev:
@@ -3070,20 +3070,16 @@
         
- 
-   
 logger.d
@@ -3103,19 +3103,17 @@
  %25s; phi
- = 
+=
 %25.3f; pi
@@ -3116,19 +3116,17 @@
 ; ping/s
- = 
+=
 %25.2f; st
@@ -3150,20 +3150,16 @@
         
- 
-   
 os.getpi
@@ -3163,20 +3163,16 @@
 tpid(),%0A
-    
         
@@ -3196,36 +3196,32 @@
 ts,%0A            
-  
-  
 roundtrip_total_
@@ -3235,28 +3235,24 @@
             
-    
 self.phi,%0A  
@@ -3253,36 +3253,32 @@
 hi,%0A            
-  
-  
 self.explicit_he
@@ -3345,28 +3345,24 @@
             
-    
 self.status,
@@ -3374,52 +3374,9 @@
     
-    ))%0A            gevent.sleep(self.timeout
+)
 )%0A%0A 

refactor live_check_loop of connection