#
# Author:: Jay Mundrawala(<jdm@chef.io>)
# Copyright:: Copyright 2015-2016, Chef Software
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative "api/net"
require_relative "error"
require_relative "../mixin/wide_string"

class Chef
  module ReservedNames::Win32
    class Net
      include Chef::ReservedNames::Win32::API::Error
      extend Chef::ReservedNames::Win32::API::Error

      include Chef::ReservedNames::Win32::API::Net
      extend Chef::ReservedNames::Win32::API::Net

      include Chef::Mixin::WideString
      extend Chef::Mixin::WideString

      def self.default_user_info_3
        ui3 = USER_INFO_3.new.tap do |s|
          { usri3_name: nil,
            usri3_password: nil,
            usri3_password_age: 0,
            usri3_priv: 0,
            usri3_home_dir: nil,
            usri3_comment: nil,
            usri3_flags: UF_SCRIPT | UF_DONT_EXPIRE_PASSWD | UF_NORMAL_ACCOUNT,
            usri3_script_path: nil,
            usri3_auth_flags: 0,
            usri3_full_name: nil,
            usri3_usr_comment: nil,
            usri3_parms: nil,
            usri3_workstations: nil,
            usri3_last_logon: 0,
            usri3_last_logoff: 0,
            usri3_acct_expires: -1,
            usri3_max_storage: -1,
            usri3_units_per_week: 0,
            usri3_logon_hours: nil,
            usri3_bad_pw_count: 0,
            usri3_num_logons: 0,
            usri3_logon_server: nil,
            usri3_country_code: 0,
            usri3_code_page: 0,
            usri3_user_id: 0,
            usri3_primary_group_id: DOMAIN_GROUP_RID_USERS,
            usri3_profile: nil,
            usri3_home_dir_drive: nil,
            usri3_password_expired: 0,
          }.each do |(k, v)|
            s.set(k, v)
          end
        end
      end

      def self.net_local_group_add(server_name, group_name)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        buf = LOCALGROUP_INFO_0.new
        buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name)

        rc = NetLocalGroupAdd(server_name, 0, buf, nil)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_del(server_name, group_name)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        rc = NetLocalGroupDel(server_name, group_name)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_get_members(server_name, group_name)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        buf = FFI::MemoryPointer.new(:pointer)
        entries_read_ptr = FFI::MemoryPointer.new(:long)
        total_read_ptr = FFI::MemoryPointer.new(:long)
        resume_handle_ptr = FFI::MemoryPointer.new(:pointer)

        rc = ERROR_MORE_DATA
        group_members = []
        while rc == ERROR_MORE_DATA
          rc = NetLocalGroupGetMembers(
            server_name, group_name, 0, buf, -1,
            entries_read_ptr, total_read_ptr, resume_handle_ptr
          )

          nread = entries_read_ptr.read_long
          nread.times do |i|
            member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer +
                                                   (i * LOCALGROUP_MEMBERS_INFO_0.size))
            member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid])
            group_members << member_sid.to_s
          end
          NetApiBufferFree(buf.read_pointer)
        end

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end

        group_members
      end

      def self.net_user_add_l3(server_name, args)
        buf = default_user_info_3

        args.each do |k, v|
          buf.set(k, v)
        end

        server_name = wstring(server_name)

        rc = NetUserAdd(server_name, 3, buf, nil)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_user_get_info_l3(server_name, user_name)
        server_name = wstring(server_name)
        user_name = wstring(user_name)

        ui3_p = FFI::MemoryPointer.new(:pointer)

        rc = NetUserGetInfo(server_name, user_name, 3, ui3_p)

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end

        ui3 = USER_INFO_3.new(ui3_p.read_pointer).as_ruby

        rc = NetApiBufferFree(ui3_p.read_pointer)

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end

        ui3
      end

      def self.net_user_set_info_l3(server_name, user_name, info)
        buf = default_user_info_3

        info.each do |k, v|
          buf.set(k, v)
        end

        server_name = wstring(server_name)
        user_name = wstring(user_name)

        rc = NetUserSetInfo(server_name, user_name, 3, buf, nil)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_set_info(server_name, group_name, comment)
        server_name = wstring(server_name)
        group_name = wstring(group_name)
        comment = wstring(comment)

        buf = LOCALGROUP_INFO_1.new
        buf[:lgrpi1_name] = FFI::MemoryPointer.from_string(group_name)
        buf[:lgrpi1_comment] = FFI::MemoryPointer.from_string(comment)

        rc = NetLocalGroupSetInfo(server_name, group_name, 1, buf, nil)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_user_del(server_name, user_name)
        server_name = wstring(server_name)
        user_name = wstring(user_name)

        rc = NetUserDel(server_name, user_name)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_add_member(server_name, group_name, domain_user)
        server_name = wstring(server_name)
        group_name = wstring(group_name)
        domain_user = wstring(domain_user)

        buf = LOCALGROUP_MEMBERS_INFO_3.new
        buf[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(domain_user)

        rc = NetLocalGroupAddMembers(server_name, group_name, 3, buf, 1)

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.members_to_lgrmi3(members)
        buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
        Array.new(members.size) do |i|
          member_info = LOCALGROUP_MEMBERS_INFO_3.new(
            buf + i * LOCALGROUP_MEMBERS_INFO_3.size
          )
          member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
          member_info
        end
      end

      def self.net_local_group_add_members(server_name, group_name, members)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        lgrmi3s = members_to_lgrmi3(members)
        rc = NetLocalGroupAddMembers(
          server_name, group_name, 3, lgrmi3s[0], members.size
        )

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_set_members(server_name, group_name, members)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        lgrmi3s = members_to_lgrmi3(members)
        rc = NetLocalGroupSetMembers(
          server_name, group_name, 3, lgrmi3s[0], members.size
        )

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_local_group_del_members(server_name, group_name, members)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        lgrmi3s = members_to_lgrmi3(members)
        rc = NetLocalGroupDelMembers(
          server_name, group_name, 3, lgrmi3s[0], members.size
        )

        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_use_del(server_name, use_name, force = :use_noforce)
        server_name = wstring(server_name)
        use_name = wstring(use_name)
        force_const = case force
                      when :use_noforce
                        USE_NOFORCE
                      when :use_force
                        USE_FORCE
                      when :use_lots_of_force
                        USE_LOTS_OF_FORCE
                      else
                        raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]"
                      end

        rc = NetUseDel(server_name, use_name, force_const)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end

      def self.net_use_get_info_l2(server_name, use_name)
        server_name = wstring(server_name)
        use_name = wstring(use_name)
        ui2_p = FFI::MemoryPointer.new(:pointer)

        rc = NetUseGetInfo(server_name, use_name, 2, ui2_p)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end

        ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby
        NetApiBufferFree(ui2_p.read_pointer)

        ui2
      end

      def self.net_use_add_l2(server_name, ui2_hash)
        server_name = wstring(server_name)
        group_name = wstring(group_name)

        buf = USE_INFO_2.new

        ui2_hash.each do |(k, v)|
          buf.set(k, v)
        end

        rc = NetUseAdd(server_name, 2, buf, nil)
        if rc != NERR_Success
          Chef::ReservedNames::Win32::Error.raise!(nil, rc)
        end
      end
    end
    NetUser = Net # For backwards compatibility
  end
end
