%%
%%  wpa.erl --
%%
%%     Wings Plugin API.
%%
%%  Copyright (c) 2001-2004 Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%%     $Id: wpa.erl,v 1.44 2004/01/04 16:08:21 bjorng Exp $
%%
-module(wpa).
-export([ask/3,ask/4,dialog/3,dialog/4,error/1,
	 yes_no/2,yes_no/3,yes_no_cancel/3,
	 bind_unicode/2,bind_virtual/3,
	 import/2,import/3,import_filename/2,
	 export/3,export_selected/3,
	 export_filename/2,export_filename/3,
	 save_images/3,
	 dialog_template/2,
	 pref_get/2,pref_get/3,pref_set/2,pref_set/3,
	 pref_set_default/3,pref_delete/2,
	 sel_get/1,sel_set/2,sel_set/3,sel_map/2,sel_fold/3,sel_convert/3,
	 sel_edge_regions/2,sel_face_regions/2,sel_strict_face_regions/2,
	 drag/3,drag/4,
	 pick/3,
	 vertices/1,vertex_pos/2,vertex_flatten/3,vertex_center/2,
	 faces/1,face_vertices/2,face_outer_vertices/2,face_outer_edges/2,
	 face_dissolve/2,
	 edge_loop_vertices/2,
	 obj_name/1,obj_id/1,
	 camera_info/1,lights/1,import_lights/2,
	 image_formats/0,image_read/1,image_write/1,
	 vm_freeze/1,
	 triangulate/1,triangulate/2,quadrangulate/1,quadrangulate/2
	]).

-export([format_error/1]).

-include("wings.hrl").
-include("e3d.hrl").

-import(lists, [reverse/1,foldl/3,foreach/2]).

format_error({crash,Term}) ->
    lists:flatten(io_lib:format("Internal error: ~P\n", [Term,20])).

%%%
%%% ask/3,4 is simpler to use, but only supports a single list of fields.
%%% dialog/3,4 is more powerful but is slightly more involved.
%%%

ask(Title, Qs, Fun) ->
    wings_ask:ask(Title, Qs, Fun).

ask(Bool, Title, Qs, Fun) ->
    wings_ask:ask(Bool, Title, Qs, Fun).

dialog(Title, Qs, Fun) ->
    wings_ask:dialog(Title, Qs, Fun).

dialog(Bool, Title, Qs, Fun) ->
    wings_ask:dialog(Bool, Title, Qs, Fun).

%% Show String in a dialog box.
error(String) ->
    wings_util:error(String).

yes_no(Question, Yes) ->
    wings_util:yes_no(Question, Yes).

yes_no(Question, Yes, No) ->
    wings_util:yes_no(Question, Yes, No).

yes_no_cancel(Question, Yes, No) ->
    wings_util:yes_no_cancel(Question, Yes, No).

bind_unicode(Key, Command) ->
    wings_hotkey:bind_unicode(Key, Command, plugin).

bind_virtual(Key, Mods, Command) ->
    wings_hotkey:bind_virtual(Key, Mods, Command, plugin).

%%%
%%% Import/export support.
%%%

