require 'fileutils'
require 'pathname'

require 'mxx_ru/cpp'

require File.join( File.dirname( __FILE__ ), 'util' )
require File.join( File.dirname( __FILE__ ), 'paths' )

module ACE_wrappers

module Builders

class UnsupportedToolsetEx < MxxRu::Ex
  def initialize( toolset_name )
    super( "Unsupported toolset: #{toolset_name}" )
  end
end

class UnsupportedLibraryTypeEx < MxxRu::Ex
  def initialize( library_type )
    super( "Unsupported library type: #{library_type}" )
  end
end

class BuildersRegistry
  include Singleton
  Pair = Struct.new( :klass_name_prefix, :predicate )

  def initialize
    @klasses = []
  end

  def add( klass_name_prefix, &predicate )
    @klasses << Pair.new( klass_name_prefix, predicate )
  end

  def find( toolset )
    p = @klasses.find do |k| k.predicate.call( toolset ) end
    p ? p.klass_name_prefix : nil
  end
end

def Builders.create_builder( target, library_type )
  raise UnsupportedLibraryTypeEx.new( library_type ) if
      ![ :Static, :Shared ].member?( library_type )

  klass_name_prefix = BuildersRegistry.instance.find( target.toolset )
  raise UnsupportedToolsetEx.new( target.toolset.name ) unless klass_name_prefix

  klass = eval "#{klass_name_prefix}#{library_type}"
  klass.new( target )
end

FILE_COPIER =
  lambda do |src, dest|
    Util.delete_if_exists( dest )

    MxxRu.show_brief( "copying #{src} -> #{dest}" )
    FileUtils.cp( src, dest )
  end

SYMLINK_MAKER =
  lambda do |src, dest|
    Util.delete_if_exists( dest )

    full_src_name = Pathname.new( src )
    full_src_name = (Pathname.pwd + full_src_name).realpath if
        full_src_name.relative?

    MxxRu.show_brief( "making symlink #{full_src_name} -> #{dest}" )
    FileUtils.ln_s( full_src_name, dest )
  end

class BuildResultDescriptor
  attr_reader :src, :dest

  def initialize( src, dest, copier )
    @src = src
    @dest = dest
    @copier = copier
  end

  def copy
    @copier.call( @src, @dest )
  end
end

class AbstractBuilder 
  include Paths

  def initialize( target, required_sub_config )
    @target = target
    setup_common_params( required_sub_config )
  end

  def build
    do_in_ace_subdir do
      MxxRu::AbstractTarget.run(
        make_build_cmdlines, [],
        'building ACE via native build method' )
    end

    state = copy_if_necessary( make_build_result_names )

    setup_target_dependencies

    state
  end

  def clean
    do_in_ace_subdir do
      MxxRu::AbstractTarget.run(
        make_clean_cmdlines,
        [],
        'cleaning ACE via native build method' )
    end
    
    delete_if_exists( make_build_result_names )
  end

protected

  def setup_common_params( required_sub_config )
    ENV[ 'ACE_ROOT' ] ||= full_ace_root_name
    @target.include_path( RELATIVE_ACE_ROOT, MxxRu::Cpp::Target::OPT_UPSPREAD )
    @target.threading_mode( MxxRu::Cpp::THREADING_MULTI )

    ensure_valid_config_h( required_sub_config )
  end

  def ensure_valid_config_h( required_sub_config )
    search_regex = Regexp.compile(
        "^#include\\s+\"#{required_sub_config}\"$" )

    config_file_name = from_relative_ace_root( 'ace/config.h' )

    valid = File.exists?( config_file_name ) &&
        nil != File.readlines( config_file_name ).find { |line|
          nil != ( search_regex =~ line )
        }
    if !valid
      MxxRu.show_brief( "making ace/config.h for #{required_sub_config}" )
      File.open( config_file_name, 'w' ) do |io|
        io.puts "#include \"#{required_sub_config}\""
      end
    end
  end

  # Returns MxxRu::TargetState::REBUILT if some of specified files updated.
  # Returns MxxRu::TargetState::EXISTS if no file updated.
  def copy_if_necessary( descriptors )
    updated = 0
    descriptors.each do |d|
      s = MxxRu::TargetState.detect( d.dest, [ d.src ] )
      if MxxRu::TargetState::EXISTS != s.state
        updated += 1
        # File must be copied
        if !MxxRu::Util::Mode.instance.is_dry_run
          d.copy
        end
      end
    end

    MxxRu::TargetState.new( 0 == updated ?
        MxxRu::TargetState::EXISTS :
        MxxRu::TargetState::REBUILT )
  end

  # Removes all target files.
  def delete_if_exists( descriptors )
    descriptors.each do |d|
      Util.delete_if_exists( d.dest )
    end
  end

  def AbstractBuilder.define_build_result_name_maker(
      what, obj_placement_method, path, &short_name_maker )
    define_method "#{what}_name_maker__".to_sym, &short_name_maker
    class_eval %Q{
      def #{what}
        unless @#{what}
          @#{what} = File.join(
              @target.mxx_obj_placement.#{obj_placement_method}(
                  '#{path}', @target.toolset, @target ),
              #{what}_name_maker__ )
        end
        @#{what}
      end
    }
  end

  def make_result_copy_descriptor( src, dest )
    BuildResultDescriptor.new( src, dest, FILE_COPIER )
  end

  def make_result_symlink_descriptor( src, dest )
    BuildResultDescriptor.new( src, dest, SYMLINK_MAKER )
  end
