import sys, os
import struct
import unittest
import copy
import datetime
import win32timezone

try:
    sys_maxsize = sys.maxsize # 2.6 and later - maxsize != maxint on 64bits
except AttributeError:
    sys_maxsize = sys.maxint

import win32con
import pythoncom
import pywintypes
from win32com.shell import shell
from win32com.shell.shellcon import *
from win32com.storagecon import *

import win32com.test.util
from pywin32_testutil import str2bytes

class ShellTester(win32com.test.util.TestCase):
    def testShellLink(self):
        desktop = str(shell.SHGetSpecialFolderPath(0, CSIDL_DESKTOP))
        num = 0
        shellLink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
        persistFile = shellLink.QueryInterface(pythoncom.IID_IPersistFile)
        names = [os.path.join(desktop, n) for n in os.listdir(desktop)]
        programs = str(shell.SHGetSpecialFolderPath(0, CSIDL_PROGRAMS))
        names.extend([os.path.join(programs, n) for n in os.listdir(programs)])
        for name in names:
            try:
                persistFile.Load(name,STGM_READ)
            except pythoncom.com_error:
                continue
            # Resolve is slow - avoid it for our tests.
            #shellLink.Resolve(0, shell.SLR_ANY_MATCH | shell.SLR_NO_UI)
            fname, findData = shellLink.GetPath(0)
            unc = shellLink.GetPath(shell.SLGP_UNCPRIORITY)[0]
            num += 1
        if num == 0:
            # This isn't a fatal error, but is unlikely.
            print "Could not find any links on your desktop or programs dir, which is unusual"

    def testShellFolder(self):
        sf = shell.SHGetDesktopFolder()
        names_1 = []
        for i in sf: # Magically calls EnumObjects
            name = sf.GetDisplayNameOf(i, SHGDN_NORMAL)
            names_1.append(name)
        
        # And get the enumerator manually    
        enum = sf.EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN)
        names_2 = []
        for i in enum:
            name = sf.GetDisplayNameOf(i, SHGDN_NORMAL)
            names_2.append(name)
        names_1.sort()
        names_2.sort()
        self.assertEqual(names_1, names_2)

class PIDLTester(win32com.test.util.TestCase):
    def _rtPIDL(self, pidl):
        pidl_str = shell.PIDLAsString(pidl)
        pidl_rt = shell.StringAsPIDL(pidl_str)
        self.assertEqual(pidl_rt, pidl)
        pidl_str_rt = shell.PIDLAsString(pidl_rt)
        self.assertEqual(pidl_str_rt, pidl_str)

    def _rtCIDA(self, parent, kids):
        cida = parent, kids
        cida_str = shell.CIDAAsString(cida)
        cida_rt = shell.StringAsCIDA(cida_str)
        self.assertEqual(cida, cida_rt)
        cida_str_rt = shell.CIDAAsString(cida_rt)
        self.assertEqual(cida_str_rt, cida_str)

    def testPIDL(self):
        # A PIDL of "\1" is:   cb    pidl   cb
        expect =            str2bytes("\03\00"  "\1"  "\0\0")
        self.assertEqual(shell.PIDLAsString([str2bytes("\1")]), expect)
        self._rtPIDL([str2bytes("\0")])
        self._rtPIDL([str2bytes("\1"), str2bytes("\2"), str2bytes("\3")])
        self._rtPIDL([str2bytes("\0") * 2048] * 2048)
        # PIDL must be a list
        self.assertRaises(TypeError, shell.PIDLAsString, "foo")

    def testCIDA(self):
        self._rtCIDA([str2bytes("\0")], [ [str2bytes("\0")] ])
        self._rtCIDA([str2bytes("\1")], [ [str2bytes("\2")] ])
        self._rtCIDA([str2bytes("\0")], [ [str2bytes("\0")], [str2bytes("\1")], [str2bytes("\2")] ])

    def testBadShortPIDL(self):
        # A too-short child element:   cb    pidl   cb
        pidl =                       str2bytes("\01\00"  "\1")
        self.assertRaises(ValueError, shell.StringAsPIDL, pidl)

        # ack - tried to test too long PIDLs, but a len of 0xFFFF may not
        # always fail.

