from PyObjCTools.TestSupport import *
import objc
import array
import sys

from Foundation import *
from PyObjCTest.testhelper import PyObjC_TestClass3


if sys.version_info[0] == 3:
    buffer = memoryview
    def array_frombytes(a, b):
        return a.frombytes(b)

    def array_tobytes(a):
        return a.tobytes()

else:
    def array_frombytes(a, b):
        return a.fromstring(b)

    def array_tobytes(a):
        return a.tostring()

try:
    memoryview
except NameError:
    memoryview = None


rawBytes = b"a\x13b\x00cd\xFFef\xEFgh"
otherBytes = array.array('B')
array_frombytes(otherBytes, b'12345678901234567890' * 5)

class TestNSData(TestCase):
    def testMethods(self):
        self.assertResultIsBOOL(NSData.isEqualToData_)
        self.assertResultIsBOOL(NSData.writeToFile_atomically_)
        self.assertArgIsBOOL(NSData.writeToFile_atomically_, 1)
        self.assertResultIsBOOL(NSData.writeToURL_atomically_)
        self.assertArgIsBOOL(NSData.writeToURL_atomically_, 1)
        self.assertResultIsBOOL(NSData.writeToFile_options_error_)
        self.assertArgIsOut(NSData.writeToFile_options_error_, 2)
        self.assertResultIsBOOL(NSData.writeToURL_options_error_)
        self.assertArgIsOut(NSData.writeToURL_options_error_, 2)
        self.assertArgIsOut(NSData.dataWithContentsOfFile_options_error_, 2)
        self.assertArgIsOut(NSData.dataWithContentsOfURL_options_error_, 2)
        self.assertArgIsOut(NSData.initWithContentsOfFile_options_error_, 2)
        self.assertArgIsOut(NSData.initWithContentsOfURL_options_error_, 2)

    def testConstants(self):
        self.assertEqual(NSMappedRead, 1)
        self.assertEqual(NSUncachedRead, 2)

        self.assertEqual(NSAtomicWrite, 1)

    @min_os_level('10.6')
    def testConstants10_6(self):
        self.assertEqual(NSDataReadingMapped, 1<<0)
        self.assertEqual(NSDataReadingUncached, 1<<1)
        self.assertEqual(NSDataWritingAtomic, 1<<0)
        self.assertEqual(NSDataSearchBackwards, 1<<0)
        self.assertEqual(NSDataSearchAnchored, 1<<1)

    @min_os_level('10.7')
    def testConstants10_7(self):
        self.assertEqual(NSDataReadingMappedAlways, 1<<3)

    @min_os_level('10.8')
    def testConstants10_8(self):
        self.assertEqual(NSDataWritingWithoutOverwriting, 1<<1)

    @min_os_level('10.6')
    def testMethods10_6(self):
        self.assertResultHasType(NSData.rangeOfData_options_range_, NSRange.__typestr__)
        self.assertArgHasType(NSData.rangeOfData_options_range_, 2, NSRange.__typestr__)

    def assertDataContents(self, d1, d2, rawData):
        self.assertEqual(len(d1), d1.length(), "d1: len() and -length didn't match.")
        self.assertEqual(len(d1), len(rawData), "d1: len(<data>) and len(<input>) didn't match. %d vs %d"%(len(d1), len(rawData)))
        self.assertEqual(len(d2), d2.length(), "d2: len() and -length didn't match.")
        self.assertEqual(len(d2), len(rawData), "d2: len(<data>) and len(<input>) didn't match. %d vs %d"%(len(d2), len(rawData)))

    def testDataWithBytes_length_(self):
        # Test +dataWithBytes:length
        data = NSData.dataWithBytes_length_(rawBytes, len(rawBytes))
        mutableData = NSMutableData.dataWithBytes_length_(rawBytes, len(rawBytes))
        self.assertDataContents(data, mutableData, rawBytes)

    def testAppendBytes_length_(self):
        self.assertArgIsIn(NSMutableData.appendBytes_length_, 0)
        self.assertArgSizeInArg(NSMutableData.appendBytes_length_, 0, 1)

    def testreplaceBytesInRange_withBytes_(self):
        self.assertArgIsIn(NSMutableData.replaceBytesInRange_withBytes_, 1)
        self.assertArgSizeInArg(NSMutableData.replaceBytesInRange_withBytes_, 1, 0)

    def testreplaceBytesInRange_withBytes_length_(self):
        self.assertArgIsIn(NSMutableData.replaceBytesInRange_withBytes_length_, 1)
        self.assertArgSizeInArg(NSMutableData.replaceBytesInRange_withBytes_length_, 1, 2)

    def testDataWithBytesNoCopy_length_freeWhenDone_(self):
        data = NSData.dataWithBytesNoCopy_length_freeWhenDone_(rawBytes, len(rawBytes), False)
        mutableData = NSMutableData.dataWithBytesNoCopy_length_freeWhenDone_(rawBytes, len(rawBytes), False)
        self.assertDataContents(data, mutableData, rawBytes)

    def testInitWithBytes_length_(self):
        # Test -initWithBytes:length:
        data = NSData.alloc().initWithBytes_length_(rawBytes, len(rawBytes))
        mutableData = NSMutableData.alloc().initWithBytes_length_(rawBytes, len(rawBytes))
        self.assertDataContents(data, mutableData, rawBytes)

    def testInitWithBytesNoCopy_length_freeWhenDone_(self):
        # Test -initWithBytesNoCopy:length:
        data = NSData.alloc().initWithBytesNoCopy_length_freeWhenDone_(rawBytes, len(rawBytes), False)
        mutableData = NSMutableData.alloc().initWithBytesNoCopy_length_freeWhenDone_(rawBytes, len(rawBytes), False)
        self.assertDataContents(data, mutableData, rawBytes)

    def testBytes(self):
        # Test -bytes
        data = NSData.alloc().initWithBytes_length_(rawBytes, len(rawBytes))
        bytesValue = data.bytes()
        self.assertEqual(len(bytesValue), len(rawBytes), "bytes() and rawBytes not equal length.")

        if sys.version_info[:2] <= (2,6):
            self.assertEqual(buffer(rawBytes), bytesValue)

        else:
            self.assertEqual(rawBytes, bytesValue)

        try:
            bytesValue[3] = b'\xAE'
        except TypeError as r:
            if str(r).find('buffer is read-only') == 0:
                pass
            elif str(r).find('cannot modify read-only memory') == 0:
                pass
            else:
                raise

    def testMutableBytes(self):
        # Test -mutableBytes
        mutableData = NSMutableData.dataWithBytes_length_(rawBytes, len(rawBytes))
        mutableBytes = mutableData.mutableBytes()
        for i in range(0, len(mutableBytes)):
            if sys.version_info[:2] >= (3,3):
                mutableBytes[i] = array_tobytes(otherBytes[i:i+1])[0]
            else:
                mutableBytes[i] = array_tobytes(otherBytes[i:i+1])
        mutableBytes[1:8] = array_tobytes(otherBytes[1:8])

        try:
            mutableBytes[2:10] = array_tobytes(otherBytes[1:5])
        except (TypeError, ValueError) as r:
            if str(r).find('right operand length must match slice length') == 0:
                pass
            elif 'cannot modify size of memoryview object' in str(r):
                pass
            elif 'ndarray assignment: lvalue and rvalue have different structures' in str(r):
                pass
            else:
                raise

    def testVariousDataLengths(self):
        # Test data of different lengths.
        #
        # Data of different lengths may be stored in different subclasses within the class cluster.
        testFactor = list(range(1, 64)) + [ 1000, 10000, 1000000]
        for aFactor in testFactor:
            bigRawBytes = b"1234567890" * aFactor

            mutableData = NSMutableData.dataWithBytes_length_(bigRawBytes, len(bigRawBytes))
            data = NSData.dataWithBytes_length_(bigRawBytes, len(bigRawBytes))

            self.assertDataContents(data, mutableData, bigRawBytes)

            mutableBytes = mutableData.mutableBytes()
            bytes = data.bytes()

            self.assertEqual(len(bytes), data.length())
            self.assertEqual(len(mutableBytes), mutableData.length())
            self.assertEqual(bytes, mutableBytes)

            mutableBytes[0:len(mutableBytes)] = bytes[0:len(bytes)]

    def testInitWithContents(self):
        b, err = NSData.alloc().initWithContentsOfFile_options_error_(
                "/etc/hosts", 0, None)
        self.assertIsInstance(b, NSData)
        self.assertIs(err, None)
        b2, err = NSData.alloc().initWithContentsOfFile_options_error_(
                "/etc/hosts.nosuchfile", 0, None)
        self.assertIs(b2, None)
        self.assertIsInstance(err, NSError)
        url = NSURL.fileURLWithPath_isDirectory_('/etc/hosts', False)
        b, err = NSData.alloc().initWithContentsOfURL_options_error_(
                url, 0, None)
        self.assertIsInstance(b, NSData)
        self.assertIs(err, None)
        url = NSURL.fileURLWithPath_isDirectory_('/etc/hosts.nosuchfile', False)
        b2, err = NSData.alloc().initWithContentsOfURL_options_error_(
                url, 0, None)
        self.assertIs(b2, None)
        self.assertIsInstance(err, NSError)