end

module ACEAsStaticLibFunctionality
  def setup_static_lib_params( target )
    target.define( 'ACE_AS_STATIC_LIBS', MxxRu::Cpp::Target::OPT_UPSPREAD )
  end

  def setup_win32_required_libs( target )
    target.mxx_add_required_lib( 'user32' )
    target.mxx_add_required_lib( 'advapi32' )
    target.mxx_add_required_lib( 'ws2_32' )
    target.mxx_add_required_lib( 'netapi32' )
    target.mxx_add_required_lib( 'mswsock' )
  end

  def setup_unix_required_libs( target )
#FIXME: возможно, здесь придется обрабатывать особенности конкретной платформы.
    target.mxx_add_required_lib( 'pthread' )
    target.mxx_add_required_lib( 'dl' )
  end
end

module Win32PlatformFunctionality
  def setup_platform_params( target )
    target.define( 'WIN32', MxxRu::Cpp::Target::OPT_UPSPREAD )
  end
end

module UnixPlatformFunctionality
  def setup_platform_params( target )
  end
end

module GnuMakeFunctionality
  def setup_make_platform_macros( platform_macros )
    File.open(
      "#{full_ace_root_name}/include/makeinclude/platform_macros.GNU",
      "w" ) do |io|
      io.puts "include $(ACE_ROOT)/include/makeinclude/#{platform_macros}.GNU"
    end
  end

  def make_build_cmdlines
    [ "make #{configuration}" ]
  end

  def make_clean_cmdlines
    [ "make #{configuration} clean" ]
  end

  def configuration
    debug = MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode ? 0 : 1
    static = static_libs? ? 1 : 0
    shared = shared_libs? ? 1 : 0

    "static_libs=#{static} shared_libs=#{shared} debug=#{debug}"
  end

  def static_libs?
    false
  end

  def shared_libs?
    false
  end
end

class Vc7Base < AbstractBuilder
  include Win32PlatformFunctionality

  def initialize( target, project_name, sln_name )
    super( target, 'config-win32.h' )
    @project_name = project_name
    @sln_name = sln_name

    setup_platform_params( target )
    setup_vc_specific_params( target )
  end

protected 
  def setup_vc_specific_params( target )
    target.compiler_option( '/Zc:wchar_t', MxxRu::Cpp::Target::OPT_UPSPREAD )
  end

  def make_build_cmdlines
    [ "devenv /build #{configuration} /project #{@project_name} #{@sln_name}" ]
  end

  def make_clean_cmdlines
    [ "devenv /clean #{configuration} /project #{@project_name} #{@sln_name}" ]
  end

  def configuration
    MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode ?
      'Release' : 'Debug'
  end
end

class Vc7Shared < Vc7Base

  def initialize( target, project_name = 'ACE', sln_name = 'ace.sln' )
    super( target, project_name, sln_name )
  end

protected 
  def setup_target_dependencies
    @target.mxx_add_full_target_name( dll_name )
    @target.mxx_add_required_lib( lib_name )
  end

  def make_build_result_names
    if MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode
      [
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACE.dll' ), dll_name ),
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACE.lib' ), lib_name )
      ]
    else
      [
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEd.dll' ), dll_name ),
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEd.lib' ), lib_name ),
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEd.pdb' ), pdb_name )
      ]
    end
  end

  define_build_result_name_maker( :dll_name, :get_dll, '.' ) {
    MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode ?
        'ACE.dll' : 'ACEd.dll'
  }
  define_build_result_name_maker( :lib_name, :get_lib, 'lib' ) {
    MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode ?
        'ACE.lib' : 'ACEd.lib'
  }
  define_build_result_name_maker( :pdb_name, :get_dll, '.' ) { 'ACEd.pdb' }

