from .YahooSocket import YahooConnectionBase
from .yahoologinsocket import YahooLoginBase
from .yahoolookup import ykeys
from common import callsback
from hashlib import sha1
from itertools import izip
from logging import getLogger
from .yahooutil import from_utf8
from util.xml_tag import tag
from common.asynchttp.cookiejartypes import CookieJarHTTPMaster
from urllib2 import HTTPError
from httplib import HTTPResponse as libHTTPResponse
from common.asynchttp.httptypes import HTTPResponse as asyncHTTPResponse
RESPONSE_TYPES = (libHTTPResponse, asyncHTTPResponse)
import cookielib
import re
import string
log = getLogger('yahoohttp')
argsep = '^$'
ASCII_LETTERS = '@' + ''.join(sorted(string.ascii_letters))[:0x1f]
caret_split_re = re.compile('((?:\^.)|!(?:\^.))')
even_caret          = r'(?:(?:\^{2})*)'
single_caret_dollar = r'(?:\^\$)'
odd_caret_dollar_re    = re.compile(even_caret + single_caret_dollar)
parser_re = re.compile('(' + even_caret + (single_caret_dollar + '?') + ')')
def decode_caret(s):
    ss = caret_split_re.split(s)
    ret = []
    for s2 in ss:
        if s2 == '^^':
            s2 = '^'
        elif s2 == '^@':
            s2 = '\0'
        elif len(s2) == 2 and s2[0] == '^':
            ascii_pos = ASCII_LETTERS.find(s2[1])
            if ascii_pos >= 0: # != -1
                s2 = chr(ascii_pos)
    return ''.join(ret)
#TODO: use StringIO
def encode_caret(s):
    ret = []
    for char in s:
        if char == '^':
        if 0 <= ord(char) < len(ASCII_LETTERS): # < 0x20 (1 + 0x1f)
            ret.append('^' + ASCII_LETTERS[ord(char)])
    return ''.join(ret)
class YahooHTTPConnection(YahooConnectionBase, YahooLoginBase):
    def __init__(self, yahoo, server):
        YahooConnectionBase.__init__(self, yahoo)
        YahooLoginBase.__init__(self, yahoo)
        self.server = server
    def init_vars(self):
        self.ClientCounter = 0
        #I kid you not, an 8 digit integer string representation encoded in hex
        # from the last 8 digits of the string representation of the lower word
        # of GetSystemTimeAsFileTime.  Random seems just as good, since that value
        # in units of 100 nanoseconds, 8 digits will cycle every 10 seconds
        from random import randint
        self.secret = '%08d' % randint(0, int('9'*8))
        self.Secret = (self.secret).encode('hex')
        self.jar = cookielib.CookieJar()
        self.client_http_opener = CookieJarHTTPMaster(jar=self.jar)
        self.server_http_opener = CookieJarHTTPMaster(jar=self.jar)
    def close(self):
        #if we don't inform the server that we're leaving, we won't appear offline until
        # it decides we have timed-out.
        if self.client_http_opener is not None:
            self._push_cb('logoff', 'available'),
                         success = self._close_client,
                         error   = self._close_client)
    def _close_server(self, *a):
        self.server_http_opener, sho = None, self.server_http_opener
        if sho is not None:
    def _close_client(self, *a):
        self.client_http_opener, cho = None, self.client_http_opener
        if cho  is not None:
    def to_ydict(cls, d):
        if not d:
            return ''
        def to_ydict_entry(k, v):
            try: n = int(k)
            except ValueError:
                    n = ykeys[k]
                    log.warning('to_ydict got a dict with a non number key: %s', k)
                    return ''
                v = str(v)
            except UnicodeEncodeError:
                v = unicode(v).encode('utf-8')
            return ''.join([str(n), argsep, encode_caret(v), argsep])
        # find some way to iterate
        if hasattr(d, 'iteritems'):        item_iterator = d.iteritems()
        elif isinstance(d, (list, tuple)): item_iterator = izip(d[::2],d[1::2])
        return ''.join(to_ydict_entry(k,v) for k,v in item_iterator)
    def from_ydict(cls, data):
        d = {}
        for k, v in cls.from_ydict_iter(data):
            if k in d:
                log.warning('duplicate %s: %s', k, v)
                #raise AssertionError()
            d[k] = v
        return d
    def from_ydict_iter(cls, data):
        rval = None
        retval = []
        for val in parser_re.split(data):
            if odd_caret_dollar_re.match(val) is not None:
                if rval is None:
                rval = None
                if rval is None:
                    rval = val
                    rval += val
        keys, values = retval[::2], retval[1::2]
        # see note in yahooutil:from_ydict_iter
        values = [from_utf8(v) for v in values]
        return izip(keys, values)
    def ypacket_pack(self, command, status, version, data):
        data = self.to_ydict(data)
        return Ymsg(command, status, version, data, vendor_id='0', session=self)
    def push(self, packet, count=0):
                     success = self.recieve_response,
                     error = lambda *a: self.handle_client_stream_error(packet=packet, count=count, *a))
    def _push_cb(self, packet, callback=None):
        http, to_send = self.wrap_packet(packet)
        self._client_send(http, to_send, callback=callback)
    def wrap_packet(self, packet):
        is_auth = getattr(packet, '_yahoo_auth', False)
        http = 'http'
        if is_auth:
            http = 'https'
            assert self.ClientCounter == 0
            to_send = SessionWrapper(packet, self)
            to_send = SessionWrapper(packet, self)
        self.ClientCounter += 1
        return http, to_send
    def _client_send(self, http, to_send, callback=None):
        if self.client_http_opener is None:
            callback.error(Exception("Not connected"))
        log.debug_s('yahoo sending\n%r', str(to_send)) + '://' + self.server + '/', str(to_send),
        #if request already in progress, wait, let responses go first?  keep order separate until actually sending.
    def server_update(self):
        to_send = SessionWrapper(None, self, server=True)
        self.ClientCounter += 1
        log.debug_s('yahoo sending\n%r', str(to_send))'http://' + self.server + '/', str(to_send),
                                                success = self.server_response,
                                                error = self.handle_server_stream_error)
        #wait for list15 "online" to start this chain.
        #SessionWrapper(None, self, server=True), send on server connection.
        #wait for response, send again.
    def server_response(self, req, resp=None):
            self.recieve_response(req, resp)
        except Exception:
    def handle_server_stream_error(self, req, resp=None, *a, **k):
        log.error('handle_server_stream_error %r, %r, %r, %r', req, resp, a, k)
        if isinstance(resp, RESPONSE_TYPES):
            return self.server_response(req, resp)
        if isinstance(req, HTTPError) or isinstance(resp, HTTPError):
            return self.server_response(req, resp)
        import traceback;traceback.print_exc()
    def handle_client_stream_error(self, req, resp=None, packet=None, count=0, *a, **k):
        log.error('handle_client_stream_error %r, %r, %r, %r, %r, %r', req, resp, packet, count, a, k)
        if resp is None: #urllib compatibility
            resp = req
            del req
        #it's not going to be a 200 if we're in the error handler, no need to check that here.
        if isinstance(resp, RESPONSE_TYPES) or isinstance(resp, HTTPError):
            if count < 3:
                return self.push(packet, count + 1)
    def handle_connect(self):
        return self.async_proc(self.logon())
    def send_auth_req(self, user):
        cmd, wait, pkt = super(YahooHTTPConnection, self).send_auth_req(user)
        pkt._yahoo_auth = True
        return cmd, wait, pkt
    def recieve_response(self, req, resp=None):
        if resp is None: #urllib compatibility
            resp = req
            del req
        #should probably all be treated the same, who knows what can come back from where, nor should we care.
        data =
        log.debug_s('yahoo recieved\n%r', data)
        Session = tag(data)
        #deal with sequence numbers
        if Session['Payload'] == 'yes':
            ymsg = Session.Ymsg
            if not isinstance(ymsg, list):
                ymsg = [ymsg]
            for pkt in ymsg:
                self.handle_packet(pkt, Session)
            self.ServerSeqno = Session['ServerSeqno']
        except KeyError:
    def handle_packet(self, pkt, session):
        from util import Storage
        header = Storage()
        header.command = int(pkt['Command'])
        header.status  = int(pkt['Status'])
        account_id, session_id = session['SessionId'].split('-', 1)
        self.account_id = account_id
        header.session_id = self.session_id = int(session_id)
        data = pkt._cdata.strip().encode('utf-8')
        YahooConnectionBase.handle_packet(self, header, data)
        if header.command == 241 and header.status == 0:
