#!/usr/bin/ruby
# vim:set foldmethod=indent:
# vim:set sts=4:set sw=4:set ts=4:
# vim:set et:

$: << "../rdf/lib"
$: << "."
require 'jfif'
require 'exif'
require 'cgi'

include JFIF
include EXIF

require 'gtk2'
require 'glib2'

require 'gtk_forms'

# switch to UTF-8
$KCODE="U"

require 'windowutils.rb'


# this is the secondary window used to display sources of a Dnd
# drop in the application
class Thumbnail_Viewer < Gtk::Window
    def initialize opener, file_list=nil
		super()
		default_width = 32*WIDTH
		default_height = 32
		if file_list.length < WIDTH
			default_width = file_list.length * 32
		else
			default_height = 32*(file_list.length/WIDTH)
		end
		self.default_height = default_height
		self.default_width = default_width

		self.set_title("Thumbnail View")
		self.allow_shrink = false

		#container = Gtk::ScrolledWindow.new
		# non homogeneous Vbox, 0 pix of padding
		vb = Gtk::VBox.new

		@table = Gtk::Table.new file_list.length/WIDTH, WIDTH-1, true
		vb.pack_start @table, true, true
		@tx = 0
		@ty = 0

		add(vb)
		
		@file_list = file_list
        @image_list = Array.new
		@current_position = 0
        @current_frame =  nil
		@opener = opener

 
		show_all
		@idle = Gtk::idle_add { idle }
	end
	WIDTH = 10
	# do something when idle !
    def idle
        # finished all, stop this
        if @current_position > @file_list.length+1
            return false
        end
          
        position = @current_position
        file = @file_list[position]
        @current_position += 1
        begin
            ji = Image.new file, true
        rescue JFIF::FoundThumbException
        rescue Exception => e
            puts "File #{file}(#{@current_position}/#{@file_list.length}) is broken!\n"+e.to_s
            # show must go on
            # raise e
            return true
        end
        i = Gtk::Image.new("")
        if ji.thumbnail
            pic_loader = Gdk::PixbufLoader.new()
            pic_loader.write(ji.thumbnail.clone)
            pic_loader.close
            pix = pic_loader.pixbuf 
            ji = nil
        else
            pix = Gdk::Pixbuf.new(file)
        end
        (new_width, new_height) = Utils::new_size( pix.width.to_f, pix.height.to_f, 32,32)
        pix2 = pix.scale(new_width, new_height)
        i.pixbuf = pix2
        GC.start
        eventbox = Gtk::EventBox.new
        fr = Gtk::Frame.new
        
        fr.add(i)
        fr.shadow_type = Gtk::SHADOW_NONE
        eventbox.add(fr)
        
        press_callback = Proc.new{
            |w,e,data|
            if e && e.button != 1
                false
            else
                if @current_frame != nil
                    @current_frame.shadow_type = Gtk::SHADOW_NONE
                end
                fr.shadow_type = Gtk::SHADOW_OUT
                @current_frame = fr
                @current_loaded = position
                @opener.load_picture self, file
            end
        }
        eventbox.signal_connect('button-press-event', Gdk::Event::BUTTON_PRESS, &press_callback)

        @image_list[position] = press_callback

        @table.attach eventbox, @tx, @tx+1, @ty, @ty+1
        @tx += 1
        if @tx > WIDTH
                @tx = 0
                @ty += 1
        end
        eventbox.show_all
        return true
    end
    def next_image
        if @current_loaded+1 == @image_list.length
            @current_loaded = -1
        end
        @image_list[@current_loaded+1].call(nil,nil,nil)
    end
    def prev_image
        puts "prev!"
        if @current_loaded == 0
            @current_loaded = @image_list.length
        end
        @image_list[@current_loaded-1].call(nil,nil,nil)
    end
end

# constant for the RDF parser...
RDF_NS =   "http://www.w3.org/1999/02/22-rdf-syntax-ns#"

class NSException < Exception; end
class UnknowPropertyException < Exception; end


# question sur le RDF
# quel est le namespace du dublin core
# as-t-on interet a se limiter à un nombre fini de namespace ? (DC et tech)
# ai-je toujours le choix entre resource ou un literal ?
# pour les subjects, resource ou literal ?


