// +build !windows,!darwin

// 29 july 2014

package ui

import (
	"fmt"
	"reflect"
	"unsafe"
)

// #include "gtk_unix.h"
// extern void goTableModel_toggled(GtkCellRendererToggle *, gchar *, gpointer);
// extern void tableSelectionChanged(GtkTreeSelection *, gpointer);
import "C"

type table struct {
	*tablebase

	*scroller
	treeview *C.GtkTreeView

	model     *C.goTableModel
	modelgtk  *C.GtkTreeModel
	selection *C.GtkTreeSelection

	pixbufs []*C.GdkPixbuf

	selected *event

	// stuff required by GtkTreeModel
	nColumns C.gint
	old      C.gint
	types    []C.GType
	crtocol  map[*C.GtkCellRendererToggle]int
}

var (
	attribText   = togstr("text")
	attribPixbuf = togstr("pixbuf")
	attribActive = togstr("active")
)

func finishNewTable(b *tablebase, ty reflect.Type) Table {
	widget := C.gtk_tree_view_new()
	t := &table{
		scroller:  newScroller(widget, true, true, false), // natively scrollable; has a border; no overlay
		tablebase: b,
		treeview:  (*C.GtkTreeView)(unsafe.Pointer(widget)),
		crtocol:   make(map[*C.GtkCellRendererToggle]int),
		selected:  newEvent(),
	}
	model := C.newTableModel(unsafe.Pointer(t))
	t.model = model
	t.modelgtk = (*C.GtkTreeModel)(unsafe.Pointer(model))
	t.selection = C.gtk_tree_view_get_selection(t.treeview)
	g_signal_connect(
		C.gpointer(unsafe.Pointer(t.selection)),
		"changed",
		C.GCallback(C.tableSelectionChanged),
		C.gpointer(unsafe.Pointer(t)))
	C.gtk_tree_view_set_model(t.treeview, t.modelgtk)
	for i := 0; i < ty.NumField(); i++ {
		cname := togstr(ty.Field(i).Name)
		switch {
		case ty.Field(i).Type == reflect.TypeOf(ImageIndex(0)):
			// can't use GDK_TYPE_PIXBUF here because it's a macro that expands to a function and cgo hates that
			t.types = append(t.types, C.gdk_pixbuf_get_type())
			C.tableAppendColumn(t.treeview, C.gint(i), cname,
				C.gtk_cell_renderer_pixbuf_new(), attribPixbuf)
		case ty.Field(i).Type.Kind() == reflect.Bool:
			t.types = append(t.types, C.G_TYPE_BOOLEAN)
			cr := C.gtk_cell_renderer_toggle_new()
			crt := (*C.GtkCellRendererToggle)(unsafe.Pointer(cr))
			t.crtocol[crt] = i
			g_signal_connect(C.gpointer(unsafe.Pointer(cr)),
				"toggled",
				C.GCallback(C.goTableModel_toggled),
				C.gpointer(unsafe.Pointer(t)))
			C.tableAppendColumn(t.treeview, C.gint(i), cname,
				cr, attribActive)
		default:
			t.types = append(t.types, C.G_TYPE_STRING)
			C.tableAppendColumn(t.treeview, C.gint(i), cname,
				C.gtk_cell_renderer_text_new(), attribText)
		}
		freegstr(cname) // free now (not deferred) to conserve memory
	}
	// and for some GtkTreeModel boilerplate
	t.nColumns = C.gint(ty.NumField())
	return t
}

func (t *table) Lock() {
	t.tablebase.Lock()
	d := reflect.Indirect(reflect.ValueOf(t.data))
	t.old = C.gint(d.Len())
}

func (t *table) Unlock() {
	t.unlock()
	// there's a possibility that user actions can happen at this point, before the view is updated
	// alas, this is something we have to deal with, because Unlock() can be called from any thread
	go func() {
		Do(func() {
			t.RLock()
			defer t.RUnlock()
			d := reflect.Indirect(reflect.ValueOf(t.data))
			new := C.gint(d.Len())
			C.tableUpdate(t.model, t.old, new)
		})
	}()
}

