%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%

%%
-module(gstk_grid).

-export([event/5,create/3,config/3,option/5,read/3,delete/2,destroy/2,
	 mk_create_opts_for_child/4,read_option/5]).

-include("gstk.hrl").

%%-----------------------------------------------------------------------------
%% 			    GRID OPTIONS
%%
%%	rows		{ViewFrom, ViewTo}
%%	columnwidths	[CW1, CW2, ..., CWn]
%%	vscroll		Bool | left | right
%%	hscroll		Bool | top | bottom
%%	x		Coord
%%	y		Coord
%%	width		Int
%%	height		Int
%%	fg		Color  (lines and default line color)
%%	bg		Color
%%-----------------------------------------------------------------------------

-record(state,{canvas,ncols,max_range,cell_id, cell_pos,ids,db,tkcanvas}).
-record(item,{text_id,rect_id,line_id}).

%%======================================================================
%% Interfaces
%%======================================================================

event(DB, Gstkid, Etype, Edata, Args) ->
    gstk_gridline:event(DB, Gstkid, Etype, Edata, Args).

create(DB, Gstkid, Options) ->
    WinParent=Gstkid#gstkid.parent,
    {OtherOpts,CanvasOpts} = parse_opts(Options,[],[]),
    %% Why this (secret) hack? Performance reasons.
    %% if we ".canvas bind all" once and for all, then we can
    %% create lines twice as fast since we don't have to bind each line.
    C = make_ref(),
    gstk:create_impl(DB,{a_grid, {canvas,C,WinParent,
				 [{secret_hack_gridit, Gstkid}
				  | CanvasOpts]}}),
    CanvasGstkid = gstk_db:lookup_gstkid(DB, C),
    Wid = CanvasGstkid#gstkid.widget,
    SO = CanvasGstkid#gstkid.widget_data,
    TkCanvas = SO#so.object,
    CI=ets:new(gstk_grid_cellid,[private,set]),
    CP=ets:new(gstk_grid_cellpos,[private,set]),
    IDs=ets:new(gstk_grid_id,[private,set]),
    S=#state{db=DB,ncols=length(gs:val(columnwidths,OtherOpts)),
	     canvas=C,cell_id=CI,tkcanvas=TkCanvas,cell_pos=CP,ids=IDs},
    Ngstkid = Gstkid#gstkid{widget=Wid,widget_data=S},
    gstk_db:insert_opts(DB,Ngstkid,OtherOpts),
    gstk_db:insert_widget(DB,Ngstkid),
    gstk_generic:mk_cmd_and_exec(lists:keydelete(columnwidths,1,OtherOpts),
				Ngstkid, TkCanvas,"","", DB,nop).

config(DB, Gstkid, Options) ->
    #gstkid{widget=TkW,widget_data=State}=Gstkid,
    {OtherOpts,CanvasOpts} = parse_opts(Options,[],[]),
    case gstk:config_impl(DB,State#state.canvas,CanvasOpts) of
	ok ->
	    SimplePreCmd = "nyi?",
	    PlacePreCmd = [";place ", TkW],
	    gstk_generic:mk_cmd_and_exec(OtherOpts,Gstkid,TkW,
					SimplePreCmd,PlacePreCmd,DB,State);
	Err -> Err
    end.
    

option(Option, Gstkid, _TkW, DB,State) ->
    case Option of
	{rows,{From,To}} ->
	    Ngstkid = reconfig_rows(From,To,Gstkid),
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    gstk_db:update_widget(DB,Ngstkid),
	    {none,Ngstkid};
	{fg,_Color} ->
	    reconfig_grid(DB,Option,State),
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    none;
	{bg,_Color} ->
	    reconfig_grid(DB,Option,State),
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    none;
	{font,_Font} ->
	    reconfig_grid(DB,Option,State),
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    none;
	{columnwidths,ColWs} ->
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    Rows = gstk_db:opt(DB,Gstkid,rows),
	    CellHeight = gstk_db:opt(DB,Gstkid,cellheight),
	    gstk:config_impl(DB,State#state.canvas,
			    [calc_scrollregion(Rows,ColWs,CellHeight)]),
	    %% Crash upon an error msg (so we know WHY)
	    {result,_} = gstk:call(["resize_grid_cols ",State#state.tkcanvas,
				   " [list ",asc_tcl_colw(ColWs),"]"]),
	    none;
	{cellheight,_Height} ->
	    gstk_db:insert_opt(DB,Gstkid,Option),
	    none;	
	_ ->
	    invalid_option
    end.

reconfig_grid(_,_,nop) -> done;
reconfig_grid(DB,Option,#state{tkcanvas=TkW,cell_pos=CP,
			       ncols=Ncols,max_range={From,To}}) ->
    reconfig_grid(DB,TkW,Option,From,To,CP,Ncols).
    