end

class Vc8Shared < Vc7Shared
  def initialize( target, project_name = 'ACE', sln_name = 'ace_vc8.sln' )
    super( target, project_name, sln_name )
  end
end

class Vc7Static < Vc7Base
  include ACEAsStaticLibFunctionality

  def initialize( target, project_name = 'ACE_Static', sln_name = 'ace_static.sln' )
    super( target, project_name, sln_name )
    setup_static_lib_params( target )
  end

protected 
  def setup_target_dependencies
    @target.mxx_add_full_target_name( lib_name )
    @target.mxx_add_required_lib( lib_name )
    setup_win32_required_libs( @target )
  end

  def make_build_result_names
    if MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode
      [
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEs.lib' ), lib_name )
      ]
    else
      [
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEsd.lib' ), lib_name ),
        make_result_copy_descriptor(
            from_relative_ace_root( 'lib/ACEsd.pdb' ), pdb_name )
      ]
    end
  end

  define_build_result_name_maker( :lib_name, :get_lib, 'lib' ) {
    MxxRu::Cpp::RUNTIME_RELEASE == @target.mxx_runtime_mode ?
        'ACEs.lib' : 'ACEsd.lib'
  }
  define_build_result_name_maker( :pdb_name, :get_dll, '.' ) { 'ACEsd.pdb' }

end

class Vc8Static < Vc7Static
  def initialize( target, project_name = 'ACE_Static', sln_name = 'ace_static_vc8.sln' )
    super( target, project_name, sln_name )
  end
end

class GccOnWin32Base < AbstractBuilder
  include Win32PlatformFunctionality
  include GnuMakeFunctionality

  def initialize( target, required_sub_config, platform_macros )
    super( target, required_sub_config )
    setup_platform_params( target )
    setup_make_platform_macros( platform_macros )
  end
end

class GccOnWin32SharedBase < GccOnWin32Base
protected
  def setup_target_dependencies
    @target.mxx_add_full_target_name( dll_name )
    @target.mxx_add_required_lib( 'ACE.dll' )
    @target.mxx_add_required_lib_path( File.dirname( lib_name ) )
  end

  def make_build_result_names
    [
      make_result_copy_descriptor(
          from_relative_ace_root( 'ace/libACE.dll' ), dll_name ),
      make_result_copy_descriptor(
          from_relative_ace_root( 'ace/libACE.dll.a' ), lib_name )
    ]
  end

  def shared_libs?
    true
  end

  define_build_result_name_maker( :dll_name, :get_dll, '.' ) { 'libACE.dll' }
  define_build_result_name_maker( :lib_name, :get_lib, 'lib' ) { 'libACE.dll.a' }
end

class GccOnWin32StaticBase < GccOnWin32Base
  include ACEAsStaticLibFunctionality

  def initialize( target, required_sub_config, platform_macros )
    super( target, required_sub_config, platform_macros )
    setup_static_lib_params( target )
  end

protected
  def setup_target_dependencies
    @target.mxx_add_full_target_name( lib_name )
    @target.mxx_add_required_lib( 'ACE' )
    @target.mxx_add_required_lib_path( File.dirname( lib_name ) )
    setup_win32_required_libs( @target )
  end

  def make_build_result_names
    [
      make_result_copy_descriptor(
          from_relative_ace_root( 'ace/libACE.a' ), lib_name )
    ]
  end

  def static_libs?
    true
  end

  define_build_result_name_maker( :lib_name, :get_lib, 'lib' ) { 'libACE.a' }
end

class GccOnUnixBase < AbstractBuilder
  include UnixPlatformFunctionality
  include GnuMakeFunctionality

  def initialize( target, required_sub_config, platform_macros )
    super( target, required_sub_config )
    setup_platform_params( target )
    setup_make_platform_macros( platform_macros )
  end
end

class GccOnUnixSharedBase < GccOnUnixBase
  def initialize( target, required_sub_config, platform_macros )
    super( target, required_sub_config, platform_macros )
    @precise_version = detect_precise_ace_version
  end

