#!/usr/bin/env ruby

if $debug != false
	require 'Yk/path_aux'
	require 'Yk/auto_escseq'
	require 'binding_of_caller'
	require 'Yk/eval_alt'
	#require 'Yk/inot'

	$cmdline = ([$0.basename] + ARGV).join(" ")
#	class Exception
#	    alias message_org message
#	    alias backtrace_org backtrace
#	    def ft
#	    	if backtrace[0] =~ /^(.*)?:(\d+):in/
#	    		"#{$1.basename}:#{$2}"
#	    	end
#	    end
#	    def message
#	        "---msg-start#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] #{ft} " + message_org
#	    end
#	    def backtrace
#	    	caller_locations.each do |ln|
#	    	#	system "echo 1"
#	    	end
#	    	backtrace_org#.map{|e| e + "---msg-start#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] "}
#	    end
#	end

DEBUG_FILES = {}

	$ldif = 0

	class TZDebug
		def initialize
		end
		def line f, l
			loc = caller_locations(1)[0]
			$alt_path = f
			$ldif = l - loc.lineno
		end
		def > f
			f = f.expand_path
			f.touch
			f.truncate
			$debugFileName.write f
		end
		def >> f
			f = f.expand_path
			f.touch
		#	if f.file_size > 4000000
		#		d = f.read
		#		f.write d[-4000000..-1]
		#	end
			$debugFileName.write f
		end
		def < pid
			$__orgPid__cw__.write [pid].pack("S*")
			$__orgPid__cw__.flush
		end
		Escseq::Colors.each_with_index do |e, i|
			col = e.to_s
			capCol = e.to_s.capitalize
			class_eval %{
				#{capCol} = "\\x1b[#{i + 30}m"
				Bg#{capCol} = "\\x1b[#{i + 40}m"
				def #{col} *args, &bl
					STDERR.write #{capCol}
					begin
						TZDebug.p *args, &bl
					ensure
						STDERR.write Default
						STDERR.write BgDefault
						STDERR.write "\r\n"
					end
				end
				def bg#{capCol} *args, &bl
					STDERR.write Bg#{capCol}
					STDERR.write Black
					begin
						TZDebug.p *args, &bl
					ensure
						STDERR.write Default
						STDERR.write BgDefault
						STDERR.write "\r\n"
					end
				end
			}
		end
		@@onStack = []
		def self.on
			@@onStack.empty? ? @@onStack.push(true) : (@@onStack[-1] ||= true)
		end
		def self.off
			@@onStack.empty? ? @@onStack.push(false) : (@@onStack[-1] &&= false)
		end
		def self.p *exprs, &bl
			if bl
				@@onStack.push exprs[0]
				begin
					ret = bl.call
				ensure
					@@onStack.pop
				end
				return ret
			end
			if !@@onStack.empty? && !@@onStack[-1]
				return TZDebug.new
			end
			noLn = noTitle = false
			if Escseq::Colors.index exprs[0] 
				col = exprs.shift
			end
			locCnt = 1
			begin
				locCnt += 1
				loc = caller_locations(locCnt)[0]
			end until !["Yk/with.rb", "Yk/debug2.rb"].include?(loc.path.split(/\//)[-2..-1] * "/")
			out = ""
			if loc.path == "-" || loc.path == "(eval)" || loc.path == "-e"
				if $alt_path
					pth = $alt_path
				end
			else
				pth = loc.path
			end
			if pth
				title = "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] #{pth.basename}:#{loc.lineno + $ldif}"
				ln = (DEBUG_FILES[pth] ||= IO.read(pth).force_encoding(Encoding::ASCII_8BIT).split(/\n/).unshift(""))[loc.lineno + $ldif]
				if ln =~ /(^|\s)p\b/
					args = $'.strip
					case args
					when /^\.line\b/, /^\>/
						out = nil
					when ""
						out = ""
					when /^\>/
						noTitle = true
						noLn = true
						out = nil
					when /^\.([\w]+)\b/
#						noTiltle = true
#						noLn = true
						func = $1
						if exprs.size == 0
							case $'.strip_comment
							when ""
								if caller_locations(1)[0].path == "(eval)"
									out = "***"
									noTitle = true
									noLn = true
								else
									out = nil
									noLn = true
								end
							elsif /\bdo$/
								col = func
								noTitle = true
								out = nil
								noLn = true
							else
								col = func
								out = nil
								noLn = true
							end
						else
							left = $'.strip_comment
							right = exprs.inspect[1..-2]
							if left != right
								out = "#{left} = #{right}"
							else
								out = "#{left}"
							end
							noTitle = true
							noLn = true
						end
					else
						if col
							args = args.split(/\s*,\s*/)[1..-1].join(", ")
						end
						left = args
						right = exprs.inspect[1..-2]
						if left != right
							out = "#{left} = #{right}"
						else
							out = "#{left}"
						end
					end
				end
				if !noTitle
					o = title + " "
					o = col ? (o.split[0...-1] + [o.split[-1].method(col).call]).join(" ") + " " : o
					STDERR.write o
				end
				if out
					if col
						STDERR.write out.method(col).call
					else
						STDERR.write out
					end
				end
				if !noLn
					STDERR.write "\r\n"
				end
			end
			return TZDebug.new
		end
		def self.pinit
			fr, fw = IO.pipe
			if STDERR.tty?
				lnk = "/proc/#{$$}/fd/#{STDERR.to_i}".readlink
				fe = lnk.open File::WRONLY
			else
				fe = STDERR.dup
			end
			pinfo = "" # "[#{$cmdline}:#{$$}]"
			STDERR.reopen fw
			if STDERR.respond_to? :dont_use_select
				STDERR.dont_use_select
			end
			$debugFileName = "#{ENV['HOME']}/.tmp/Yk/site_ruby/debug2.rb".check_dir / rand.to_s + ".file"
			wPids = [$$]
			cr, $__orgPid__cw__ = IO.pipe
			gr, gw = IO.pipe
			pid = fork do
				fw.close
				gr.close
				$__orgPid__cw__.close
				Process.daemon true, true
				gw.writeln $$.to_s
				gw.close
				#fe.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] started debug attachment to #{wPids.join(' ')}\n"
				begin
					buff = ""
					residue = ""
					dw = dfw = nil
					debFile = ""
					interrupted = false
					loop do
						necand = nil
						serr = false
						unexpected = false
						unexpectedNext = false
						unexpectedLine = nil
						waitReaders = []
						waitReaders.push fr if fr
						waitReaders.push cr if cr
						if fr == nil && cr == nil
							break
						end
						r = nil
						if !interrupted
							begin
								r = IO.select waitReaders, [], [], 10
							rescue Exception
								interrupted = true
							end
						end
						trap :USR1 do
							if dw
								dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] #{wPids[0]} exited.\n"
								dw.flush
								Process.kill :INT, $$
							end
						end
						if r && r[0].delete(fr)
							begin
								if debFile != (tmp = $debugFileName.read rescue nil)
									if tmp && tmp._w?
										dfw.close if dfw
										dfw = tmp.open "a"
										debFile = tmp
									end
								end
								if dfw
									dw = dfw
								else
									dw = fe
								end
								fr.readpartial 4096, buff
								buff = residue + buff
								residue.replace ""
								locked = false
								begin
									outlines = []
									head = "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{wPids[0]}]"
									handleSerr = ->msg{
										if msg =~ /\s*(\/.*):(\d+)\s+syntax error, /
											serr = true
											msg.replace($1.basename + $2 + ":" + $')
											if msg =~ /unexpected local variable or method, /
												unexpected = true
												msg.reaplace $` + $'
											end
										end
									}
									buff.each_line do |ln|
										if ln[-1] == "\n"
											if unexpected
												unexpected = false
												unexpectedNext = true
												unexpectedLine = ln
												outlines[-1][3] += ln
											elsif unexpectedNext
												unexpectedNext = false
												s, e = ln.getStartEnd
												outlines[-1][3] += ln
												usym = unexpectedLine[s .. e]
												if ri = outlines[-1][3].rindex("expecting")
													outlines[-1][3].insert ri, "`#{usym}', "
												end
											elsif serr && handleSerr.(ln)
												outlines[-1][3] += ln
											elsif ln =~ /^Did you mean\? /
												necand = $'.strip
											elsif ln =~ /(^|\s*from )((\.+\/|\/)[^\s]+)(:(\d+):in \`(block in |)(\<(module|class)|(.*?)\'))/
												isFirst = $1 == ""
												fName = $2
												lno = $5
												msg = $'
												func = (($8 && "<#{$8}>") || $9).strip
												func.gsub! /(\w)\s+(\W)/, '\1\2'
												func.gsub! /(\W)\s+(\w)/, '\1\2'
												msg = msg.strip
												if msg =~ /^:(.*)\((.*?)\)$/
													err, msg = $2, $1.strip
													if msg =~ /\s*undefined local variable or method \`(.*?)\' for\s*/
														msg = "#{$'}::`#{$1}'"
													end
													handleSerr.(msg)
													msg = "#{head} #{err}: #{msg}\n"
												elsif msg =~ /^:/
													msg = "#{head} #{$'.strip}\n"
												elsif !msg.empty?
													msg = head + " " + msg + "\n"
												end
												if !outlines.empty? && outlines[-1][0..1] == [fName, lno] && msg == ""
													outlines[-1][2].push func
												else
													outlines.push [fName, lno, [func], msg]
												end
											else
												if dw != fe && !locked
													dw.flock File::LOCK_EX
													locked = true	
												end
												dw.write ln
											end
										else
											residue += ln
										end
									end
									prevFName = nil
									outlines.each do |fName, lno, func, msg|
										if fName == prevFName
											pFName = " " * fName.basename.size + ":"
										else
											pFName = fName.basename + ":"
										end
										lcontent = " " + String::LInfo::getFileLine(fName, lno.to_i).strip rescue ""
										pos = pFName + lno + ":(" + func * "," + ")"
										if dw != fe && !locked
											dw.flock File::LOCK_EX
											locked = true	
										end
										if necand
											msg = msg.chomp + " is `#{necand}'?\n" 
											necand = nil
										end
										dw.write msg + "> " + pos + lcontent.ln
										prevFName = fName
									end
								ensure
									dw.flush
									dw.flock File::LOCK_UN if dw != fe && locked
								end
							rescue EOFError
								fr = nil
							rescue Exception
								interrupted = true
							end
						end
						if r && r[0].delete(cr)
							begin
								if !cr.read 4, buff
									raise EOFError
								end
								if buff || buff != ""
									tpid, rpid = buff.unpack("S*")
									i = wPids.index(tpid)
									if i
										wPids[i] = rpid
										dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] watching pid, #{tpid} changed to #{rpid}\n"
										dw.flush
									else
										wPids.push rpid
										dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] added new pid, #{rpid}\n"
										dw.flush
									end
									#pinfo = "[#{$cmdline}:#{$__orgPid__}]"
								end
							rescue EOFError
								cr = nil
							end
						end
						if !r
							if interrupted
								dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] interrupted.\n"
								dw.flush
							end
							wPidsExistList = wPids.map{|e|"/proc/#{e}"._e?}
							#dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] watching, #{wPids.inspect} => #{wPidsExistList.inspect}\n"
							dw.flush
							found = false
							wPids.each do |e|
								if "/proc/#{e}"._e?
									if "/proc/#{e}/stat".read.split[2] == "Z"
										dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] " + "pid, #{e} has become Zombie.\n".red
										dw.flush
									else
										found = true
									end
								end
							end
							if !found
								dw.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] break\n"
								dw.flush
								break
							end
						end
					end
					#fe.write "#{Time.now.strftime('%X.%3N')} [#{$cmdline}:#{$$}] exitting debug attachment\n"
				ensure
#					$debugFileName.unlink
				end
			end
			gw.close
			$__degugWatchDogPid = gr.readline.to_i
			gr.close
			at_exit{
#				Process.kill :USR1, $__degugWatchDogPid
			}
			Process.detach pid
			fr.close
			cr.close
		end
		pinit
	end


	def p *exprs, &bl
		TZDebug.p *exprs, &bl
	end




	module Process
		class << self
			alias orgDaemon daemon
			def daemon *args
				prev = $$
				Process.orgDaemon *args
				$__orgPid__cw__.write [prev, $$].pack("S*")
				$__orgPid__cw__.flush
			end
		end
	end



	if $0 == __FILE__
		p.blue 1 + 2
		eval "
	#		p >> '~/debug2'
			p.line '#{__FILE__}', #{__LINE__}
			p.bgCyan 3 + 4, 5 + 9
			p.bgGreen
			p :red, 5 + 6
			p
		"
	end


else

	require 'Yk/path_aux'
	class TZDebug
		def initialize
		end
		def method_missing *args
			return
		end
	end
	def p *args
		return TZDebug.new
	end

end