reconfig_grid(DB,TkW,Opt,Row,MaxRow,CellPos,Ncols) when Row =< MaxRow ->
    [{_,Item}] = ets:lookup(CellPos,{1,Row}),
    case Item#item.line_id of
	free -> empty_cell_config(DB,TkW,Row,1,Ncols,CellPos,Opt);
	GridLine ->
	    gstk_gridline:config(DB,gstk_db:lookup_gstkid(DB,GridLine),
				[Opt])
    end,
    reconfig_grid(DB,TkW,Opt,Row+1,MaxRow,CellPos,Ncols);
reconfig_grid(_,_,_,_,_,_,_) -> done.

%%----------------------------------------------------------------------
%% Purpose: Config an empty cell (i.e. has no gridline)
%%----------------------------------------------------------------------
empty_cell_config(DB,TkW,Row,Col,Ncols,CellPos,Opt) when Col =< Ncols ->
    [{_,Item}] = ets:lookup(CellPos,{Col,Row}),
    empty_cell_config(DB,TkW,Item,Opt),
    empty_cell_config(DB,TkW,Row,Col+1,Ncols,CellPos,Opt);
empty_cell_config(_,_,_,_,_,_,_) -> done.

empty_cell_config(_,TkW,#item{rect_id=Rid},{bg,Color}) ->
    gstk:exec([TkW," itemconf ",gstk:to_ascii(Rid)," -f ",gstk:to_color(Color)]);
empty_cell_config(_,TkW,#item{rect_id=Rid,text_id=Tid},{fg,Color}) ->
    Acolor = gstk:to_color(Color),
    Pre = [TkW," itemconf "],
    RectStr = [Pre, gstk:to_ascii(Rid)," -outline ",Acolor],
    TexdStr = [Pre,  gstk:to_ascii(Tid)," -fi ",Acolor],
    gstk:exec([RectStr,$;,TexdStr]);
empty_cell_config(DB,TkW,#item{text_id=Tid},{font,Font}) ->
    gstk:exec([TkW," itemconf ",gstk:to_ascii(Tid)," -font ",
	      gstk_font:choose_ascii(DB,Font)]);
empty_cell_config(_,_,_,_) -> done.



reconfig_rows(From, To, Gstkid) ->
    #gstkid{widget_data=State,id=Id} = Gstkid,
    #state{tkcanvas=TkCanvas,cell_pos=CP,cell_id=CI,
	   canvas=C,db=DB,max_range=Range}=State,
    NewRange =
	if Range == undefined ->
		mkgrid(DB,CP,CI,TkCanvas,Id,From,To),
		{From,To};
	   true ->
		{Top,Bot} = Range,
		if
		    From < Top -> % we need more rects above
			mkgrid(DB,CP,CI,TkCanvas,Id,From,Top-1);
		    true -> true
		end,
		if
		    To > Bot ->   % we need more rects below
			mkgrid(DB,CP,CI,TkCanvas,Id,Bot+1,To);
		    true -> true
		end,
		{lists:min([Top, From]), lists:max([Bot, To])}
	end,
    gstk:config_impl(DB,C,[calc_scrollregion({From,To},
					    gstk_db:opt(DB,Id,columnwidths),
					    gstk_db:opt(DB,Id,cellheight))]),
    S2 = State#state{max_range=NewRange},
    Gstkid#gstkid{widget_data=S2}.

read(DB,Gstkid,Opt) ->
    State = Gstkid#gstkid.widget_data,
    case lists:member(Opt,[x,y,width,height,hscroll,vscroll]) of
	true -> gstk:read_impl(DB,State#state.canvas,Opt);
	false ->
	    gstk_generic:read_option(DB, Gstkid, Opt,State)
      end.

read_option(Option,Gstkid,_TkW,DB,State) -> 
    case Option of
	{obj_at_row,Row} ->
	    case ets:lookup(State#state.cell_pos,{1,Row}) of
		[{_pos,Item}] ->
		    case Item#item.line_id of
			free -> undefined;
			GridLine ->
			    gstk:make_extern_id(GridLine, DB)
		    end;
		_ -> undefined
	    end;
	Opt -> gstk_db:opt(DB,Gstkid#gstkid.id,Opt,undefined)
    end.


%%----------------------------------------------------------------------
%% Is always called.
%% Clean-up my specific side-effect stuff.
%%----------------------------------------------------------------------
delete(DB, Gstkid) ->
    gstk_db:delete_widget(DB, Gstkid),
    State = Gstkid#gstkid.widget_data,
    #state{canvas=C,cell_pos=CP,cell_id=CIs, ids=IDs} = State,
    ets:delete(CP),
    ets:delete(CIs),
    ets:delete(IDs),
    {Gstkid#gstkid.parent, Gstkid#gstkid.id, gstk_grid, [C]}.

%%----------------------------------------------------------------------
%% Is called iff my parent is not also destroyed.
%%----------------------------------------------------------------------
destroy(DB, Canvas) ->
    gstk:destroy_impl(DB,gstk_db:lookup_gstkid(DB,Canvas)).

mk_create_opts_for_child(DB,Cgstkid, Pgstkid, Opts) ->
    gstk_generic:mk_create_opts_for_child(DB,Cgstkid,Pgstkid,Opts).

mkgrid(DB,CellPos,CellIds,TkCanvas,Id,From,To) ->
    ColWs = gstk_db:opt(DB,Id,columnwidths),
    AscColW = ["[list ",asc_tcl_colw(ColWs),"]"],
    Font = gstk_font:choose_ascii(DB,gstk_db:opt(DB,Id,font)),
    Fg = gstk:to_color(gstk_db:opt(DB,Id,fg)),
    Bg = gstk:to_color(gstk_db:opt(DB,Id,bg)),
    Objs = tcl2erl:ret_list(["mkgrid ",TkCanvas," ",AscColW," ",
			     gstk:to_ascii(From)," ",
			     gstk:to_ascii(To)," ",
			     gstk:to_ascii(gstk_db:opt(DB,Id,cellheight))," ",
			     Font," ",Fg," ",Bg]),
    insert_objs(CellPos,CellIds,From,To,1,length(ColWs)+1,Objs).

insert_objs(_,_,_,_,_,_,[]) -> done;
insert_objs(CP,CI,Row,T,MaxCol,MaxCol,Objs) ->
    insert_objs(CP,CI,Row+1,T,1,MaxCol,Objs);
insert_objs(CellPos,CellIds,Row,To,Col,Ncols,[RectId,TextId|Objs]) ->
    ets:insert(CellPos,{{Col,Row},
		   #item{text_id=TextId,rect_id=RectId,line_id=free}}),
    ets:insert(CellIds,{RectId,{Col,Row}}),
    ets:insert(CellIds,{TextId,{Col,Row}}),
    insert_objs(CellPos,CellIds,Row,To,Col+1,Ncols,Objs).

asc_tcl_colw([]) -> "";
asc_tcl_colw([Int|T]) -> [gstk:to_ascii(Int)," "|asc_tcl_colw(T)].

%%----------------------------------------------------------------------
%% Args: Cols  list of column sizes (measured in n-chars)
%%----------------------------------------------------------------------
calc_scrollregion({From, To}, Cols, Height) ->
    {scrollregion, {0, ((From-1) * Height) + From,
		    lists:sum(Cols)+length(Cols)+1, (To * Height)+ To+1}}.

parse_opts([],OtherOpts,CanvasOpts) -> {OtherOpts,CanvasOpts};
parse_opts([{Key,Val}|Opts],OtherOpts,CanvasOpts) ->
    case lists:member(Key,[x,y,width,height,vscroll,hscroll]) of
	true -> parse_opts(Opts,OtherOpts,[{Key,Val}|CanvasOpts]);
	false -> parse_opts(Opts,[{Key,Val}|OtherOpts],CanvasOpts)
    end;
parse_opts([Opt|Opts],OtherOpts,CanvasOpts) ->
    parse_opts(Opts,[Opt|OtherOpts],CanvasOpts).