class MyData (NSData):
    def dataWithBytes_length_(self, bytes, length):
        return ("data", bytes, length)

BYTES="dummy bytes"
class MyData2 (NSData):
    def initWithBytes_length_(self, bytes, length):
        return ("init", bytes, length)

    def length(self):
        return 42

    def bytes(self):
        return BYTES


class MyData3 (NSData):
    def initWithBytes_length_(self, bytes, length):
        self._bytes = bytes
        self._length = length
        return self

    def bytes(self):
        return self._bytes

    def length(self):
        if hasattr(self, '_length'):
            return self._length
        return -1

class MyData4 (NSData):
    def initWithBytes_length_(self, bytes, length):
        return self

    def bytes(self):
        return None

    def length(self):
        return -1

class MyData5(NSData):
    def initWithBytes_length_(self, bytes, length):
        return self

    def bytes(self):
        raise ValueError("No bytes available")

    def length(self):
        return -1



class TestMyData (TestCase):
    # 'initWithBytes:length:' and 'dataWithBytes:length:' have custom IMP's
    def testData(self):
        r = PyObjC_TestClass3.makeDataWithBytes_method_(MyData, 0)
        self.assertEqual(r, ('data', b'hello world', 11))

    def testInit(self):
        r = PyObjC_TestClass3.makeDataWithBytes_method_(MyData2, 1)
        self.assertEqual(r, ('init', b'hello world', 11))

    def testBytes(self):
        r = PyObjC_TestClass3.makeDataWithBytes_method_(MyData3, 1)
        b = PyObjC_TestClass3.getBytes_(r)

        # Check for memoryview
        if isinstance(b.bytes(), memoryview):
            self.assertEqual(b.bytes().tobytes(), b'hello world')
        else:
            self.assertEqual(bytes(b.bytes()), b'hello world')

        self.assertEqual(b.getBytes_length_(None, 4), b'hell')
        self.assertEqual(b.getBytes_range_(None, NSRange(2, 4)), b'llo ')


    def testBytesNone(self):
        b = PyObjC_TestClass3.makeDataWithBytes_method_(MyData4, 1)
        self.assertEqual(b.bytes(), None)

    def testBytesRaises(self):
        b = PyObjC_TestClass3.makeDataWithBytes_method_(MyData5, 1)
        self.assertRaises(ValueError, b.bytes)



import array
class TestBuffer(TestCase):
    def testArray(self):
        a = array.array('b', b'foo')
        m = NSMutableData.dataWithData_(a)
        self.assertEqual(array_tobytes(a), m[:])
        self.assertTrue(objc.repythonify(a) is a)
        array_frombytes(a, m)
        self.assertEqual(array_tobytes(a), b'foofoo')
        m.appendData_(a)
        self.assertEqual(m[:], b'foofoofoo')
        m[3:6] = b'bar'
        self.assertEqual(m[:], b'foobarfoo')

    def testBuffer(self):
        if sys.version_info[0] == 3:
            b = b'foo'
        else:
            b = buffer('foo')
        m = NSMutableData.dataWithData_(b)
        self.assertEqual(b[:], m[:])
        self.assertTrue(objc.repythonify(b) is b)
        self.assertEqual(buffer(m)[:], m[:])


class TestRegressions (TestCase):
    def testDataStr(self):
        if sys.version_info[0] == 2:
            input = buffer("hello")
            input_str = "hello"
        else:
            input = b"hello"
            input_str = str(input)

        buf = NSData.dataWithData_(input)
        self.assertEqual(str(buf), input_str)


if __name__ == '__main__':
    main( )