class FILEGROUPDESCRIPTORTester(win32com.test.util.TestCase):
    def _getTestTimes(self):
        if issubclass(pywintypes.TimeType, datetime.datetime):
            ctime = win32timezone.now()
            atime = ctime + datetime.timedelta(seconds=1)
            wtime = atime + datetime.timedelta(seconds=1)
        else:
            ctime = pywintypes.Time(11)
            atime = pywintypes.Time(12)
            wtime = pywintypes.Time(13)
        return ctime, atime, wtime

    def _testRT(self, fd):
        fgd_string = shell.FILEGROUPDESCRIPTORAsString([fd])
        fd2 = shell.StringAsFILEGROUPDESCRIPTOR(fgd_string)[0]
        
        fd = fd.copy()
        fd2 = fd2.copy()
        
        # The returned objects *always* have dwFlags and cFileName.
        if 'dwFlags' not in fd:
            del fd2['dwFlags']
        if 'cFileName' not in fd:
            self.assertEqual(fd2['cFileName'], '')
            del fd2['cFileName']

        self.assertEqual(fd, fd2)

    def _testSimple(self, make_unicode):
        fgd = shell.FILEGROUPDESCRIPTORAsString([], make_unicode)
        header = struct.pack("i", 0)
        self.assertEqual(header, fgd[:len(header)])
        self._testRT(dict())
        d = dict()
        fgd = shell.FILEGROUPDESCRIPTORAsString([d], make_unicode)
        header = struct.pack("i", 1)
        self.assertEqual(header, fgd[:len(header)])
        self._testRT(d)

    def testSimpleBytes(self):
        self._testSimple(False)

    def testSimpleUnicode(self):
        self._testSimple(True)

    def testComplex(self):
        if sys.hexversion < 0x2030000:
            # no kw-args to dict in 2.2 - not worth converting!
            return
        clsid = pythoncom.MakeIID("{CD637886-DB8B-4b04-98B5-25731E1495BE}")
        ctime, atime, wtime = self._getTestTimes()
        d = dict(cFileName="foo.txt",
                 clsid=clsid,
                 sizel=(1,2),
                 pointl=(3,4),
                 dwFileAttributes = win32con.FILE_ATTRIBUTE_NORMAL,
                 ftCreationTime=ctime,
                 ftLastAccessTime=atime,
                 ftLastWriteTime=wtime,
                 nFileSize=sys_maxsize + 1)
        self._testRT(d)

    def testUnicode(self):
        # exercise a bug fixed in build 210 - multiple unicode objects failed.
        if sys.hexversion < 0x2030000:
            # no kw-args to dict in 2.2 - not worth converting!
            return

        ctime, atime, wtime = self._getTestTimes()
        d = [dict(cFileName="foo.txt",
                 sizel=(1,2),
                 pointl=(3,4),
                 dwFileAttributes = win32con.FILE_ATTRIBUTE_NORMAL,
                 ftCreationTime=ctime,
                 ftLastAccessTime=atime,
                 ftLastWriteTime=wtime,
                 nFileSize=sys_maxsize + 1),
            dict(cFileName="foo2.txt",
                 sizel=(1,2),
                 pointl=(3,4),
                 dwFileAttributes = win32con.FILE_ATTRIBUTE_NORMAL,
                 ftCreationTime=ctime,
                 ftLastAccessTime=atime,
                 ftLastWriteTime=wtime,
                 nFileSize=sys_maxsize + 1),
            dict(cFileName=u"foo\xa9.txt",
                 sizel=(1,2),
                 pointl=(3,4),
                 dwFileAttributes = win32con.FILE_ATTRIBUTE_NORMAL,
                 ftCreationTime=ctime,
                 ftLastAccessTime=atime,
                 ftLastWriteTime=wtime,
                 nFileSize=sys_maxsize + 1),
            ]
        s = shell.FILEGROUPDESCRIPTORAsString(d, 1)
        d2 = shell.StringAsFILEGROUPDESCRIPTOR(s)
        # clobber 'dwFlags' - they are not expected to be identical
        for t in d2:
            del t['dwFlags']
        self.assertEqual(d, d2)

class FileOperationTester(win32com.test.util.TestCase):
    def setUp(self):
        import tempfile
        self.src_name = os.path.join(tempfile.gettempdir(), "pywin32_testshell")
        self.dest_name = os.path.join(tempfile.gettempdir(), "pywin32_testshell_dest")
        self.test_data = str2bytes("Hello from\0Python")
        f=open(self.src_name, "wb")
        f.write(self.test_data)
        f.close()
        try:
            os.unlink(self.dest_name)
        except os.error:
            pass

    def tearDown(self):
        for fname in (self.src_name, self.dest_name):
            if os.path.isfile(fname):
                os.unlink(fname)

    def testCopy(self):
        s = (0, # hwnd,
             FO_COPY, #operation
             self.src_name,
             self.dest_name)

        rc, aborted = shell.SHFileOperation(s)
        self.failUnless(not aborted)
        self.failUnlessEqual(0, rc)
        self.failUnless(os.path.isfile(self.src_name))
        self.failUnless(os.path.isfile(self.dest_name))

    def testRename(self):
        s = (0, # hwnd,
             FO_RENAME, #operation
             self.src_name,
             self.dest_name)
        rc, aborted = shell.SHFileOperation(s)
        self.failUnless(not aborted)
        self.failUnlessEqual(0, rc)
        self.failUnless(os.path.isfile(self.dest_name))
        self.failUnless(not os.path.isfile(self.src_name))

    def testMove(self):
        s = (0, # hwnd,
             FO_MOVE, #operation
             self.src_name,
             self.dest_name)
        rc, aborted = shell.SHFileOperation(s)
        self.failUnless(not aborted)
        self.failUnlessEqual(0, rc)
        self.failUnless(os.path.isfile(self.dest_name))
        self.failUnless(not os.path.isfile(self.src_name))

    def testDelete(self):
        s = (0, # hwnd,
             FO_DELETE, #operation
             self.src_name, None,
             FOF_NOCONFIRMATION)
        rc, aborted = shell.SHFileOperation(s)
        self.failUnless(not aborted)
        self.failUnlessEqual(0, rc)
        self.failUnless(not os.path.isfile(self.src_name))

if __name__=='__main__':
    win32com.test.util.testmain()
