require 'strscan'
require 'stringio'
require 'request'


MAX_COLOR=10

class XMLScanner < StringIO
    def initialize(text)
	super(text)
    end
    def read_brackets
	string=getch
	if string!="<" then return "" end
	while !eos?&&string.count("<")!=string.count(">")
	    string.<<scan_until(/>/)
	end
	string
    end
end

class BibleVerse
    attr_reader :number
    HEAD=-1
    def initialize(book,book_number,chapter,number,content,bible)
	@bible=bible
	@book=book
	@book_number=book_number
	@chapter=chapter.to_i
	@number=if number=="head" then HEAD else number.to_i end
	@text=content
    end
    # returns the total number of matches
    def match_expression(search,quick_search=nil)
	if quick_search
	end
	mat=0
	search.each {|se| 
	    nb=0;
	    if se.negative
		return 0 if @text.match(se.regexp)
	    else @text.scan(se.regexp){nb+=1};
		if nb==0 then return 0 else mat+=nb end
	    end
	}
	return mat
    end
    def write(buffer,iter,search_arr,main=true)
	if main then buffer.insert(iter,"#{@book} #{(@chapter==BibleChapter::NONE)?"":"#{@chapter}"}#{(@number==HEAD)?" -- Suscription":"v#{@number}"} ","bold")
	else buffer.insert(iter,"\t")
	end
	if main&&search_arr
	    search=search_arr.reject{|se| se.negative}.map{|se| se.regexp}
	    index=0
	    matching_index=Array::new(search.length,-1)
	    matching_length=Array::new(search.length,0)
	    forw=Array::new(search.length,0)
	    old_color=-1
            color=-1
	    next_index=@text.length
	    next_matching=-1
	    color_length=100000
	    (0..search.length-1).each do |i|
		ind=@text.index(search[i],0)
		if ind&&ind<next_index
		    next_matching=i
		    next_index=ind
		end
	    end
	    while index<@text.length
		if (next_index>index+color_length)
		    text=@text[index..index+color_length-1]
		    index+=color_length
		    buffer.insert(iter,"#{text}","target#{color%MAX_COLOR}")
		    color=-1
		elsif next_index>index
		    text=@text[index..next_index-1]
		    index=next_index
		    if color>=0
			buffer.insert(iter,"#{text}","target#{color%MAX_COLOR}")
		    else
			buffer.insert(iter,"#{text}")
		    end
		end
		###############
		color_length=100000
		color=-1
		(0..search.length-1).each do |i|
		    if @text.index(search[i],index)==index
			match=@text[index..-1].match(search[i]).to_s
			matching_index[i]=index
			matching_length[i]=match.length
			color=i
		    end
		end
		if color==-1
		    (0..matching_index.length-1).inject(index){|first,ind| if matching_index[ind]<=first&&matching_index[ind]+matching_length[ind]>index then color=ind;matching_index[ind] else first end}
		end
		color_length=(0..matching_index.length-1).inject(100000){|len,ind| mend=matching_index[ind]+matching_length[ind]; if matching_index[ind]<=index&&mend>index&&mend<index+len then mend-index else len end}
		next_index=@text.length
		next_matching=-1
		(0..search.length-1).each do |i|
		    ind=@text.index(search[i],index+1)
		    if ind&&ind<next_index
			next_matching=i
			next_index=ind
		    end
		end
	    end
	    buffer.insert(iter,"\n")
	else
	    buffer.insert(iter,"#{@text}\n")
	end
	return
    end
    def write_to_file(file)
	file.puts "#{@book} #{(@chapter==BibleChapter::NONE)?"":"#{@chapter}"}#{(@number==HEAD)?" -- Suscription":"v#{@number}"}: #{@text}"
    end
    def reference
	Reference::new(@book_number,@chapter,@number)
    end
end

class BibleChapter
    NONE=0
    attr_reader :number
    def initialize(book,book_number,number,ssc,bible)
	@book=book
	@bible=bible
	@number=if number=="none" then NONE else number.to_i end
	@verses=Array::new
	while !ssc.eof?
	    l=ssc.readline
	    break if l.match(/<\/chapter>/) 
	    l.scan(/\s*<verse\s+number="(\d+|head)"\s*>(.*)<\/verse\s*>/){|n,t| @verses.push(BibleVerse::new(@book,book_number,@number,n.to_s,t.to_s,bible))}
	end
    end

    def show_ref
	@verses
    end
    def search_expression(search,quick_search=nil)
	rep=Array::new
	matches=0
	@verses.each {|v| mat=v.match_expression(search,quick_search); if mat>0 then rep.push(v.reference);matches+=mat; end}
	[rep,matches]
    end
    # OK
    def get_verse(ref)
	verse=@verses.find{|v| v.number==ref.verse}
	if !verse then nil else verse end
    end
    def expand_ref(ref,verses)
	ret=Array::new
	@verses.each {|vr| if (ref.verse==Reference::ALL||vr.number==ref.verse) then 
	    if verses then ret.push vr
	    else ret.push(vr.reference) 
	    end end}
	ret
    end
end

class BibleBook
    attr_reader :title,:number,:last_matches
    def initialize(title,number,ssc,bible)
	@title=title
	@number=number
	@chapters=Array::new
	@bible=bible
	#puts "Livre: #{title}"
	while !ssc.eof?
	    l=ssc.readline
	    break if l.match(/<\/book>/) 
	    if l.match(/<chapter/)
		number=""
		l.scan(/number=\"(.*)\"/){|n| number=n.to_s}
		@chapters.push(BibleChapter::new(@title,@number,number,ssc,bible))
	    end
	end
	return
    end

    def list_chapters
	@chapters.compact.map{|ch| ch.number}
    end
    def show_ref(chapter)
	if chapter>=0 then
	    @chapters[chapter].show_ref
	else
	    rep=Array::new
	    @chapters.each {|c| rep.concat(c.show_ref())}
	    rep
	end
    end
    def search_expression(search,quick_search=nil)
	rep=Array::new
	matches=0
	@chapters.each {|c| ans=c.search_expression(search,quick_search);rep.concat ans[0];matches+=ans[1];}
	[rep,matches]
    end
    def noc
	@chapters.length
    end
    # OK
    def get_verse(ref)
	chapter=@chapters.find{|ch| ch.number==ref.chapter}
	if !chapter then nil else chapter.get_verse(ref) end
    end
    def expand_ref(ref,verses)
	ret=Array::new
	@chapters.each {|ch| if (ref.chapter==Reference::ALL||ch.number==ref.chapter) then ret.concat(ch.expand_ref(ref,verses)) end}
	ret
    end
end

class BibleWhole
    attr_reader :xml_name,:equiv_class
    def initialize(text_file,lang_file="")
	#@xml_name=text_file.gsub(".xml","").gsub(/.*\//,"")
	@valid=false
	@xml_name=text_file.gsub(/.*\//,"")
	@bible_name="Sans nom"
	@lang="Français"
	@inputenc="ascii"
	@equiv_class=Array::new
	content=""
	@books=Array::new
	begin
	    file=open(text_file)
	    content=file.read
	    file.close
	rescue
	    Gtk::timeout_add(0){print_warning "Impossible d'ouvrir le fichier #{text_file}";}
	    return
	end
	ssc=XMLScanner::new(content)
	ssc.readline
	l=ssc.readline
	puts l
	if l.match(/^<bibliquest_concordance_bible/) then
	    l.gsub!(/^<bibliquest_concordance_bible/,"")
	    l.gsub!(/>\s*$/,"")
	    tab=l.scan(/\s*(\w+)="(.*?)"/){|par,val| 
		case par.to_s
		when "name": @bible_name=val.to_s
		when "lang": @lang=val.to_s
		when "inputenc": @inputenc=val.to_s
		end
	    }
	end
	#puts "Chargement de la concordance #{@bible_name}!"
	dtime=0
	while !ssc.eof?
	    l=ssc.readline
	    l.scan(/<equiv-class\s*>(.*)<\/equiv-class\s*>/){|cl| @equiv_class.push cl.to_s}
	    if l.match(/<book/)
		title=""
		number=0
		l.scan(/title=\"(.*)\"/){|t| title=t.to_s}
		l.scan(/number=\"([0-9]*)\"/){|t| number=t.to_s.to_i}
		#puts number
		@books[number]=(BibleBook::new(title,number,ssc,self))
	    end
	end
	puts "Chargement réussi de la concordance #{@bible_name}!"
	@valid=true
	return
    end

    def show_ref(book,chapter)
	@books[book].show_ref(chapter)
    end

    # OK
    def valid?
	@valid
    end
    def book_refs_list
	@books.map{|b| [b,b.title,b.list_chapters]}
    end
    def mini_books_list
	@books.map{|b| [@books.index(b),b.title,b.list_chapters]}
    end
    def get_verse(ref)
	if !@books[ref.book] then nil else @books[ref.book].get_verse(ref) end
    end
    def expand_ref(ref,verses=false)
	if !@books[ref.book] then nil else @books[ref.book].expand_ref(ref,verses) end
    end
    def list_verses(list)
	list.map{|b| b.book.expand_ref(Reference::WHOLE,false)}.flatten
    end
    def each_book(&bloc)
	@books.each {|b| yield(b)}
    end
end

