namespace eval scene_import {
namespace export load_scene 


proc parse_scene {scenefile fieldtypes blocktypes} {
	set file_data [read $scenefile]
	set data [split $file_data "\n"]

	set blocklist {}

	set type ""
	set inside 0
	set row 0
	set block {}

	foreach line $data {
		incr row

		if {$line == ""} {
			continue
		}

		set firstchar [lindex [split $line ""] 0]

		if {$firstchar == "#"} {
			continue
		}

		if {$inside} {
			if {$firstchar == "\}"} {
				#puts "read [dict get $block _type] block: $block"
				lappend blocklist $block
				set type ""
				set inside 0
				set block {}
				continue
			} elseif {$firstchar == "\{"} {
				continue
			}

			set value ""
			set parts [regexp -all -inline {\S+} $line]
			set key [lindex $parts 0]
			set value [lrange $parts 1 [llength $parts]]
			dict set block $key $value
		} else {
			if {[lsearch $blocktypes $line] == -1} {
				puts "Parsing error: invalid blocktype $line at line $row!"
				return ""
			}

			set inside 1
			set block [dict create]
			dict set block _type $line

			if {$firstchar == "\}"} {
				puts "Parsing error: invalid \} at line $row"
				return ""
			}
		}

	}

	return $blocklist
}

proc list_from_dict_keys {dictionary keys} {
	set out []

	foreach key $keys {
		lappend out [dict get $dictionary $key]
	}

	return $out
}

proc create_native_object {type values} {
	switch $type {
		camera {
			set params [list_from_dict_keys $values {position orientation angle_y clip_start clip_end}]
			#puts "PARAMS: $params"
			#return [Camera {*}$params]
			set cam [new_Camera {*}$params]
			return $cam
		} instance {
			if {[dict get $values mesh] == "NULL"} {
				puts "Scene: Warning: $type [dict get $values name]: mesh is NULL"
			}
			
			if {[dict get $values material] == "NULL"} {
				puts "Scene: Warning: $type [dict get $values name]: material is NULL"
			}
			
			set params [list_from_dict_keys $values {mesh material position scale orientation}]
			
			puts "Instance: [dict get $values name]\t= [dict get $values mesh]"
			
			#puts $values
			#puts $params
			#set inst [new_Instance {*}$params]
			#Instance instobj -this $inst
			Instance instobj {*}$params
			instobj -disown
			
			#puts "Instance: [instobj cget -this], $params"
			return [instobj cget -this]
			#return $inst
		} light {
			puts "values: $values"
			set params [list_from_dict_keys $values {position dir color energy falloff spot_size shadow enabled}]
			
			# TODO: just use new_Light here? (who owns the pointer then?)
			# 	Light(glm::vec3 pos, glm::vec3 eulerangles, glm::vec3 color, float energy, float falloff, float spot_size, bool shadow, bool enabled);
			Light lamp {*}$params
			lamp -disown
			
			return [lamp cget -this] 
		} default {
			puts "ERROR: invalid object type $type"
			return 0
		}
	}
}

proc addNativeObjectToScene {scene type name obj} {
	#puts "$scene $type $obj"
	switch $type {
		camera {
			Scene_giveCamera $scene $obj
		} instance {
			Scene_addPointerToScene $scene $name $obj
		} light {
			Scene_giveLight $scene $obj
		} default {
			puts "ERROR: addNativeObjectToScene: invalid object type $type"
			return 0
		}
	}
}

# Takes in the parsed scene representation and turns it into native objects
proc build_scene {parsed fieldtypes fieldlengths blocktypes meshstorep texturestorep materialsp} {
	upvar $meshstorep meshes
	upvar $texturestorep textures
	upvar $materialsp materials

	set scene [new_Scene]

	foreach object $parsed {
		set type [dict get $object _type]
		set name [dict get $object name]
		#puts "$type $name"
		set nativevalues [dict create]

		dict for {key value} $object {
			if {$key == "_type"} {
				continue
			}

			# Flatten the list (some strings are of form {"string"})
			set value [concat {*}$value]

			set fieldtype [dict get $fieldtypes $key]
			set clean_value $value

			if {[llength $value] != [dict get $fieldlengths $fieldtype]} {
				puts "ERROR: $type $name has invalid $fieldtype field $key: $value"
				delete_Scene $scene
				return ""
			}

			# Remove quotes if necessary. TODO: do this in the parsing phase
			if {$fieldtype == "str" || $fieldtype == "meshref" || $fieldtype == "materialref"} {
				set value [string map {\" ""} $value]
			}

			set native $value
			
			switch $fieldtype {
				vec3 {
					set native [create_vec3 {*}$value]
				}

				vec4 {
					set native [create_vec4 {*}$value]
				}

				quat {
					set native [create_quat {*}$value]
				}

				meshref {
					if {![meshes exists $value]} {
						logerror "Mesh referes to non-existing mesh '$value'"
					}
				
					set native [meshes get $value]
				}
				
				materialref {
					if {![materials exists $value]} {
						logerror "Mesh referes to non-existing material '$value'"
					}
					
					set native [materials get $value]
					#puts "NATIVE $value = $native"
				}
			}

			dict set nativevalues $key $native

			#puts "\t$fieldtype $key = $clean_value"

		}

		set obj [create_native_object $type $nativevalues]
		
		if {$obj == "NULL"} {
			puts "Scene: Warning: $type $name is NULL"
		}
		
		addNativeObjectToScene $scene $type [dict get $nativevalues name] $obj

		#puts "Native: $nativevalues"
		#puts "Native obj: $obj"
	}

	return $scene
}

proc load_scene {path meshstore texturestore materialstore} {
	set field_list {
		name str 
		mesh meshref 
		position vec3 
		orientation vec3 
		scale vec3 
		material materialref 
		clip_end float 
		clip_start float
		angle_y float
		type str
		shadow float
		color vec3
		energy float
		spot_size float
		falloff float
		enabled float
		dir vec3
	}

# How many space separated words each data type consists of
	set field_lengths_list {
		str 1
		meshref 1
		materialref 1
		float 1
		vec3 3
		vec4 4
		quat 4
	}

	set oldpath [pwd]	

	cd [getAssetSourceDir]
	puts "Loading scene $path..."

	set fieldtypes [dict create {*}$field_list] 
	set fieldlengths [dict create {*}$field_lengths_list] 
	set blocktypes "instance camera light"

	if {! [file exists $path]} {
		logerror "ERROR: $path not found"
		cd $oldpath
		return ""
	}

	set infile [open $path "r"]
	set result [parse_scene $infile $fieldtypes $blocktypes]
	close $infile
	cd $oldpath

	set scene [build_scene $result $fieldtypes $fieldlengths $blocktypes $meshstore $texturestore $materialstore]
	#puts "Scene: $scene"

	return $scene
}
}
