=begin
	$Id: main.rb,v 1.1 2005-10-04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
=end

# ENV["RUBY_VERBOSE_GC"]="yes"

# this file gets loaded by main.c upon startup
# module GridFlow is supposed to be created by main.c
# this includes GridFlow.post_string(s)

# because Ruby1.6 has no #object_id and Ruby1.8 warns on #id
unless Object.instance_methods(true).include? "object_id"
	class Object; alias object_id id end
end

# in case of bug in Ruby ("Error: Success")
module Errno; class E000 < StandardError; end; end

#$post_log = File.open "/tmp/gridflow.log", "w"
$post_log = nil

class Array
	def split(elem)
		r=[]
		j=0
		for i in 0...length
			(r<<self[j,i-j]; j=i+1) if self[i]==elem
		end
		r<<self[j,length-j]
	end
end

module GridFlow #------------------

def self.post(s,*a)
	post_string(sprintf("%s"+s,post_header,*a))
	($post_log << sprintf(s,*a); $post_log.flush) if $post_log
end

class<<self
	attr_accessor :data_path
	attr_accessor :post_header
	attr_accessor :verbose
	attr_reader :fobjects
	attr_reader :fclasses
	attr_reader :cpu_hertz
	attr_reader :subprocesses
	attr_reader :bridge_name
	alias gfpost post
end

@subprocesses={}
@verbose=false
@data_path=[]
if GridFlow.respond_to? :config then
  @data_path << GridFlow.config["PUREDATA_PATH"]+"/extra/gridflow/images"
end

def self.hunt_zombies
	#STDERR.puts "GridFlow.hunt_zombies"
	# the $$ value is bogus
	begin
		died = []
		subprocesses.each {|x,v|
			Process.waitpid2(x,Process::WNOHANG) and died<<x
		}
	rescue Errno::ECHILD
	end
	#STDERR.puts died.inspect
	died.each {|x| subprocesses.delete x }
end

def self.packstring_for_nt(nt)
	case nt
	when :u, :u8,  :uint8; "C*"
	when :s, :i16, :int16; "s*"
	when :i, :i32, :int32; "l*"
	when :f, :f32, :float32; "f*"
	when :d, :f64, :float64; "d*"
	else raise "no decoder for #{nt.inspect}"
	end
end

self.post_header = "[gf] "

def self.gfpost2(fmt,s); post("%s",s) end

if GridFlow.bridge_name then
  post "This is GridFlow #{GridFlow::GF_VERSION} within Ruby version #{RUBY_VERSION}"
  post "base/main.c was compiled on #{GridFlow::GF_COMPILE_TIME}"
  post "Please use at least 1.6.6 if you plan to use sockets" if RUBY_VERSION<"1.6.6"
end

if not GridFlow.bridge_name then
  require "gridflow/bridge/placebo"
end

Brace1 = "{".intern
Brace2 = "}".intern
Paren1 = "(".intern
Paren2 = ")".intern

def self.parse(m)
	m = m.gsub(/(\{|\})/," \\1 ").split(/\s+/)
	m.map! {|x| case x
		when Integer, Symbol; x
		when /^[+\-]?[0-9]+$/; x.to_i
		when String; x.intern
		end
	}
	m
end

def self.stringify_list(argv)
	argv.map {|x| stringify x }.join(" ")
end

def self.stringify(arg)
	case arg
	when Integer, Float, Symbol; arg.to_s
	when Array; "{#{stringify_list arg}}"
	end
end

::Object.module_eval do def FloatOrSymbol(x) Float(x) rescue x.intern end end

# adding some functionality to that:
class FObject
	@broken_ok = false
	@do_loadbangs = true
	class<<self
		# global
		attr_accessor :broken_ok
		# per-class
		attr_reader :ninlets
		attr_reader :noutlets
		attr_accessor :do_loadbangs
		attr_accessor :comment
		def foreign_name; @foreign_name if defined? @foreign_name end
	end
	def post(*a) GridFlow.post(*a) end
	def self.subclass(*args,&b)
		qlass = Class.new self
		qlass.install(*args)
		qlass.module_eval(&b)
	end
	alias :total_time :total_time_get
	alias :total_time= :total_time_set
	attr_writer :args # String
	attr_accessor :argv # Array
	attr_reader :outlets
	attr_accessor :parent_patcher
	attr_accessor :properties
	attr_accessor :classname
	def initialize2; end
	def args
		if defined? @args
			@args
		else
			"[#{self.class} ...]"
		end
	end
	alias info args
	def connect outlet, object, inlet
		@outlets ||= []
		@outlets[outlet] ||= []
		@outlets[outlet].push [object, inlet]
	end
	def self.name_lookup sym
		qlasses = GridFlow.fclasses
		qlass = qlasses[sym.to_s]
		if not qlass
			return qlasses['broken'] if @broken_ok
			raise "object class '#{sym}' not found"
		end
		qlass
	end
	def self.[](*m)
		o=nil
		if m.length==1 and m[0] =~ / /
			o="[#{m[0]}]"
			m=GridFlow.parse(m[0]) 
		else
			o=m.inspect
		end
		GridFlow.handle_braces!(m)
		ms = m.split ','.intern
		m = ms.shift
		qlass = m.shift
		qlassname = qlass.to_s
		qlass = name_lookup qlass.to_s unless Class===qlass
		r = qlass.new(*m)
		r.classname = qlassname
		GridFlow.post "%s",r.args if GridFlow.verbose
		for x in ms do r.send_in(-2, *x) end if FObject.do_loadbangs
		r
	end
	def inspect
		if args then "#<#{self.class} #{args}>" else super end
	end
	def initialize(*argv)
		s = GridFlow.stringify_list argv
		@argv = argv
		@args = "["
		@args << (self.class.foreign_name || self.to_s)
		@args << " " if s.length>0
		@args << s << "]"
		@parent_patcher = nil
		@properties = {}
		@init_messages = []
	end
