# Automatically generates FFI code reflecting headers.
#
# Author:: Victor Costan
# Copyright:: Copyright (C) 2009 Massachusetts Institute of Technology
# License:: MIT

require 'ffi'
require 'ffi/tools/const_generator'
require 'ffi/tools/struct_generator'
require 'set'

desc 'Regenerate ffi_autogen.rb'
task :ffi_header do
  Smartcard::Tasks.generate_ffi_header
end

# :nodoc: namespace
module Smartcard

module Tasks
  def self.resolve_constants(pattern, format)
    if FFI::Platform.mac?
      header_path = '/System/Library/Frameworks/PCSC.framework/Headers/'
    else
      header_path = '/usr/include/PCSC/'
    end
    headers = Dir.glob(header_path + '*.h').map { |f| File.basename f }

    consts = Set.new
    headers.each do |header|
      File.read("#{header_path}#{header}").each_line do |line|
        tokens = line.split
        next unless tokens[0] == '#define'
        next if tokens[1].index '('
        consts << tokens[1] if pattern =~ tokens[1]
      end
    end
    p consts

    const_gen = FFI::ConstGenerator.new(nil,
        :cppflags => "-w -I#{header_path}") do |g|
      headers.each { |header| g.include header }
      consts.each { |const| g.const const, format }
    end

    const_gen.constants
  end

  def self.output_constants(constants, f)
    constants.each do |name, const|
      f.write "    #{const.to_ruby}\n"
    end
  end

  def self.output_enum(enum_name, constants, name_regexp, f)
    f.write "  #{enum_name} = enum [\n"

    re = Regexp.new('^' + name_regexp + '$')
    constants.each do |name, const|
      ruby_name = re.match(name)[1]
      f.write "    :#{ruby_name.downcase}, Consts::#{name},\n"
    end

    f.write "  ]\n"
  end

  def self.generate_ffi_header
    File.open('lib/smartcard/pcsc/ffi_autogen.rb', 'w') do |f|
      f.write "# Automatically generated by tasks/ffi_codegen.rb\n\n"

      # Return codes.
      stat_consts = resolve_constants(/^SCARD_(E|S|W)_.*$/, '0x%08X')
      # Context scopes.
      scope_consts = resolve_constants(/^SCARD_SCOPE_/, '0x%08X')
      # Communication protocols (e.g. T=0).
      proto_consts = resolve_constants(/^SCARD_PROTOCOL_/, '0x%08X')
      # Reader sharing modes.
      share_consts = resolve_constants(/^SCARD_SHARE_/, '0x%08X')
      # Card dispositions.
      disp_consts = resolve_constants(/^SCARD_([^_]*)_CARD$/, '0x%08X')
      # Reader attributes.
      attr_consts = resolve_constants(/^SCARD_ATTR_/, '0x%08lX')
      # Card states.
      card_state_consts = resolve_constants(/^SCARD_STATE_/, '0x%08X')
      # Reader states.
      reader_state_consts = resolve_constants(/^SCARD_[^_]*$/, '0x%08X')
      # Misc constants.
      misc_consts = resolve_constants(/^MAX_ATR_SIZE|INFINITE$/, '0x%08X')

      f.write "# :nodoc: namespace\n"
      f.write "module Smartcard::PCSC\n\n"
      f.write "# :nodoc: auto-generated\n"
      f.write "module FFILib\n"

      f.write "  # Constant values extracted from headers.\n"
      f.write "  module Consts\n"
      output_constants stat_consts, f
      output_constants scope_consts, f
      output_constants proto_consts, f
      output_constants share_consts, f
      output_constants disp_consts, f
      output_constants attr_consts, f
      output_constants card_state_consts, f
      output_constants reader_state_consts, f
      output_constants misc_consts, f
      f.write "  end  # module Smartcard::PCSC::FFILib::Consts\n\n"

      f.write "  # Error codes returned by the PCSC functions.\n"
      output_enum 'Status', stat_consts, 'SCARD_._(.*)', f
      f.write "\n"

      f.write "  # Values for the context's scope.\n"
      output_enum 'Scope', scope_consts, 'SCARD_SCOPE_(.*)', f
      f.write "\n"

      f.write "  # Smart-card communication protocols.\n"
      output_enum 'Protocol', proto_consts, 'SCARD_PROTOCOL_(.*)', f
      f.write "\n"

      f.write "  # Smart-card sharing modes.\n"
      output_enum 'Share', share_consts, 'SCARD_SHARE_(.*)', f
      f.write "\n"

      f.write "  # Smart-card dispositions.\n"
      output_enum 'Disposition', disp_consts, 'SCARD_(.*)_CARD', f
      f.write "\n"

      f.write "  # Smart-card reader attributes.\n"
      output_enum 'Attribute', attr_consts, 'SCARD_ATTR_(.*)', f
      f.write "\n"

      f.write "  # Smart-card states.\n"
      output_enum 'CardState', card_state_consts, 'SCARD_STATE_(.*)', f
      f.write "\n"

      f.write "  # Smart-card reader states.\n"
      output_enum 'State', reader_state_consts, 'SCARD_(.*)', f
      f.write "\n"


      f.write "end  # module Smartcard::PCSC::FFILib\n"
      f.write "end  # namespace Smartcard::PCSC\n"
    end
  end
end  # namespace Smartcard::Tasks
end  # namespace Smartcard