%% returns: St
import(#e3d_file{}=E3dFile, St) ->
    wings_import:import(E3dFile, St).

%% Does not return.
import(Props, Importer, St0) ->
    Cont = fun(Name) ->
		   case ?SLOW(do_import(Importer, Name, St0)) of
		       #st{}=St -> St;
		       {error,Reason} ->
			   error("Import failed: " ++ Reason)
		   end
	   end,
    import_filename(Props, Cont).

do_import(Importer, Name, St0) ->
    case Importer(Name) of
	{ok,#e3d_file{}=E3DFile} ->
	    wings_import:import(E3DFile, St0);
	{error,Reason} ->
	    wings_util:error(Reason)
    end.

%% import_filename([Prop], Continuation).
%%   The Continuation fun will be called like this: Continuation(Filename).
import_filename(Ps0, Cont) ->
    This = wings_wm:this(),
    Dir = wings_pref:get_value(current_directory),
    Ps = Ps0 ++ [{title,"Import"},{directory,Dir}],
    Fun = fun(Name) ->
		  case catch Cont(Name) of
		      {command_error,Error} ->
			  wings_util:message(Error);
		      #st{}=St ->
			  wings_wm:send(This, {new_state,St});
		      Tuple when is_tuple(Tuple) ->
			  wings_wm:send(This, {action,Tuple});
		      ignore -> keep;
		      keep -> keep
		  end
	  end,
    wings_plugin:call_ui({file,open_dialog,Ps,Fun}).

export(none, Exporter, St) ->
    wings_export:export(Exporter, none, 0, St);
export(Ps, Exporter, St) ->
    SubDivs = proplists:get_value(subdivisions, Ps, 0),
    Cont = fun(Name) -> wings_export:export(Exporter, Name, SubDivs, St) end,
    export_filename(Ps, St, Cont).

export_selected(Props, Exporter, #st{selmode=Mode}=St)
  when Mode == body; Mode == face ->
    Shs0 = wings_sel:fold(
	     fun(Elems, #we{id=Id}=We, A) ->
		     [{Id,export_sel_set_holes(Mode, Elems, We)}|A]
	     end, [], St),
    Shs = gb_trees:from_orddict(reverse(Shs0)),
    export(Props, Exporter, St#st{shapes=Shs});
export_selected(_, _, _) -> error("Select objects or faces.").

export_sel_set_holes(body, _, We) -> We;
export_sel_set_holes(face, Faces0, #we{fs=Ftab}=We) ->
    Faces1 = gb_sets:to_list(Faces0),
    AllFaces = gb_trees:keys(Ftab),
    Faces = ordsets:subtract(AllFaces, Faces1),
    wings_material:assign('_hole_', Faces, We).

%% export_filename([Prop], Continuation).
%%   The Continuation fun will be called like this: Continuation(Filename).
export_filename(Prop0, Cont) ->
    This = wings_wm:this(),
    Dir = wings_pref:get_value(current_directory),
    Prop = Prop0 ++ [{directory,Dir}],
    Fun = fun(Name) ->
		  case catch Cont(Name) of
		      {command_error,Error} ->
			  wings_util:message(Error);
		      #st{}=St ->
			  wings_wm:send(This, {new_state,St});
		      Tuple when is_tuple(Tuple) ->
			  wings_wm:send(This, {action,Tuple});
		      ignore -> keep;
		      keep -> keep;
		      ok -> keep
		  end
	  end,
    wings_plugin:call_ui({file,save_dialog,Prop++[{title,"Export"}],Fun}).

%% export_filename([Prop], St, Continuation).
%%   The St will only be used to setup the default filename.
%%   The Continuation fun will be called like this: Continuation(Filename).
export_filename(Prop, #st{file=undefined}, Cont) ->
    export_filename(Prop, Cont);
export_filename(Prop0, #st{file=File}, Cont) ->
    Prop = case proplists:get_value(ext, Prop0) of
	       undefined -> Prop0;
	       Ext ->
		   Def = filename:rootname(filename:basename(File), ".wings") ++ Ext,
		   [{default_filename,Def}|Prop0]
	   end,
    export_filename(Prop, Cont).

%% save_images(E3DFile0, Directory, DefaultFiletype) -> E3DFile
%%  Save all images in all materials, inserting the filename
%%  into each saved image. 
%%    E3DFile = #e3d_file{}
%%    DefaultFiletype = Extension (list), e.g. ".bmp".
save_images(E3DFile, Directory, Filetype) ->
    wings_export:save_images(E3DFile, Directory, Filetype).

%% dialog_template(Module, Type) -> Template
%%  Return a template for a standard dialog.
%%  Module = caller's module (used a preference key)
%%  Type = import|export
dialog_template(Mod, import) ->
    {label_column,
     [{"Import scale",{text,pref_get(Mod, import_scale, 1.0),[{key,import_scale}]}},
      {"(Export scale)",{text,pref_get(Mod, export_scale, 1.0),[{key,export_scale}]}}]};
dialog_template(Mod, export) ->
    FileTypes = [{lists:flatten([Val," (*",Key,")"]),Key} ||
		    {Key,Val} <- image_formats()],
    DefFileType = pref_get(Mod, default_filetype, ".bmp"),
    {vframe,
     [{label_column,
       [{"(Import scale)",{text,pref_get(Mod, import_scale, 1.0),[{key,import_scale}]}},
	{"Export scale",{text,pref_get(Mod, export_scale, 1.0),[{key,export_scale}]}},
	{"Sub-division Steps",{text,pref_get(Mod, subdivisions, 0),
			       [{key,subdivisions},{range,0,4}]}}]},
      panel,
      {vframe,
       [{menu,FileTypes,DefFileType,[{key,default_filetype}]}],
       [{title,"Default texture file type"}]} ]}.

%%%
%%% Preferences.
%%%
%%% As Mod, pass in ?MODULE.
%%%

pref_get(Mod, Key) ->
    wings_pref:get_value({Mod,Key}).

pref_get(Mod, Key, Default) ->
    wings_pref:get_value({Mod,Key}, Default).

pref_set(Mod, KeyVals) when is_list(KeyVals) ->
    foreach(fun({Key,Val}) ->
		    wings_pref:set_value({Mod,Key}, Val)
	    end, KeyVals).

pref_set(Mod, Key, Value) ->
    wings_pref:set_value({Mod,Key}, Value).

pref_set_default(Mod, Key, Value) ->
    wings_pref:set_default({Mod,Key}, Value).

pref_delete(Mod, Key) ->
    wings_pref:delete_value({Mod,Key}).

%%%    
%%% Selection utilities.
%%%

sel_set(Sel, St) ->
    wings_sel:set(Sel, St).

sel_set(Mode, Sel, St) ->
    wings_sel:set(Mode, Sel, St).

sel_get(#st{sel=Sel}) ->
    Sel.

sel_map(F, St) ->
    wings_sel:map(F, St).

sel_fold(F, Acc, St) ->
    wings_sel:fold(F, Acc, St).

sel_convert(F, Mode, St) ->
    Sel = wings_sel:fold(
	    fun(Items0, #we{id=Id}=We, A) ->
		    case F(Items0, We) of
			[] -> A;
			[_|_]=Items ->
			    [{Id,gb_sets:from_list(Items)}|A];
			Items ->
			    case gb_sets:is_empty(Items) of
				true -> A;
				false -> [{Id,Items}|A]
			    end
		    end
	    end, [], St),
    wings_sel:set(Mode, Sel).

sel_edge_regions(Edges, We) ->
    wings_sel:edge_regions(Edges, We).

%% Faces must share at least one edge to belong to the same region
%% (sharing a vertex is not sufficient).
sel_face_regions(Faces, We) ->
    wings_sel:face_regions(Faces, We).

%% Faces that share at least one vertex (or edge) to belong to
%% the same region.
sel_strict_face_regions(Faces, We) ->
    wings_sel:strict_face_regions(Faces, We).

%%%
%%% Picking.
%%%

pick(X, Y, St) ->
    wings_pick:do_pick(X, Y, St).

%%%
%%% Dragging support
%%%

drag(Tvs, Units, St) ->
    wings_drag:setup(Tvs, Units, [], St).

drag(Tvs, Units, Flags, St) ->
    wings_drag:setup(Tvs, Units, Flags, St).

%%%
%%% Vertex functions.
%%%

vertices(#we{vp=Vtab}) ->
    gb_trees:keys(Vtab).

vertex_pos(V, #we{vp=Vtab}) ->
    gb_trees:get(V, Vtab).

vertex_flatten(Vs, PlaneNormal, We) ->
    wings_vertex:flatten(Vs, PlaneNormal, We).

vertex_center(Vs, We) ->
    wings_vertex:center(Vs, We).

%%% Edges.

edge_loop_vertices(Edges, We) ->
    wings_edge_loop:edge_loop_vertices(Edges, We).

%%% Faces

faces(#we{fs=Ftab}) -> gb_trees:keys(Ftab).

face_vertices(Face, We) ->
    wings_face:vertices_ccw(Face, We).

face_outer_vertices(Faces, We) ->
    wings_vertex:outer_partition(Faces, We).

face_outer_edges(Faces, We) ->
    wings_face_cmd:outer_edge_partition(Faces, We).

face_dissolve(Faces, We) when is_list(Faces) ->
    wings_face_cmd:dissolve(gb_sets:from_list(Faces), We);
face_dissolve(Faces, We) ->
    wings_face_cmd:dissolve(Faces, We).

%%% Objects.

obj_name(#we{name=Name}) -> Name.
obj_id(#we{id=Id}) -> Id.

%%%
%%% Camera info.
%%%

camera_info(As) ->
    camera_info(As, wings_view:current()).

camera_info([aim|As], #view{origin=Aim}=View) ->
    [Aim|camera_info(As, View)];
camera_info([distance_to_aim|As], #view{distance=Dist}=View) ->
    [Dist|camera_info(As, View)];
camera_info([azimuth|As], #view{azimuth=Az}=View) ->
    [Az|camera_info(As, View)];
camera_info([elevation|As], #view{elevation=El}=View) ->
    [El|camera_info(As, View)];
camera_info([tracking|As], #view{pan_x=X,pan_y=Y}=View) ->
    [{X,Y}|camera_info(As, View)];
camera_info([fov|As], #view{fov=Fov}=View) ->
    %% Field of view.
    [Fov|camera_info(As, View)];
camera_info([hither|As], #view{hither=Hither}=View) ->
    %% Near clipping plane.
    [Hither|camera_info(As, View)];
camera_info([yon|As], #view{yon=Yon}=View) ->
    %% Far clipping plane.
    [Yon|camera_info(As, View)];
camera_info([], _) -> [].

%%%
%%% Get all lights.
%%%

lights(St) ->
    case wings_light:export(St) of
	[] -> wings_light:export_camera_lights();
	L -> L
    end.

import_lights(Lights, St) ->
    wings_light:import(Lights, St).

%%%
%%% Images.
%%%

image_formats() ->
    wings_plugin:call_ui({image,formats,[]}).

image_read(Ps) ->
    wings_plugin:call_ui({image,read,Ps}).

image_write(Ps) ->
    case catch wings_plugin:call_ui({image,write,Ps}) of
	{'EXIT',Reason} ->
	    {error,{none,?MODULE,{crash,Reason}}};
	Result -> Result
    end.

%%%
%%% Virtual mirror.
%%%

vm_freeze(#we{mirror=none}=We) -> We;
vm_freeze(#we{mirror=Face}=We0) ->
    We = wings_face_cmd:mirror_faces([Face], We0),
    We#we{mirror=none}.

%%%
%%% Tesselation/subdivision.
%%%

triangulate(We) ->
    wings_tesselation:triangulate(We).

triangulate(Faces, We) ->
    wings_tesselation:triangulate(Faces, We).

quadrangulate(We) ->
    wings_tesselation:quadrangulate(We).

quadrangulate(Faces, We) ->
    wings_tesselation:quadrangulate(Faces, We).