end

class FPatcher < FObject
	class << self
		attr_reader :fobjects
		attr_reader :wires
	end
	def initialize(*)
		super
		fobjects = self.class.fobjects
		wires = self.class.wires
		@fobjects = fobjects.map {|x| if String===x then FObject[x] else x.call end }
		@inlets = []
		@ninlets = self.class.ninlets or raise "oops"
		i=0
		@fobjects << self
		while i<wires.length do
			a,b,c,d = wires[i,4]
			if a==-1 then
				a=self
				@inlets[b]||=[]
				@inlets[b] << [@fobjects[c],d]
			else
				if c==-1 then
					@fobjects[a].connect b,self,d+@ninlets
				else
					@fobjects[a].connect b,@fobjects[c],d
				end
			end
			i+=4
		end
	end
	def method_missing(sym,*args)
		sym=sym.to_s
		if sym =~ /^_(\d)_(.*)/ then
			inl = Integer $1
			sym = $2.intern
			if inl<@ninlets then
			raise "#{inspect} has not @inlets[#{inl}]" if not @inlets[inl]
				for x in @inlets[inl] do
				 x[0].send_in x[1],sym,*args end
			else
				send_out(inl-@ninlets,sym,*args)
			end
		else super end
	end
end

def GridFlow.estimate_cpu_clock
	u0,t0=GridFlow.rdtsc,Time.new.to_f; sleep 0.01
	u1,t1=GridFlow.rdtsc,Time.new.to_f; (u1-u0)/(t1-t0)
end

begin
	@cpu_hertz = (0...3).map {
		GridFlow.estimate_cpu_clock
	}.sort[1] # median of three tries
rescue
	GridFlow.post $!
end

def GridFlow.find_file s
	s=s.to_s
	if s==File.basename(s) then
		dir = GridFlow.data_path.find {|x| File.exist? "#{x}/#{s}" }
		if dir then "#{dir}/#{s}" else s end
	elsif GridFlow.respond_to? :find_file_2
		GridFlow.find_file_2 s
	else
		s
	end
end

def GridFlow.macerr(i)
  begin
    f=File.open("/System/Library/Frameworks/CoreServices.framework/"+
      "Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/"+
      "MacErrors.h")
    while f.gets
      m = /^\s*(\w+)\s*=\s*(-\d+),\s*\/\*\s*(.*)\s*\*\/$/.match $_
      next if not m
      if m[2].to_i == i then return "#{m[2]}: \"#{m[3]}\"" end
    end
    return "no error message available for this error number"
  rescue FileError
    return "Can't find Apple's precious copyrighted list of error messages on this system."
  ensure
    f.close if f	
  end
end

end # module GridFlow

class IO
  def nonblock= flag
    bit = Fcntl::O_NONBLOCK
    state = fcntl(Fcntl::F_GETFL, 0)
    fcntl(Fcntl::F_SETFL, (state & ~bit) |
      (if flag; bit else 0 end))
  end
end

def protect
  yield
rescue Exception => e
  STDERR.puts "#{e.class}: #{e}"
  STDERR.puts e.backtrace
end

def GridFlow.load_user_config
	require "gridflow/bridge/puredata.rb" if GridFlow.bridge_name == "puredata"
	user_config_file = ENV["HOME"] + "/.gridflow_startup"
	begin
		load user_config_file if File.exist? user_config_file
	rescue Exception => e
		GridFlow.post "#{e.class}: #{e}:\n" + e.backtrace.join("\n")
		GridFlow.post "while loading ~/.gridflow_startup"
	end
end

require "gridflow/base/flow_objects.rb"
require "gridflow/format/main.rb"

%w(
  # #for #finished #type #dim #transpose #perspective #store #outer
  #grade #redim #import #export #export_list #cast
  #scale_by #downscale_by #draw_polygon #draw_image #layer
  #print #pack #export_symbol #rotate
  #in #out
).each {|k|
	GridFlow::FObject.name_lookup(k).add_creator k.gsub(/#/,"@")
}

END {
	GridFlow.fobjects.each {|k,v| k.delete if k.respond_to? :delete }
	GridFlow.fobjects.clear
	GC.start
}