# the main windows, displays an image
# accepts drop with list of files
# create a interface depending on the
# RDF inside the picture
class PhotoRDF < Gtk::Window
    require 'KeyBinding'
        # uses the basic RDF API
        begin 
		require 'rubygems'
		require_gem 'rubyrdf'
	rescue LoadError=>e
	end
	require 'basicrdf'
    require 'RDF4R/Model/Standard'


    require 'find'

    if RUBY_PLATFORM=~/linux/
	File_Match = /file:\/\/(.*)/
    else
        File_Match = /file:\/\/\/(.*)/
    end

    include WindowUtils
    include RDF4R::Model
	# translate an URL into a real file path
	def url_to_path url
		puts "URL : #{url}"
		# dos if url=~/file:\/\/\/(.*)/
		if url=~ File_Match
			path = $1
			path = CGI.unescape(path)
			path.gsub!(/\//,File::SEPARATOR)
            #path.gsub!(/ /,"\\ ")
			path
		else
			raise "Invalid url,not starting with file:// : \"#{url}\""
		end
	end
    # handle the drop on the application
    def handle_drop  widget, context, x, y, selection_data, info, time
        name = selection_data.data
        name = name.split(/\r\n/)
        @position = 0
        name.collect!{ |n| url_to_path n}
        new_name = Array.new
        name.each do |name|
            Find.find(name) do |file|
                next if file=~/ignore/ || file=~/\.xvpics/
                if file=~/\.jpe?g/i
                    new_name << file
                end
            end
        end
        info "got #{new_name.length} files from drop"
        if new_name.length == 1
            load_picture nil, new_name[0]
        else
            Thumbnail_Viewer.new(self, new_name).show
        end
        
        Gtk::Drag.finish(context, false, false, 0)
    end
    # say something in the statusbar
	def info(string)
		@statusbar.push 0, "Press #{@kbc.signal_to_key["display-config"]} to configure - #{string}"
	end
    # setup the Drag And Drop to accept
    # any list of files
    def enable_dnd
        target_table = [ ['text/uri-list', 0, 0] ]
        Gtk::Drag.dest_set(self , Gtk::Drag::DEST_DEFAULT_ALL, target_table,
        Gdk::DragContext::ACTION_COPY)

        signal_connect("drag_data_received") { |a,b,c,d,e,f,g| handle_drop a,b,c,d,e,f,g }
    end

    BINDING_FILE = "photordfbinding.conf"
    def callback signal
        case signal
        when /^focus_(.*)/
            @panes.focus($1)
        when 'goto-next-image'
            if @thumb_browser!=nil
                @thumb_browser.next_image
            end
        when 'goto-prev-image'
            if @thumb_browser!= nil
                @thumb_browser.prev_image
            end
        when 'quick-quit'
            Gtk.main_quit
        when 'save-current-image'
            save_data
        when 'display-config'
            @kbc.show_all
        end
    end
	def initialize image
		super()
        enable_dnd()

        if Conf::USE_PANES
            @panes = RDFNoteBook.new
        else
            @panes = RDFFrame.new
        end

        # fill up accel_table
        accel_table = {
            'Goto next image'=> 'goto-next-image',
            'Goto previous image'=> 'goto-prev-image',
            'Quick quit'=> 'quick-quit',
            'Save current image'=> 'save-current-image',
            'Show Configuration'=> 'display-config',
        }
        # generates the interface
        Conf::CONTENT.keys.sort.each{ |name|
            tab = Conf::CONTENT[name]
            pane = RDFEnumeration.new(tab[:ns], name)
            tab[:content].each{ |rdf_property, real_name|
                case real_name
                when InputHelper::Textarea
                    accel_table['Focus '+real_name.text] = 'focus_'+rdf_property
                    pane << RDFTableTextarea.new( real_name.text, rdf_property )
                 when String
                    accel_table['Focus '+real_name] = 'focus_'+rdf_property
                    pane << RDFTableItem.new( real_name, rdf_property )
                when Array
                    pane << RDFTableItem.new( real_name[0], rdf_property, real_name[1] )
                when Hash
                    pane << RDFMultipleChoice.new( real_name[:name], rdf_property,
                            real_name[:content])
                end
            }
            pane.create
            @panes << pane
        }

        # setup bey bindings
        @kbc = KeyBindingConfigurator.new( self, accel_table )
        @kbc.config_file = BINDING_FILE
        @kbc.callback = "callback"
        if !@kbc.signal_to_key.has_key?('display-config')
            @kbc.load_bindings(KeyBinding.new("Configure","display-config","<Ctrl>F1"))
        end
        
        @kbc.set_default_size(600,400)
        @kbc.hide
        
        vbox = Gtk::VBox.new false, 0
		vbox.pack_start @panes.get_widget, true, true, 0

        bb = Gtk::HButtonBox.new
        save = Gtk::Button.new Gtk::Stock::SAVE
        quit = Gtk::Button.new Gtk::Stock::QUIT
        bb.add save
		bb.add quit
        vbox.pack_start bb, false, false, 0

		@statusbar = Gtk::Statusbar.new

        pane = Gtk::HPaned.new
		pane.add1(vbox)
        pane.position = 300

        @left_box = Gtk::HBox.new(true)
        
		@image_frame = Gtk::EventBox.new
		@image = Gtk::Image.new
		load_picture nil, image
		@image.set_size_request(100,100)
		
		@image.pixbuf = @pixbuf
		@current_width, @current_height = 0, 0
		@image_frame.add(@image)
		@left_box.pack_start(@image_frame)
        
        
        
        
        
		pane.add2(@left_box)
		
		@main_vbox = Gtk::VBox.new
		@main_vbox.pack_start pane, true, true, 0
		@main_vbox.pack_start @statusbar, false, false, 0
		add(@main_vbox)

		# for the redraw system
		@last_allocate = Time.now

        signal_connect("destroy") { self.quit }

		save.signal_connect('clicked') { save_data }
        quit.signal_connect('clicked') { self.quit }

		@image.signal_connect("size-allocate") { |w,e|
			# the redraw scheme is buggy as size-allocate is not called if we 
			# don't touch the image size...
			
            if @pixbuf!=nil && Time.now - @last_allocate > Conf::REDRAW_INTERVAL && 
                    (@current_width!=e.width || @current_height!=e.height)
				(new_width, new_height) = Utils::new_size( @pixbuf.width.to_f, @pixbuf.height.to_f, 
                    e.width.to_f, e.height.to_f)
                if(new_width < 10 || new_height<10)
                    (new_width, new_height) = Utils::new_size( @pixbuf.width.to_f, 
                        @pixbuf.height.to_f, 10, 10)
                end
				@current_pixbuf = @pixbuf.scale(new_width, new_height)
				@image.pixbuf = @current_pixbuf
				@current_width, @current_height = e.width, e.height
				@last_allocate = Time.now
			end
		}
        group = Gtk::AccelGroup.new
        
        info("Welcome to the PhotoRDF Editor")

        self.add_accel_group( group )

        pane.show_all

    end

    require 'stringio'
    #generates the RDF and writes it out to the picture 
	def save_data
        model = Model.new
        image_resource = model.resource( nil )
        #image_resource = model.resource("file://oct.zoy.org/photo/"+File.basename(@current_file))
        
        @panes.each{ |p|
            p.content.each{ |rdf_item|
                rdf_item.to_rdf model, image_resource
            }
        }
        output = StringIO.new
        model.to_rdf_xml(output)
        output = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + output.string
        image = Image.new @current_file, false
        if @current_comment != -1
            image.comments[@current_comment].text = output
        else
            m = Marker::get_marker 'M_COM'
            m.text = output
            image.comments << m
        end
        image.save
	end


    require 'IPhotoRDF'

    
    # load a picture from the thumb browser
	def load_picture thumb_browser, file
		if thumb_browser != nil 
			@thumb_browser = thumb_browser
		end
		if file != "" and not file == nil
            @current_file = file
            
            self.title = "PhotofRDF - #{file}"
            
            @pixbuf = Gdk::Pixbuf.new(file)
			@current_pixbuf = @pixbuf
			@image.pixbuf = @pixbuf
            
            # force immediate redraw of the picture
			@last_allocate = Time.new - 2*Conf::REDRAW_INTERVAL
			
			tag_repository = JFIF::Helper::TagRepository.instance
			tag_repository.clean
			
            # the tag repository is filled when the Image is instancied
            image = Image.new file, false
			

            @panes.clear
            rdf_document = RDF::Photo::Document.new_from_jfif_image(image)
            @current_comment = -1 

            # fill the data from the EXIF embedded in the image
            @panes.exif_locator.each{ |k,v|
                v.value = tag_repository[k]
            }
            
            image.comments.each_with_index do 
				|comment, index|
                t = comment.text #.gsub(/\n/m,'')
				
				if t=~ /^<\?xml version=['"]1\.0['"] encoding=['"]([^'"]+)['"]/
					print "Encoding is "+$1+"\n"
                    @current_comment = index
					encoding = $1
                    # uses TempFile
                    require 'tempfile'
                    rdf_tempfile = Tempfile.new("rdf_file")
                    rdf_tempfile.puts(t)
                    rdf_tempfile.close
                    rdf_tempfile_name = rdf_tempfile.path.gsub(/\\/,'/')
                    
                    # creates an RDF Loader
                    rdf_parser = Loader.new
                    
                    content = Loader.get_rdf rdf_tempfile_name
                    content.statements.each{ |statement|
                        
                        predicate = statement.predicate.to_s
                        data = predicate.split("#")
                        ns = data[0]+"#"
                        property = data[1]
                        begin
                            node = statement.object
                            value = node.to_s
                            @panes[ns,property] = value
                        rescue NSException =>e
                            puts e
                        rescue UnknowPropertyException=>e
                            puts e
                            puts " for value "+statement.object.to_s
                        end
                            
                    }
				end
			end
			
		else
			@pixbuf = nil
			@current_pixbuf = nil
			@image.pixbuf = nil
		end
		@current_width, @current_height = 0, 0
	end
	def quit
		Gtk.main_quit
	end
end

