from __future__ import with_statement

import os
import time
import logging

from concurrence import unittest, Tasklet, Channel, Timeout
from concurrence.memcache import MemcacheResult, Memcache, MemcacheProtocol, MemcacheConnection, MemcacheConnectionManager, MemcacheError, MemcacheBehaviour, MemcacheCodec

MEMCACHE_IP = '127.0.0.1'

MEMCACHED_PATHS = ['/opt/memcached/bin/memcached',
                   '/usr/bin/memcached',
                   '/opt/local/bin/memcached']

MEMCACHED_BIN = None
for path in MEMCACHED_PATHS:
    if os.path.exists(path):
        MEMCACHED_BIN = path
        break
assert MEMCACHED_BIN is not None, "could not find memcached daemon binary"

#TODO check for memcached version before testing cas/gets commands

TEST_EXT = True

class TestMemcache(unittest.TestCase):
    log = logging.getLogger("TestMemcache")

    def setUp(self):
        self.log.debug("using memcached daemon: %s", MEMCACHED_BIN)

        for i in range(4):
            cmd = '%s -m 10 -p %d -u nobody -l 127.0.0.1&' % (MEMCACHED_BIN, 11211 + i)
            self.log.debug(cmd)
            os.system(cmd)

        Tasklet.sleep(1.0) #should be enough for memcached to get up and running

    def tearDown(self):
        MemcacheConnectionManager.create("default").close_all()

        cmd = 'killall %s' % MEMCACHED_BIN
        self.log.debug(cmd)
        os.system(cmd)

        Tasklet.sleep(1.0) #should be enough for memcached to go down

    def testResultCode(self):

        self.assertTrue(MemcacheResult.get('STORED') == MemcacheResult.get('STORED'))

        self.assertEquals('blaataap', MemcacheResult.get('CLIENT_ERROR blaataap').msg)
        self.assertEquals('blaataap', MemcacheResult.get('SERVER_ERROR blaataap').msg)

        self.assertTrue(MemcacheResult.get('CLIENT_ERROR blaataap') == MemcacheResult.get('CLIENT_ERROR blaataap'))

        self.assertEquals("MemcacheResult.STORED", repr(MemcacheResult.STORED))

        try:
            MemcacheResult.get('XXX')
            self.fail()
        except MemcacheError:
            pass

    def testModuloBehaviour(self):

        try:
            MemcacheBehaviour.create("blaataap")
            self.fail("expected error")
        except MemcacheError:
            pass

        b = MemcacheBehaviour.create("modulo")
        self.assertTrue(b is MemcacheBehaviour.create(b))

        b.set_servers([1,2,3,4])

        s = set()
        for i in range(100):
            s.add(b.key_to_addr(i))

        self.assertEquals(set([1,2,3,4]), s)

    def sharedTestBasic(self, mc):

        self.assertEquals(MemcacheResult.STORED, mc.set('test1', '12345'))
        self.assertEquals(MemcacheResult.STORED, mc.set('test2', '67890'))

        self.assertEquals('12345', mc.get('test1'))
        self.assertEquals('67890', mc.get('test2'))

        #__setitem__
        mc['test1_gsi'] = '12345'
        mc['test2_gsi'] = '67890'

        #__getitem__
        self.assertEquals('12345', mc['test1_gsi'])
        self.assertEquals('67890', mc['test2_gsi'])

        #get with result:
        self.assertEquals((MemcacheResult.OK, '12345'), mc.getr('test1'))
        self.assertEquals((MemcacheResult.OK, '67890'), mc.getr('test2'))

        self.assertEquals(None, mc.get('test3')) #if not found by default returns None
        self.assertEquals('blaat', mc.get('test3', 'blaat')) #but you can make it return some other

        self.assertEquals((MemcacheResult.OK, {'test1': '12345', 'test2': '67890'}), mc.get_multi(['test1', 'test2', 'test3']))

        #update test2
        mc.set('test2', 'hello world!')

        self.assertEquals((MemcacheResult.OK, {'test1': '12345', 'test2': 'hello world!'}), mc.get_multi(['test1', 'test2', 'test3']))

        #update to int type
        mc.set('test2', 10)
        self.assertEquals(10, mc.get('test2'))
        self.assertEquals(int, type(mc.get('test2')))

        #update to long type
        mc.set('test2', 10L)
        self.assertEquals(10L, mc.get('test2'))
        self.assertEquals(long, type(mc.get('test2')))

        #update to string type
        mc.set('test2', 'blaat')
        self.assertEquals('blaat', mc.get('test2'))
        self.assertEquals(str, type(mc.get('test2')))

        #update to unicode type
        mc.set('test2', u'C\xe9line')
        self.assertEquals(u'C\xe9line', mc.get('test2'))
        self.assertEquals(unicode, type(mc.get('test2')))

        #update to some other type
        mc.set('test2', {'piet': 'blaat', 10: 20})
        self.assertEquals({'piet': 'blaat', 10: 20}, mc.get('test2'))

        #test delete
        self.assertEquals(MemcacheResult.NOT_FOUND, mc.delete('test_del1'))
        self.assertEquals(MemcacheResult.STORED, mc.set('test_del1', 'hello'))
        self.assertEquals('hello', mc.get('test_del1'))
        self.assertEquals(MemcacheResult.DELETED, mc.delete('test_del1'))
        self.assertEquals(None, mc.get('test_del1'))

        #test add command
        mc.delete('add1')
        self.assertEquals(MemcacheResult.STORED, mc.add('add1', '11111'))
        self.assertEquals(MemcacheResult.NOT_STORED, mc.add('add1', '22222'))

        #test replace
        self.assertEquals(MemcacheResult.STORED, mc.set('replace1', '11111'))
        self.assertEquals(MemcacheResult.STORED, mc.replace('replace1', '11111'))
        self.assertEquals(MemcacheResult.STORED, mc.replace('replace1', '11111'))
        self.assertEquals(MemcacheResult.DELETED, mc.delete('replace1'))
        self.assertEquals(MemcacheResult.NOT_STORED, mc.replace('replace1', '11111'))

        #test expiration
        self.assertEquals(MemcacheResult.STORED, mc.set('exp_test', 'blaat', 2)) #expire 2 seconds from now
        self.assertEquals('blaat', mc.get('exp_test')) #should still find it
        Tasklet.sleep(4)
        self.assertEquals(None, mc.get('exp_test')) #should be gone

        if TEST_EXT:
            #test cas/gets
            self.assertEquals(MemcacheResult.NOT_FOUND, mc.cas('cas_test1', 'blaat', 12345))
            self.assertEquals(MemcacheResult.STORED, mc.set('cas_test1', 'blaat'))
            result, value, cas_unique = mc.gets('cas_test1')
            self.assertEquals(MemcacheResult.OK, result)
            self.assertEquals('blaat', value)
            self.assertEquals(MemcacheResult.STORED, mc.cas('cas_test1', 'blaat2', cas_unique))
            self.assertEquals(MemcacheResult.EXISTS, mc.cas('cas_test1', 'blaat2', cas_unique))

            result, value, cas_unique = mc.gets('cas_test1_not_there')
            self.assertEquals(MemcacheResult.OK, result)
            self.assertEquals(None, value)
            self.assertEquals(None, cas_unique)

            result, value, cas_unique = mc.gets('cas_test1')
            self.assertEquals(MemcacheResult.OK, result)
            self.assertEquals('blaat2', value)
            self.assertEquals(MemcacheResult.STORED, mc.cas('cas_test1', 'blaat3', cas_unique))
            self.assertEquals(MemcacheResult.STORED, mc.set('cas_test2', 'blaat4'))

            result, values = mc.gets_multi(['cas_test1', 'cas_test2'])
            self.assertEquals(MemcacheResult.OK, result)
            self.assertTrue('cas_test1' in values)
            self.assertTrue('cas_test2' in values)
            self.assertEquals('blaat3', values['cas_test1'][0])
            self.assertEquals('blaat4', values['cas_test2'][0])

            #test append
            self.assertEquals(MemcacheResult.NOT_STORED, mc.append('append_test1', 'hello'))
            self.assertEquals(MemcacheResult.STORED, mc.set('append_test1', 'hello'))
            self.assertEquals(MemcacheResult.STORED, mc.append('append_test1', 'world'))
            self.assertEquals('helloworld', mc.get('append_test1'))

            #test prepend
            self.assertEquals(MemcacheResult.NOT_STORED, mc.prepend('prepend_test1', 'world'))
            self.assertEquals(MemcacheResult.STORED, mc.set('prepend_test1', 'world'))
            self.assertEquals(MemcacheResult.STORED, mc.prepend('prepend_test1', 'hello'))