=begin
Timba language interpreter.
Copyright (C) 2016  Nicolas Guzzo

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.

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
=end
require './parser.rb'
require 'thread'
Thread.abort_on_exception=true
def puts(s)
    super(s)
    STDOUT.flush
end
def pp(o)
	super(o)
	STDOUT.flush
end

class Timba
	attr_reader :stacks
	attr_reader :card
	attr_reader :mutex
	attr_reader :condition
	attr_reader :error

	def initialize(v,e)
		@mutex=Mutex.new
		@view=v
		@editor=e
		Struct.new("Card", :num, :suit,:status)
		@timba_parser = TimbaParser.new  
		@suits=[:oros,:copas,:espadas,:bastos]
		@stacks=[]
		@card=nil
		@condition=	""
		@error=""
		@abort=false
		@delay=0.5
		@running=false
		@wait_step=false
		@thread=nil
		@condition_var = ConditionVariable.new
		@condition_var2 = ConditionVariable.new

		#parse_file("tests/test1.tba")
	end
	def wait
		update
		d=0.0
		ws=false
		@mutex.synchronize {
       		d= @delay;
       		ws=@wait_step
        }
        if ws
        	@mutex.synchronize {    		
    			@condition_var.wait(@mutex)
    		}
    	else
    		sleep d;
    	end    	
    end
	def set_delay delay 
		@mutex.synchronize {
			@delay=delay
			puts "delay changed to #{@delay}"
		}
	end
	def clear
		@mutex.synchronize {
			@card=nil
			@abort=false
			@stacks=[]
			@condition=""
			@error=""
		}
		Qt.execute_in_main_thread {  
    		@view.update 
    	}
    end
	def parse_file(fl)
		f=File.open(fl,"r")		
		parse(f.read)
		f.close		
	end
	def parse(str)
		clear
		return if str == ""
		s=str.force_encoding("UTF-8").gsub(/#.*\n/,"\n").gsub(/[\t]+/," ").downcase
		begin
	  		@tree=@timba_parser.parse(s, reporter: Parslet::ErrorReporter::Deepest.new)
	  		#pp @tree
	  		#pp @tree[:program]			
			#pp @tree[:declarations]
	  		gen_stacks()
			
  		rescue Parslet::ParseFailed => failure
    		e= failure.cause.ascii_tree
    		puts e
    		ll= e.scan(/(.*)\sline\s+(\d+)\s+char\s+(\d+)/)    		
    		err=""
    		errors=[]
    		ll.each do |er|
    			err="#{er[0]} [ line #{er[1]}, col #{er[2]} ] "
    			errors<<er[1].to_i
    		end 
    		
    		errors.delete_at(0) if(errors.count > 1)

    		@editor.mark_errors(errors)
    		@mutex.synchronize {
    			@error="error de sintaxis, #{err}"
				@abort=true
				@running=false
			}
			update
		end
	end
	def gen_card c
		if c[:inverted]!=nil
			inv=:up
		else
			inv=:down
		end
		Struct::Card.new(c[:num].to_i,c[:suit].to_sym,inv)
	end
	def gen_stack decl
		st={}
		st[:name]=decl[:stack][:name].to_s	
		if not decl[:stack][:empty]
			l=[]
			if decl[:stack][:list].is_a?(Array)
				decl[:stack][:list].each do |c|
					l<< gen_card(c)
				end
			end
			if decl[:stack][:list].is_a?(Hash)
				c=decl[:stack][:list]
				l<< gen_card(c)
			end
			st[:list]=l
		else
			st[:list]=[]
		end
		@stacks<<st			
	end 
	def gen_stacks
		@stacks=[]
		if @tree[:declarations].is_a?(Array) 
			@tree[:declarations].each do |decl|
				gen_stack(decl)
			end
		end
		if @tree[:declarations].is_a?(Hash) 
			gen_stack(@tree[:declarations])			
		end
		#pp @stacks
		update
	end
	def run
		if not @running
			@running=true
			parse(@editor.get_text())
			return if abort?
			@thread=Thread.new {				
				run_prog
			}			
		end
	end
	def next()
		@mutex.synchronize {
			@wait_step=true
		}
		run
		@mutex.synchronize {    		
    		@condition_var.signal
    	}
	end
	def stop()
		@mutex.synchronize {
			@wait_step=false
			@running=false
			@abort=true
			@error=""
			if @running
				@condition_var2.wait(@mutex)
			end
		}
	end
	def run_prog
		begin
	    	@tree[:program].each do |op|
	    		return if abort?
	    		run_op(op) 
	    	end
	    rescue Exception => e
	    	@mutex.synchronize {
	    		@error= "ruby exception: #{e} "
	    		puts "EXCEPTION: #{e.inspect}"
    			puts "MESSAGE: #{e.message} "
    			puts e.backtrace
	    	}
	    end
    	@mutex.synchronize {
			@condition="fin"
			@condition_var2.signal
		}
		@running=false
		update
    end
	def get_stack(n)		
		stk=nil
		#begin
		@stacks.each do |s|
			if s[:name]==n
				stk=s[:list]
				return stk
			end
		end
		#rescue Exception => e
    	#	puts e
    	#end
		return stk
	end
	def take(st)
		@mutex.synchronize {
		s=get_stack(st)
		if s!=nil		
			if not s.empty?			  
			  if @card ==nil
			  	@card=s.pop
			  else
			  	@error="no puedo tomar, ya tengo carta en la mano"
			  	@abort=true
			  end
			else
				@error="la pila esta vacia"
				@abort=true
			end
		else
			@error="pila invalida"
			
			@abort=true
		end
	    }	    
	    wait
	end
	def invert
		@mutex.synchronize {
		if @card!=nil
			if @card.status==:up
				@card.status=:down
			else
				@card.status=:up
			end
		else
			@error="no se ha tomado carta"			
			@abort=true
		end
	   }	   
	   wait
	end
	def depositar(st)
		@mutex.synchronize {
		if @card!=nil
			s=get_stack(st)
			if s!=nil
			  s<< @card			  
			  @card=nil
			else
			  @error="pila invalida"			  
			  @abort=true
			end
		else
			@error="no se ha tomado carta"			
			@abort=true
		end
		}		
		wait
	end
	def h_line op		
		begin
			@current_line,col = op.line_and_column
			#puts @current_line
			Qt.execute_in_main_thread {  
				@editor.go_to(@current_line)
			}
		rescue Exception => e
			puts e
		end
	end
	def run_op op
		#pp op
		return if abort?
		if op[:take] != nil
			h_line(op[:take][:name])
			take(op[:take][:name].to_s)
		end
		if op[:deposit] != nil
			h_line(op[:deposit][:name])
			depositar(op[:deposit][:name].to_s)
		end
		if op[:invert] != nil
			h_line(op[:invert])
			invert
		end
		if op[:if] != nil
			h_line(op[:if][:si])
			wait
			if cond( op[:if][:cond] )
				return if abort?
				op[:if][:on_true] .each do |iop|					
					return if abort?
					run_op(iop)
				end
			else
				return if abort?
				op[:if][:on_false] .each do |iop|					
					return if abort?
					run_op(iop)
				end
			end
			h_line(op[:if][:nadamas])
			wait
		end
		if op[:while] != nil			
			h_line(op[:while][:mientras])
			wait			
			while cond( op[:while][:cond] )
				return if abort?
				op[:while][:on_true] .each do |wop|					
					return if abort?
					run_op(wop)
				end
			end				
		end
		
	end
	def cond(cnd)
		#pp cnd
		@mutex.synchronize {
			@condition=""
		}
		res=nil
		if cnd.is_a?(Array)
			expr=""
			@condition="("
			cnd.each do |c|				
				expr+=" and " if c[:and]
				expr+=" or "  if c[:or]
				@condition+=" Y " if c[:and]
				@condition+=" O " if c[:or]
				expr+= simple_cond(c[:cond_simple]).to_s	
				
			end
			res=eval(expr)
			@condition+=") (#{bool_s(res)})"
			#puts @condition
			return res
			#res=
		end		
		if cnd.is_a?(Hash)
			res=simple_cond(cnd[:cond_simple])
		end
		#@mutex.synchronize {
		#		puts "condition: #{@condition}"
		#}
		return res
	end

	def simple_cond(cnd)
		#pp cnd
		
		if cnd[:cond_empty] !=nil			
			n=cnd[:cond_empty][:name].to_s
			s=nil
			@mutex.synchronize {
				s=get_stack(n)
				@condition+="pila #{n} "
			}
			if s != nil				
				e=nil
				@mutex.synchronize {
					e=(s.empty?)
				}
				#puts "cond #{n} #{s.empty?}"
				if cnd[:cond_empty][:not_empty] !=nil					
					@mutex.synchronize {
						ee=(not e)
						@condition+="no esta vacia (#{bool_s(ee)})"						
					}

					update
					return (not s.empty?)
				end
				if cnd[:cond_empty][:empty]
					@mutex.synchronize {
						@condition+="esta vacia (#{bool_s(e)})"						
					}
					update
					return (s.empty?)
				end
			end
	    end
	    if cnd[:cond_suit] !=nil or cnd[:cond_suit_top] !=nil
    		
			suit=nil
			c=nil
			@mutex.synchronize { 
    			if @card==nil 
    				@error="no se ha tomado carta"
    				@abort=true
    			end	    	    
	        }
	        update
	        return false if abort?
	        @mutex.synchronize { 
				if @card.status!=:up						
					@error="la carta esta boca abajo, no se puede comparar el palo"
					@abort=true
				end    			
	        }
	        update
	        return false if abort?
			if cnd[:cond_suit] !=nil 
				c=cnd[:cond_suit]
				suit=cnd[:cond_suit][:suit].to_sym
				@mutex.synchronize {					
					if c[:is] != nil 
						@condition+="carta es de palo #{c[:suit]} (#{bool_s(@card.suit == suit)})"
						return (@card.suit == suit)
					end
					if c[:isnot] !=nil 
						@condition+="carta no es de palo #{c[:suit]} (#{bool_s(@card.suit != suit)})"
						return (@card.suit != suit)
					end
				}
			end
			if cnd[:cond_suit_top] !=nil 
				c=cnd[:cond_suit_top] 
				st=get_stack(c[:name])
				if st!=nil and not st.empty? 
					if st.last.status == :up
						suit=st.last.suit.to_sym
						if c[:is] != nil 
							@mutex.synchronize { 
    							@condition+="carta es de #{c[:is]} palo que tope de pila <#{c[:name]}> (#{bool_s(@card.suit == suit)})"
    							return (@card.suit == suit)
    						}
    					end
    					if c[:isnot] !=nil 
    						@mutex.synchronize { 
    							@condition+="carta es de #{c[:isnot]} palo que tope de pila <#{c[:name]}> (#{bool_s(@card.suit != suit)})"
    							return (@card.suit != suit)
    						}
    					end
						
					else							
						@mutex.synchronize {  
							@error="la carta en tope de pila <#{c[:name]}> esta boca abajo"														
							@abort=true
						}
						update
						return false if abort?
					end
				else
					@mutex.synchronize {  
						@error="pila vacia"						
						@abort=true
					}
					update
					return false if abort?
				end
			end
			
	    end
	    if cnd[:cond_status] !=nil
    		st=nil
    		if @card==nil	    			
				@error="no se ha tomado carta"
				update
				abort_prog()
				return false
			end
    		@mutex.synchronize {
	    		if cnd[:cond_status][:is] !=nil 
	    			@condition+="carta esta boca abajo (#{bool_s(bool_s(@card.status == :down))})"
					return (@card.status == :down)
				end
				if cnd[:cond_status][:isnot] !=nil
					@condition+="carta no esta boca abajo (#{bool_s(bool_s(@card.status != :down))})" 
					return (@card.status != :down)
				end
			}	    	
	    end
	    if cnd[:cond_val] !=nil or cnd[:cond_val_top]
    		cnum=nil
    		@mutex.synchronize {
    		if @card==nil	    			
				@error="no se ha tomado carta"				
				@abort=true
			else				
    			cnum=@card.num
    		end
    		}
    		update
    		return false if abort?
    		tmp=nil
			if cnd[:cond_val] != nil or cnd[:cond_val_top]!=nil
				num=nil
				c=nil
				if cnd[:cond_val] !=nil 
					c=cnd[:cond_val]
					num=cnd[:cond_val][:num].to_i 
				end
				if cnd[:cond_val_top] !=nil 
					c=cnd[:cond_val_top] 
					st=get_stack(c[:name])
					if st!=nil and not st.empty? 
						if st.last.status == :up
							num=st.last.num
						else
							@mutex.synchronize {
								@error="la carta en tope de pila <#{c[:name]}> esta boca abajo"
								@abort=true
							}
							update
							return false if abort?
						end
					else
						@mutex.synchronize {
							@error="pila vacia"
						}
						update
						return false if abort?
					end
				end
				
				if c[:equal] !=nil					
					tmp=(cnum==num)
					@mutex.synchronize {
						@condition+="#{cnum} == #{num} (#{bool_s(tmp)})" 
					}
				end
				if c[:notequal] !=nil					
					tmp=(cnum!=num)
					@mutex.synchronize {
						@condition+="#{cnum} != #{num} (#{bool_s(tmp)})" 
					}
				end
				if c[:grtthan] !=nil					
					tmp=(cnum>num)
					@mutex.synchronize {
						@condition+="#{cnum} > #{num} (#{bool_s(tmp)})" 
					}
				end
				if c[:lessthan] !=nil					
					tmp=(cnum < num)
					@mutex.synchronize {
						@condition+="#{cnum} < #{num} (#{bool_s(tmp)})" 
					}
				end
				if c[:grteqthan] !=nil					
					tmp=(cnum >= num)
					@mutex.synchronize {
						@condition+="#{cnum} >= #{num} (#{bool_s(tmp)})" 
					}
				end
				if c[:lesseqthan] !=nil					
					tmp=(cnum <= num)
					@mutex.synchronize {
						@condition+="#{cnum} <= #{num} (#{bool_s(tmp)})" 
					}
				end    				
    		end	
    		if tmp==nil	    	
    			@mutex.synchronize {
					@error="condicion nula"
					@abort=true
				}				
				return false
			end			
			
    		if c[:is] != nil 
    			return tmp
    		end
			if c[:isnot] != nil 
				return (not tmp)
			end
	    end	
	    abort_prog()
	    wait
		return false
	end
	def update
		Qt.execute_in_main_thread {  
    		@view.update 
    	}
    end
	def bool_s(c)
		if c
			"Verdadero"
		else
			"Falso"
		end
	end	
	def abort_prog()
		@mutex.synchronize {
			@abort=true
		}
    end
	def abort?
		ab=false
		@mutex.synchronize {		  
		  ab=@abort
		}
		#puts "abort: #{ab}"
		return ab
	end
end


