require 'fileutils'

module Albacore
  module Tasks

    # Project Lint is a task to check for simple errors in msbuild/project files
    module ProjectLint

      class Config
        attr_accessor :project
        # a list of regex patterns for files in the filesystem to ignore
        attr_accessor :ignores
      end

      # since msbuild projects have a habit of ignoring case differences, lets use downcase for comparison
      # in windows / and \ can sometimes be used interchangeably 
      class FileReference
        attr_reader :file, :downcase_and_path_replaced
        def initialize file
          @file = file
          @downcase_and_path_replaced = @file.downcase.gsub(/\//,'\\')
        end
        def ==(other)
          other.downcase_and_path_replaced == @downcase_and_path_replaced
        end
        alias_method :eql?, :==
        def hash
          @downcase_and_path_replaced.hash
        end
        def to_s
          @file
        end
      end

      def self.new *sym
        c = Albacore::Tasks::ProjectLint::Config.new
        yield c if block_given?      
        
        body = proc {
          p = Project.new c.project
          ignores = c.ignores 
          ignores = [] if ignores == nil
          ignores += [/^bin/i, /^obj/, # bin, obj usually contain result of compilation
            /csproj$/, /fsproj$/, # project files should not be part of a project file 
            /\.user$/, /\.suo$/ # user settings, source control user settings 
          ]

          files = p.included_files.select { |file|
            file.link == nil
          }.map {|file| FileReference.new(file.include) } 
          srcfolder = File.dirname(c.project)
          fsfiles = nil
          FileUtils.cd (srcfolder) {
            fsfiles = Dir[File.join('**','*.*')].select { |file|
              ! ignores.any? { |r| file.match(r) }
            }.map { |file|
              FileReference.new(file)
            }
          }

          failure_msg = []
          (files-fsfiles).tap do |list|
            if (list.length>0)
              failure_msg.push("- Files in #{c.project} but not on filesystem: \n#{list}")
            end
          end
          (fsfiles-files).tap do |list|
            if (list.length>0)
              failure_msg.push("+ Files not in #{c.project} but on filesystem: \n#{list}")
            end
          end
          fail failure_msg.join("\n") if failure_msg.length>0
        }

        Albacore.define_task *sym, &body
      end


    end
  end
end