func (t *table) LoadImageList(i ImageList) {
	i.apply(&t.pixbufs)
}

func (t *table) Selected() int {
	var iter C.GtkTreeIter

	t.RLock()
	defer t.RUnlock()
	if C.gtk_tree_selection_get_selected(t.selection, nil, &iter) == C.FALSE {
		return -1
	}
	path := C.gtk_tree_model_get_path(t.modelgtk, &iter)
	if path == nil {
		panic(fmt.Errorf("invalid iter in Table.Selected()"))
	}
	defer C.gtk_tree_path_free(path)
	return int(*C.gtk_tree_path_get_indices(path))
}

func (t *table) Select(index int) {
	t.RLock()
	defer t.RUnlock()
	C.gtk_tree_selection_unselect_all(t.selection)
	if index == -1 {
		return
	}
	path := C.gtk_tree_path_new()
	defer C.gtk_tree_path_free(path)
	C.gtk_tree_path_append_index(path, C.gint(index))
	C.gtk_tree_selection_select_path(t.selection, path)
}

func (t *table) OnSelected(f func()) {
	t.selected.set(f)
}

//export goTableModel_get_n_columns
func goTableModel_get_n_columns(model *C.GtkTreeModel) C.gint {
	tm := (*C.goTableModel)(unsafe.Pointer(model))
	t := (*table)(tm.gotable)
	return t.nColumns
}

//export goTableModel_get_column_type
func goTableModel_get_column_type(model *C.GtkTreeModel, column C.gint) C.GType {
	tm := (*C.goTableModel)(unsafe.Pointer(model))
	t := (*table)(tm.gotable)
	return t.types[column]
}

//export goTableModel_do_get_value
func goTableModel_do_get_value(data unsafe.Pointer, row C.gint, col C.gint, value *C.GValue) {
	t := (*table)(data)
	t.RLock()
	defer t.RUnlock()
	d := reflect.Indirect(reflect.ValueOf(t.data))
	datum := d.Index(int(row)).Field(int(col))
	switch {
	case datum.Type() == reflect.TypeOf(ImageIndex(0)):
		d := datum.Interface().(ImageIndex)
		C.g_value_init(value, C.gdk_pixbuf_get_type())
		C.g_value_set_object(value, C.gpointer(unsafe.Pointer(t.pixbufs[d])))
	case datum.Kind() == reflect.Bool:
		d := datum.Interface().(bool)
		C.g_value_init(value, C.G_TYPE_BOOLEAN)
		C.g_value_set_boolean(value, togbool(d))
	default:
		s := fmt.Sprintf("%v", datum)
		str := togstr(s)
		defer freegstr(str)
		C.g_value_init(value, C.G_TYPE_STRING)
		C.g_value_set_string(value, str)
	}
}

//export goTableModel_getRowCount
func goTableModel_getRowCount(data unsafe.Pointer) C.gint {
	t := (*table)(data)
	t.RLock()
	defer t.RUnlock()
	d := reflect.Indirect(reflect.ValueOf(t.data))
	return C.gint(d.Len())
}

//export goTableModel_toggled
func goTableModel_toggled(cr *C.GtkCellRendererToggle, pathstr *C.gchar, data C.gpointer) {
	t := (*table)(unsafe.Pointer(data))
	t.Lock()
	defer t.Unlock()
	path := C.gtk_tree_path_new_from_string(pathstr)
	if len := C.gtk_tree_path_get_depth(path); len != 1 {
		panic(fmt.Errorf("invalid path of depth %d given to goTableModel_toggled()", len))
	}
	// dereference return value to get our sole member
	row := *C.gtk_tree_path_get_indices(path)
	col := t.crtocol[cr]
	d := reflect.Indirect(reflect.ValueOf(t.data))
	datum := d.Index(int(row)).Field(int(col))
	datum.SetBool(!datum.Bool())
}

//export tableSelectionChanged
func tableSelectionChanged(sel *C.GtkTreeSelection, data C.gpointer) {
	t := (*table)(unsafe.Pointer(data))
	t.selected.fire()
}
