import mock
from twisted.trial import unittest

from allmydata.test.common_util import ReallyEqualMixin
from allmydata.test.mock_s3 import MockS3Bucket

# This is the code that we're going to be testing.
from allmydata import node
from allmydata.storage.server import StorageServer
from allmydata.storage.backends.null.null_backend import NullBackend
from allmydata.storage.backends.disk.disk_backend import DiskBackend, si_si2dir
from allmydata.storage.backends.s3.s3_backend import S3Backend


# The following share file content was generated with
# storage.immutable.ShareFile from Tahoe-LAFS v1.8.2
# with share data == 'a'. The total size of this input
# is 85 bytes.
shareversionnumber = '\x00\x00\x00\x01'
sharedatalength = '\x00\x00\x00\x01'
numberofleases = '\x00\x00\x00\x01'
shareinputdata = 'a'
ownernumber = '\x00\x00\x00\x00'
renewsecret  = 'x'*32
cancelsecret = 'y'*32
expirationtime = '\x00(\xde\x80'
nextlease = ''
containerdata = shareversionnumber + sharedatalength + numberofleases
client_data = shareinputdata + ownernumber + renewsecret + \
    cancelsecret + expirationtime + nextlease
share_data = containerdata + client_data
testnodeid = 'testnodeidxxxxxxxxxx'


class TestServerWithNullBackend(unittest.TestCase, ReallyEqualMixin):
    """ NullBackend is just for testing and executable documentation, so
    this test is actually a test of StorageServer in which we're using
    NullBackend as helper code for the test, rather than a test of
    NullBackend. """
    def setUp(self):
        self.ss = StorageServer(testnodeid, NullBackend())

    @mock.patch('os.mkdir')
    @mock.patch('__builtin__.open')
    @mock.patch('os.listdir')
    @mock.patch('os.path.isdir')
    def test_write_share(self, mockisdir, mocklistdir, mockopen, mockmkdir):
        """
        Write a new share. This tests that StorageServer's remote_allocate_buckets
        generates the correct return types when given test-vector arguments. That
        bs is of the correct type is verified by attempting to invoke remote_write
        on bs[0].
        """
        alreadygot, bs = self.ss.remote_allocate_buckets('teststorage_index', 'x'*32, 'y'*32, set((0,)), 1, mock.Mock())
        bs[0].remote_write(0, 'a')
        self.failIf(mockisdir.called)
        self.failIf(mocklistdir.called)
        self.failIf(mockopen.called)
        self.failIf(mockmkdir.called)


#class ServerWithS3Backend(Server):
#    def create(self, name, reserved_space=0):
#        workdir = self.workdir(name)
#        s3bucket = MockS3Bucket(workdir)
#        backend = S3Backend(s3bucket, readonly=False, reserved_space=reserved_space)
#        ss = StorageServer("\x00" * 20, backend, workdir,
#                           stats_provider=FakeStatsProvider())
#        ss.setServiceParent(self.sparent)
#        return ss


#class ServerWithDiskBackend(Server):
#    def create(self, name, reserved_space=0, klass=StorageServer):
#        workdir = self.workdir(name)
#        backend = DiskBackend(workdir)
#        ss = StorageServer("\x00" * 20, backend, workdir,
#                           stats_provider=FakeStatsProvider())
#        ss.setServiceParent(self.sparent)
#        return ss


class Server(ReallyEqualMixin):
    """ This tests the StorageServer with a given back-end. """
    def set_up(self):
        self.backend = DiskBackend(self.storedir)
        self.ss = StorageServer(testnodeid, self.backend)

        self.backendwithreserve = DiskBackend(self.storedir, reserved_space = 1)
        self.sswithreserve = StorageServer(testnodeid, self.backendwithreserve)

    @mock.patch('time.time')
    def test_write_and_read_share(self, mocktime):
        """
        Write a new share, read it, and test the server's (and disk backend's)
        handling of simultaneous and successive attempts to write the same
        share.
        """
        mocktime.return_value = 0

        shareset = self.ss.backend.get_shareset('teststorage_index')

        self.failIf(shareset.has_incoming(0))

        # Populate incoming with the sharenum: 0.
        alreadygot, bs = self.ss.remote_allocate_buckets('teststorage_index', 'x'*32, 'y'*32, frozenset((0,)), 1, mock.Mock())

        # This is a white-box test: Inspect incoming and fail unless the sharenum: 0 is listed there.
        self.failUnless(shareset.has_incoming(0))

        # Attempt to create a second share writer with the same sharenum.
        alreadygota, bsa = self.ss.remote_allocate_buckets('teststorage_index', 'x'*32, 'y'*32, frozenset((0,)), 1, mock.Mock())

        # Show that no sharewriter results from a remote_allocate_buckets
        # with the same si and sharenum, until BucketWriter.remote_close()
        # has been called.
        self.failIf(bsa)

        # Test allocated size.
        spaceint = self.ss.allocated_size()
        self.failUnlessReallyEqual(spaceint, 1)

        # Write 'a' to shnum 0. Only tested together with close and read.
        bs[0].remote_write(0, 'a')

        # Preclose: Inspect final, failUnless nothing there.
        self.failUnlessReallyEqual(len(list(self.backend.get_shares('teststorage_index'))), 0)
        bs[0].remote_close()

        # Postclose: (Omnibus) failUnless written data is in final.
        sharesinfinal = list(self.backend.get_shares('teststorage_index'))
        self.failUnlessReallyEqual(len(sharesinfinal), 1)
        contents = sharesinfinal[0].read_share_data(0, 73)
        self.failUnlessReallyEqual(contents, client_data)

        # Exercise the case that the share we're asking to allocate is
        # already (completely) uploaded.
        self.ss.remote_allocate_buckets('teststorage_index', 'x'*32, 'y'*32, set((0,)), 1, mock.Mock())


    def test_read_old_share(self):
        """ This tests whether the code correctly finds and reads
        shares written out by old (Tahoe-LAFS <= v1.8.2)
        servers. There is a similar test in test_download, but that one
        is from the perspective of the client and exercises a deeper
        stack of code. This one is for exercising just the
        StorageServer object. """
        # Contruct a file with the appropriate contents in the mockfilesystem.
        datalen = len(share_data)
        finalhome = si_si2dir(self.basedir, 'teststorage_index').child(str(0))
        finalhome.setContent(share_data)

        # Now begin the test.
        bs = self.ss.remote_get_buckets('teststorage_index')

        self.failUnlessEqual(len(bs), 1)
        b = bs['0']
        # These should match by definition, the next two cases cover cases without (completely) unambiguous behaviors.
        self.failUnlessReallyEqual(b.remote_read(0, datalen), client_data)
        # If you try to read past the end you get the as much data as is there.
        self.failUnlessReallyEqual(b.remote_read(0, datalen+20), client_data)
        # If you start reading past the end of the file you get the empty string.
        self.failUnlessReallyEqual(b.remote_read(datalen+1, 3), '')


class MockConfigParser(object):
    def __init__(self, configged_vals):
        self.configged_vals = configged_vals
    def read(self, fname):
        pass
    def getboolean(self, section, option):
        raise NotImplementedError
    def get(self, section, option):
        return self.configged_vals[(section, option)]


class TestConfigureBackends(ReallyEqualMixin, unittest.TestCase):
    @mock.patch('ConfigParser.SafeConfigParser')
    def test_configure_disk_backend_bad_reserved_space(self, mockCP):
        mockCP.return_value = MockConfigParser({
                ("storage", "backend"): "disk",
                ("storage", "reserved_space"): "not a real answer",
                })

        self.failUnlessRaises(node.MissingConfigEntry, Client)