#!/usr/bin/env ruby
begin
	# First, try to load our files from the source tree
	$: << "#{File.dirname(__FILE__)}/../lib"
	require 'ohcount'
rescue LoadError
	# Failing that, try to load from a gem
	require 'rubygems'
	require 'ohcount'
end

class OhcountCommandLine
	attr_accessor :paths

	def initialize(args=[])
		args = args.clone # Because shift is destructive
		set_option(args.shift) while args.first =~ /^-/
		assign_paths_and_files(args)
	end

	def assign_paths_and_files(args)
		@files = []
		@paths = []
		args.each do |file_or_path|
			if File.directory?(file_or_path)
				@paths << file_or_path
			else
				@files << file_or_path
			end
		end
		@paths = ['.'] if @files.empty? && @paths.empty?
	end

	def source_file_list
		@source_file_list ||= Ohcount::SourceFileList.new(:paths => @paths, :files => @files)
	end

	def annotate
		source_file_list.each_source_file do |s|
			s.parse do |language, semantic, line|
				puts "#{language}\t#{semantic}\t#{line}"
			end
		end
	end

	# Find all source code files
	def detect
		source_file_list.each_source_file do |s|
			puts "#{s.polyglot}\t#{s.filename}"
		end
	end

  # Licenses
  def licenses
		source_file_list.each_source_file do |s|
      next unless s.licenses.any?
			symbols = s.licenses.collect { |l| Ohcount::LicenseSniffer::LicenseMap.instance.map[l].symbol }.join(",")
			puts "#{symbols}\t#{s.filename}"
		end
  end

	def analyze_with_progress(what)
		count = 0
		source_file_list.analyze(what) do |s|
			if count % 100 == 0
				STDOUT.write('.')
				STDOUT.flush
			end
			count+=1
		end
		puts "\n"
	end

	def write_gestalt
		platforms = source_file_list.gestalt_facts.platforms
		platforms = platforms.any? ? platforms.collect { |p| p.to_s.split('::').last }.join(", ") : 'None'
		puts "Platforms detected: #{ platforms }"

		tools = source_file_list.gestalt_facts.tools
		tools = tools.any? ? tools.collect { |p| p.to_s.split('::').last }.join(", ") : 'None'
		puts "Tools detected: #{ tools }"
	end

  # Gestalt
	def gestalt
		analyze_with_progress(:gestalt)
		write_gestalt
	end

	def raw_entities
		source_file_list.each_source_file do |s|
			s.raw_entities do |language, entity, s, e|
				puts "#{language}\t#{entity}\t#{s}\t#{e}"
			end
		end
	end

	def help
		puts <<HELP
Usage: ohcount [option] [paths...]

Ohloh source code line counter command line tool.
   http://www.ohloh.net/

[option] can be one of the following:
   -a, --annotate
   -d, --detect
	 -re
   -h, --help
   -s, --summary

-a, --annotate                  Show annotated source code

   The contents of all source code files found within the given
   paths will be emitted to stdout. Each line will be prefixed with
   a tab-delimited language name and semantic categorization (code,
   comment, or blank).

-d, --detect                    Find source code files

   Recursively find all source code files within the given paths.
   For each source code file found, the file name will be emitted to
   stdout prefixed with a tab-delimited language name.

-h, --help                      Display this message

-g, --gestalt                   Project Properties

   Inspects project contents to determine what platform(s) the project
   runs on, as well as any detected tools/IDEs used to develop it.

-i, --individual                Count lines of code per file

   Count lines in all source code files within the given paths, and
   emit a report of the lines of code, comments, and blanks in each
   language per file.

-l, --license

   Displays detected licensing information contained in each source
   code file.

-re

   Prints raw entity information to the screen (mainly for debugging).

-s, --summary                   Count lines of code (default)

   Count lines in all source code files within the given paths, and
   emit a report of the total number of lines of code, comments,
   and blanks in each language. This is the default action.