protected
  def setup_target_dependencies
    @target.mxx_add_full_target_name( dll_name )
    @target.mxx_add_required_lib( 'ACE' )
    @target.mxx_add_required_lib_path( File.dirname( dll_name ) )
  end

  def make_build_result_names
    [
      make_result_copy_descriptor(
          from_relative_ace_root( "ace/libACE.so.#{@precise_version}" ),
          dll_name ),
      make_result_symlink_descriptor(
          dll_name,
          symlink_name )
    ]
  end

  def shared_libs?
    true
  end

  define_build_result_name_maker( :dll_name, :get_dll, '.' ) {
      "libACE.so.#{@precise_version}" }
  define_build_result_name_maker( :symlink_name, :get_dll, '.' ) { 'libACE.so' }

  def detect_precise_ace_version
    /^This\s+is\s+ACE\s+version\s+([^,]+),/.match(
        IO.read( from_relative_ace_root( 'VERSION' ) ) )[ 1 ]
  end
end

class GccOnUnixStaticBase < GccOnUnixBase
  include ACEAsStaticLibFunctionality

  def initialize( target, required_sub_config, platform_macros )
    super( target, required_sub_config, platform_macros )
    setup_static_lib_params( target )
  end

protected
  def setup_target_dependencies
    @target.mxx_add_full_target_name( lib_name )
    @target.mxx_add_required_lib( 'ACE' )
    @target.mxx_add_required_lib_path( File.dirname( lib_name ) )
    setup_unix_required_libs( @target )
  end

  def make_build_result_names
    [
      make_result_copy_descriptor(
          from_relative_ace_root( "ace/libACE.a" ),
          lib_name ),
    ]
  end

  def static_libs?
    true
  end

  define_build_result_name_maker( :lib_name, :get_lib, 'lib' ) { 'libACE.a' }
end

def Builders.define_static_shared_class_pair(
    klass_name_prefix,
    super_klass_name_prefix,
    required_sub_config,
    platform_macros,
    &predicate )

  eval %Q{
    class #{klass_name_prefix}Static < #{super_klass_name_prefix}StaticBase
      def initialize( target )
        super( target, '#{required_sub_config}', '#{platform_macros}' )
      end
    end
    class #{klass_name_prefix}Shared < #{super_klass_name_prefix}SharedBase
      def initialize( target )
        super( target, '#{required_sub_config}', '#{platform_macros}' )
      end
    end
  }

  BuildersRegistry.instance.add( klass_name_prefix, &predicate )
end

BuildersRegistry.instance.add :Vc7 do |toolset|
      'vc' == toolset.name && '7' == toolset.tag( 'ver_hi' )
    end

BuildersRegistry.instance.add :Vc8 do |toolset|
      'vc' == toolset.name && '8' == toolset.tag( 'ver_hi' )
    end

define_static_shared_class_pair :MinGW32, :GccOnWin32,
    'config-win32.h', 'platform_mingw32' do |toolset|
      'gcc' == toolset.name && 'mingw' == toolset.tag( 'gcc_port' )
    end

define_static_shared_class_pair :CygwinGcc32, :GccOnWin32,
    'config-cygwin32.h', 'platform_cygwin32' do |toolset|
      'gcc' == toolset.name && 'cygwin' == toolset.tag( 'gcc_port' )
    end

# We must patch classes for CygwinGcc to disable WIN32 definition.
class CygwinGcc32Static
  def setup_platform_params( target )
  end
end

class CygwinGcc32Shared
  def setup_platform_params( target )
  end
end

define_static_shared_class_pair :GccOnLinux, :GccOnUnix,
    'config-linux.h', 'platform_linux' do |toolset|
      'gcc' == toolset.name && 'unix' == toolset.tag( 'gcc_port' ) &&
          'unix' == toolset.tag( 'target_os' ) &&
          'linux' == toolset.tag( 'unix_port' )
    end

end # module Builders

class Target < MxxRu::Cpp::Target
  def initialize( prj_alias, library_type )
    super( prj_alias )

    @builder = Builders.create_builder( self, library_type )
    @target_state = nil
  end

  def build
    @target_state = @builder.build unless @target_state

    @target_state
  end

  def clean
    if nil == @target_state
      @builder.clean

      @target_state = MxxRu::TargetState.new( MxxRu::TargetState::ABSENT )
    end
  end

  def reset
    @target_state = nil
  end
end

end # module ACE_wrappers

# vim:ts=2:sts=2:sw=2:expandtab

