/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file can be used in the following contexts:
 *
 *  1. included from a non-osfile worker thread using importScript
 *   (it serves to define a synchronous API for that worker thread)
 *   (bug 707681)
 *
 *  2. included from the main thread using Components.utils.import
 *   (it serves to define the asynchronous API, whose implementation
 *    resides in the worker thread)
 *   (bug 729057)
 *
 * 3. included from the osfile worker thread using importScript
 *   (it serves to define the implementation of the asynchronous API)
 *   (bug 729057)
 */

{
  if (typeof Components != "undefined") {
    // We do not wish osfile_win.jsm to be used directly as a main thread
    // module yet. When time comes, it will be loaded by a combination of
    // a main thread front-end/worker thread implementation that makes sure
    // that we are not executing synchronous IO code in the main thread.

    throw new Error("osfile_win.jsm cannot be used from the main thread yet");
  }

  (function(exports) {
     "use strict";
     if (exports.OS && exports.OS.Win && exports.OS.Win.File) {
       return; // Avoid double initialization
     }

     let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
     let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
     let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
     let libc = SysAll.libc;
     let advapi32 = new SharedAll.Library("advapi32", "advapi32.dll");
     let Const = SharedAll.Constants.Win;

     /**
      * Initialize the Windows module.
      *
      * @param {function=} declareFFI
      */
     // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
     let init = function init(aDeclareFFI) {
       let declareFFI;
       if (aDeclareFFI) {
         declareFFI = aDeclareFFI.bind(null, libc);
       } else {
         declareFFI = SysAll.declareFFI;
       }
       let declareLazyFFI = SharedAll.declareLazyFFI;

       // Initialize types that require additional OS-specific
       // support - either finalization or matching against
       // OS-specific constants.
       let Type = Object.create(SysAll.Type);
       let SysFile = exports.OS.Win.File = { Type: Type };

       // Initialize types

       /**
        * A C integer holding INVALID_HANDLE_VALUE in case of error or
        * a file descriptor in case of success.
        */
       Type.HANDLE =
         Type.voidptr_t.withName("HANDLE");
       Type.HANDLE.importFromC = function importFromC(maybe) {
         if (Type.int.cast(maybe).value == INVALID_HANDLE) {
           // Ensure that API clients can effectively compare against
           // Const.INVALID_HANDLE_VALUE. Without this cast,
           // == would always return |false|.
           return INVALID_HANDLE;
         }
         return ctypes.CDataFinalizer(maybe, this.finalizeHANDLE);
       };
       Type.HANDLE.finalizeHANDLE = function placeholder() {
         throw new Error("finalizeHANDLE should be implemented");
       };
       let INVALID_HANDLE = Const.INVALID_HANDLE_VALUE;

       Type.file_HANDLE = Type.HANDLE.withName("file HANDLE");
       SharedAll.defineLazyGetter(Type.file_HANDLE,
         "finalizeHANDLE",
         function() {
           return SysFile._CloseHandle;
         });

       Type.find_HANDLE = Type.HANDLE.withName("find HANDLE");
       SharedAll.defineLazyGetter(Type.find_HANDLE,
         "finalizeHANDLE",
         function() {
           return SysFile._FindClose;
         });

       Type.DWORD = Type.uint32_t.withName("DWORD");

       /* A special type used to represent flags passed as DWORDs to a function.
        * In JavaScript, bitwise manipulation of numbers, such as or-ing flags,
        * can produce negative numbers. Since DWORD is unsigned, these negative
        * numbers simply cannot be converted to DWORD. For this reason, whenever
        * bit manipulation is called for, you should rather use DWORD_FLAGS,
        * which is represented as a signed integer, hence has the correct
        * semantics.
        */
       Type.DWORD_FLAGS = Type.int32_t.withName("DWORD_FLAGS");

       /**
        * A C integer holding 0 in case of error or a positive integer
        * in case of success.
        */
       Type.zero_or_DWORD =
         Type.DWORD.withName("zero_or_DWORD");

       /**
        * A C integer holding 0 in case of error, any other value in
        * case of success.
        */
       Type.zero_or_nothing =
         Type.int.withName("zero_or_nothing");

       /**
        * A C integer holding flags related to NTFS security.
        */
       Type.SECURITY_ATTRIBUTES =
         Type.void_t.withName("SECURITY_ATTRIBUTES");

       /**
        * A C integer holding pointers related to NTFS security.
        */
       Type.PSID =
         Type.voidptr_t.withName("PSID");

       Type.PACL =
         Type.voidptr_t.withName("PACL");

       Type.PSECURITY_DESCRIPTOR =
         Type.voidptr_t.withName("PSECURITY_DESCRIPTOR");

       /**
        * A C integer holding Win32 local memory handle.
        */
       Type.HLOCAL =
         Type.voidptr_t.withName("HLOCAL");

       Type.FILETIME =
         new SharedAll.Type("FILETIME",
                  ctypes.StructType("FILETIME", [
                  { lo: Type.DWORD.implementation },
                  { hi: Type.DWORD.implementation }]));

       Type.FindData =
         new SharedAll.Type("FIND_DATA",
                  ctypes.StructType("FIND_DATA", [
                    { dwFileAttributes: ctypes.uint32_t },
                    { ftCreationTime:   Type.FILETIME.implementation },
                    { ftLastAccessTime: Type.FILETIME.implementation },
                    { ftLastWriteTime:  Type.FILETIME.implementation },
                    { nFileSizeHigh:    Type.DWORD.implementation },
                    { nFileSizeLow:     Type.DWORD.implementation },
                    { dwReserved0:      Type.DWORD.implementation },
                    { dwReserved1:      Type.DWORD.implementation },
                    { cFileName:        ctypes.ArrayType(ctypes.char16_t, Const.MAX_PATH) },
                    { cAlternateFileName: ctypes.ArrayType(ctypes.char16_t, 14) }
                      ]));

       Type.FILE_INFORMATION =
         new SharedAll.Type("FILE_INFORMATION",
                  ctypes.StructType("FILE_INFORMATION", [
                    { dwFileAttributes: ctypes.uint32_t },
                    { ftCreationTime:   Type.FILETIME.implementation },
                    { ftLastAccessTime: Type.FILETIME.implementation },
                    { ftLastWriteTime:  Type.FILETIME.implementation },
                    { dwVolumeSerialNumber: ctypes.uint32_t },
                    { nFileSizeHigh:    Type.DWORD.implementation },
                    { nFileSizeLow:     Type.DWORD.implementation },
                    { nNumberOfLinks:   ctypes.uint32_t },
                    { nFileIndex: ctypes.uint64_t }
                   ]));

       Type.SystemTime =
         new SharedAll.Type("SystemTime",
                  ctypes.StructType("SystemTime", [
                  { wYear:      ctypes.int16_t },
                  { wMonth:     ctypes.int16_t },
                  { wDayOfWeek: ctypes.int16_t },
                  { wDay:       ctypes.int16_t },
                  { wHour:      ctypes.int16_t },
                  { wMinute:    ctypes.int16_t },
                  { wSecond:    ctypes.int16_t },
                  { wMilliSeconds: ctypes.int16_t }
                  ]));

       // Special case: these functions are used by the
       // finalizer
       libc.declareLazy(SysFile, "_CloseHandle",
                        "CloseHandle", ctypes.winapi_abi,
                        /*return */ctypes.bool,
                        /*handle*/ ctypes.voidptr_t);

       SysFile.CloseHandle = function(fd) {
         if (fd == INVALID_HANDLE) {
           return true;
         } else {
           return fd.dispose(); // Returns the value of |CloseHandle|.
         }
       };

       libc.declareLazy(SysFile, "_FindClose",
                        "FindClose", ctypes.winapi_abi,
                        /*return */ctypes.bool,
                        /*handle*/ ctypes.voidptr_t);

       SysFile.FindClose = function(handle) {
         if (handle == INVALID_HANDLE) {
           return true;
         } else {
           return handle.dispose(); // Returns the value of |FindClose|.
         }
       };

       // Declare libc functions as functions of |OS.Win.File|

       libc.declareLazyFFI(SysFile, "CopyFile",
         "CopyFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*sourcePath*/ Type.path,
                    /*destPath*/   Type.path,
                    /*bailIfExist*/Type.bool);

       libc.declareLazyFFI(SysFile, "CreateDirectory",
         "CreateDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*name*/   Type.char16_t.in_ptr,
                    /*security*/Type.SECURITY_ATTRIBUTES.in_ptr);

       libc.declareLazyFFI(SysFile, "CreateFile",
         "CreateFileW", ctypes.winapi_abi,
                    /*return*/  Type.file_HANDLE,
                    /*name*/    Type.path,
                    /*access*/  Type.DWORD_FLAGS,
                    /*share*/   Type.DWORD_FLAGS,
                    /*security*/Type.SECURITY_ATTRIBUTES.in_ptr,
                    /*creation*/Type.DWORD_FLAGS,
                    /*flags*/   Type.DWORD_FLAGS,
                    /*template*/Type.HANDLE);

       libc.declareLazyFFI(SysFile, "DeleteFile",
         "DeleteFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path);

       libc.declareLazyFFI(SysFile, "FileTimeToSystemTime",
         "FileTimeToSystemTime", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*filetime*/Type.FILETIME.in_ptr,
                    /*systime*/ Type.SystemTime.out_ptr);

       libc.declareLazyFFI(SysFile, "SystemTimeToFileTime",
         "SystemTimeToFileTime", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*systime*/  Type.SystemTime.in_ptr,
                    /*filetime*/ Type.FILETIME.out_ptr);

       libc.declareLazyFFI(SysFile, "FindFirstFile",
         "FindFirstFileW", ctypes.winapi_abi,
                    /*return*/ Type.find_HANDLE,
                    /*pattern*/Type.path,
                    /*data*/   Type.FindData.out_ptr);

       libc.declareLazyFFI(SysFile, "FindNextFile",
         "FindNextFileW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*prev*/   Type.find_HANDLE,
                    /*data*/   Type.FindData.out_ptr);

       libc.declareLazyFFI(SysFile, "FormatMessage",
         "FormatMessageW", ctypes.winapi_abi,
                    /*return*/ Type.DWORD,
                    /*flags*/  Type.DWORD_FLAGS,
                    /*source*/ Type.void_t.in_ptr,
                    /*msgid*/  Type.DWORD_FLAGS,
                    /*langid*/ Type.DWORD_FLAGS,
                    /*buf*/    Type.out_wstring,
                    /*size*/   Type.DWORD,
                    /*Arguments*/Type.void_t.in_ptr
                   );

       libc.declareLazyFFI(SysFile, "GetCurrentDirectory",
         "GetCurrentDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_DWORD,
                    /*length*/ Type.DWORD,
                    /*buf*/    Type.out_path
                   );

       libc.declareLazyFFI(SysFile, "GetFullPathName",
         "GetFullPathNameW", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_DWORD,
                    /*fileName*/ Type.path,
                    /*length*/   Type.DWORD,
                    /*buf*/      Type.out_path,
                    /*filePart*/ Type.DWORD
                   );

       libc.declareLazyFFI(SysFile, "GetDiskFreeSpaceEx",
         "GetDiskFreeSpaceExW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*directoryName*/ Type.path,
                    /*freeBytesForUser*/ Type.uint64_t.out_ptr,
                    /*totalBytesForUser*/ Type.uint64_t.out_ptr,
                    /*freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr);

       libc.declareLazyFFI(SysFile, "GetFileInformationByHandle",
         "GetFileInformationByHandle", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*handle*/ Type.HANDLE,
                    /*info*/   Type.FILE_INFORMATION.out_ptr);

       libc.declareLazyFFI(SysFile, "MoveFileEx",
         "MoveFileExW", ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*sourcePath*/ Type.path,
                    /*destPath*/ Type.path,
                    /*flags*/    Type.DWORD
                   );

       libc.declareLazyFFI(SysFile, "ReadFile",
         "ReadFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE,
                    /*buffer*/ Type.voidptr_t,
                    /*nbytes*/ Type.DWORD,
                    /*nbytes_read*/Type.DWORD.out_ptr,
                    /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
         );

       libc.declareLazyFFI(SysFile, "RemoveDirectory",
         "RemoveDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path);

       libc.declareLazyFFI(SysFile, "SetCurrentDirectory",
         "SetCurrentDirectoryW", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*path*/   Type.path
                   );

       libc.declareLazyFFI(SysFile, "SetEndOfFile",
         "SetEndOfFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE);

       libc.declareLazyFFI(SysFile, "SetFilePointer",
         "SetFilePointer", ctypes.winapi_abi,
                    /*return*/ Type.DWORD,
                    /*file*/   Type.HANDLE,
                    /*distlow*/Type.long,
                    /*disthi*/ Type.long.in_ptr,
                    /*method*/ Type.DWORD);

       libc.declareLazyFFI(SysFile, "SetFileTime",
         "SetFileTime",  ctypes.winapi_abi,
                    /*return*/   Type.zero_or_nothing,
                    /*file*/     Type.HANDLE,
                    /*creation*/ Type.FILETIME.in_ptr,
                    /*access*/   Type.FILETIME.in_ptr,
                    /*write*/    Type.FILETIME.in_ptr);


       libc.declareLazyFFI(SysFile, "WriteFile",
         "WriteFile", ctypes.winapi_abi,
                    /*return*/ Type.zero_or_nothing,
                    /*file*/   Type.HANDLE,
                    /*buffer*/ Type.voidptr_t,
                    /*nbytes*/ Type.DWORD,
                    /*nbytes_wr*/Type.DWORD.out_ptr,
                    /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
         );

        libc.declareLazyFFI(SysFile, "FlushFileBuffers",
          "FlushFileBuffers", ctypes.winapi_abi,
                     /*return*/ Type.zero_or_nothing,
                     /*file*/   Type.HANDLE);

        libc.declareLazyFFI(SysFile, "GetFileAttributes",
          "GetFileAttributesW", ctypes.winapi_abi,
                     /*return*/   Type.DWORD_FLAGS,
                     /*fileName*/ Type.path);

        libc.declareLazyFFI(SysFile, "SetFileAttributes",
          "SetFileAttributesW", ctypes.winapi_abi,
                     /*return*/         Type.zero_or_nothing,
                     /*fileName*/       Type.path,
                     /*fileAttributes*/ Type.DWORD_FLAGS);

        advapi32.declareLazyFFI(SysFile, "GetNamedSecurityInfo",
          "GetNamedSecurityInfoW", ctypes.winapi_abi,
                     /*return*/       Type.DWORD,
                     /*objectName*/   Type.path,
                     /*objectType*/   Type.DWORD,
                     /*securityInfo*/ Type.DWORD,
                     /*sidOwner*/     Type.PSID.out_ptr,
                     /*sidGroup*/     Type.PSID.out_ptr,
                     /*dacl*/         Type.PACL.out_ptr,
                     /*sacl*/         Type.PACL.out_ptr,
                     /*securityDesc*/ Type.PSECURITY_DESCRIPTOR.out_ptr);

        advapi32.declareLazyFFI(SysFile, "SetNamedSecurityInfo",
          "SetNamedSecurityInfoW", ctypes.winapi_abi,
                     /*return*/       Type.DWORD,
                     /*objectName*/   Type.path,
                     /*objectType*/   Type.DWORD,
                     /*securityInfo*/ Type.DWORD,
                     /*sidOwner*/     Type.PSID,
                     /*sidGroup*/     Type.PSID,
                     /*dacl*/         Type.PACL,
                     /*sacl*/         Type.PACL);

        libc.declareLazyFFI(SysFile, "LocalFree",
          "LocalFree", ctypes.winapi_abi,
                     /*return*/       Type.HLOCAL,
                     /*mem*/          Type.HLOCAL);
     };

     exports.OS.Win = {
       File: {
           _init:  init
       }
     };
   })(this);
}