class Ymsg(object):
    def __init__(self, command, status, version, data='', vendor_id='0', session=None):
        self.Command = command
        self.Status = status
        self.Version = version = data
        self.VendorId = vendor_id
        self.SessionId = getattr(session, 'session_id', '0')
    def _as_tag(self):
        t = tag('Ymsg')
        t['Command']  = self.Command
        t['Status']   = self.Status
        t['Version']  = self.Version
        t['VendorId'] = self.VendorId
        t['SessionId'] = self.SessionId
        t._cdata =
        return t
    def __str__(self):
        t = self._as_tag()
        #should all be utf-8 already, but we don't want to accidently go back to unicode via ascii
        l = t._to_xml(pretty=False, self_closing=False, return_list=True)
        return ''.join((s if isinstance(s, bytes) else s.encode('utf-8')) for s in l)
class SessionWrapper(object):
    def __init__(self, payload=None, session=None, server=False):
        assert payload is None or isinstance(payload, Ymsg)
        self.payload = payload
        self.server  = server
        self.session = session = self.session.ClientCounter
    def Payload(self):
        return "yes" if self.payload else "no"
    def SessionId(self):
        return '%s-%s' % (self.session.account_id, self.session.session_id)
    def Channel(self):
        return "ServerPost" if self.server else "ClientPost"
    def ClientHash(self):
        return sha1(str( + self.SessionId + self.session.secret + self.Channel).hexdigest().upper()
    def __str__(self):
        t = tag('Session')
        t['Payload'] = self.Payload
        t['Channel'] = self.Channel
        session_id = getattr(self.session, 'session_id', None)
        if session_id:
            t['ClientHash'] = self.ClientHash
            t['SessionId']  = self.SessionId
            t['Secret']     = self.session.Secret
        t['ClientCounter'] =
        t['ClientSeqno']   =
        if self.server:
            server_seqno = getattr(self.session, 'ServerSeqno', None)
            if server_seqno:
                t['ServerSeqno'] = server_seqno
        if self.payload:
            t.Ymsg = self.payload._as_tag()
        #should all be utf-8 already, but we don't want to accidently go back to unicode via ascii
        l = t._to_xml(pretty=False, self_closing=False, return_list=True)
        return ''.join((s if isinstance(s, bytes) else s.encode('utf-8')) for s in l)