[paths] can refer to any number of individual files or directories.
   Directories will be probed recursively. If no path is given,
   the current directory will be used.

HELP
	end

	def individual
		STDOUT.write "Examining #{source_file_list.size} file(s)"

		puts
		puts "Ohloh Line Count".center(76)
		puts "Language               Code    Comment  Comment %      Blank      Total  File"
		puts "----------------  ---------  ---------  ---------  ---------  ---------  -----------------------------------------------"

		source_file_list.analyze(:language) do |s|
			s.language_breakdowns.sort {|l1,l2| l2.name <=> l1.name}.each do |lb|
				write_individual_row(s.filename, lb.name, lb.code_count, lb.comment_count, lb.blanks)
			end
		end
	end

	def write_individual_row(file, name, code, comment, blank)
		printf("%-16s",name)
		printf(" %10d",code)
		printf(" %10d",comment)
		if comment+code > 0
			printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
		else
			printf("           ")
		end
		printf(" %10d",blank)
		printf(" %10d",code+comment+blank)
		printf("  %s\n", file)
	end

	def memoryleak
		puts "Parsing one file repeatedly. Watch your RAM vanish...."
		s = Ohcount::SourceCode.new(files.first, :filenames => files)
		while true
			s.parse {}
		end
	end

	def summary
		STDOUT.write "Examining #{source_file_list.size} file(s)"

		analyze_with_progress(:*)

		puts
		puts "Ohloh Line Count Summary".center(76)
		puts

		puts "Language          Files       Code    Comment  Comment %      Blank      Total"
		puts "----------------  -----  ---------  ---------  ---------  ---------  ---------"

		source_file_list.loc_list.locs.sort { |a,b| b.code <=> a.code}.each do |loc|
			write_summary_row(loc.language, loc.filecount, loc.code, loc.comments, loc.blanks)
		end

		puts "----------------  -----  ---------  ---------  ---------  ---------  ---------"
		write_summary_row('Total',
											source_file_list.loc_list.filecount,
											source_file_list.loc_list.code,
											source_file_list.loc_list.comments,
											source_file_list.loc_list.blanks)


		puts
		write_gestalt
	end

	def write_summary_row(name, file_count, code, comment, blank)
		printf("%-16s", name)
		printf(" %6d", file_count)
		printf(" %10d", code)
		printf(" %10d", comment)
		if comment+code > 0
			printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
		else
			printf("       0.0%");
		end
		printf(" %10d", blank)
		printf(" %10d\n", code+comment+blank)
	end

	def subcommand=(s)
		if @subcommand
			STDERR.puts "Error: Multiple commands specified."
			exit 1
		else
			@subcommand=s
		end
	end

	def subcommand
		@subcommand
	end

	def set_option(option)
		case option
		when '-s', '--summary'
			self.subcommand = :summary
		when '-d', '--detect'
			self.subcommand = :detect
		when '-a', '--annotate'
			self.subcommand = :annotate
		when '-g', '--gestalt'
			self.subcommand = :gestalt
		when '-i', '--individual'
			self.subcommand = :individual
    when '-l', '--licenses'
      self.subcommand = :licenses
    when '-m', '--memoryleak'
      self.subcommand = :memoryleak
		when '-e', '--entities'
			self.subcommand = :entities
		when '-re'
			self.subcommand = :raw_entities
		#when /-n([\w_]+)/
		#	@entities ||= []
		#	@entities << $1.to_sym
		#	self.subcommand = :entities unless @subcommand
		when '-?', '-h', '--help'
			self.subcommand = :help
		else
			STDERR.puts "Type 'ohcount -?' for usage."
			exit 1
		end
	end

	def run!
		self.subcommand ||= :summary
		if self.respond_to?(self.subcommand)
			self.send(self.subcommand)
		else
			STDERR.puts "Type 'ohcount -?' for usage."
			exit 1
		end
	end
end

OhcountCommandLine.new(ARGV).run!
