%%
%%  wings_dissolve.erl --
%%
%%     This module implements dissolve of faces.
%%

-module(wings_dissolve).

-export([faces/2, complement/2]).

-include("wings.hrl").

%% faces([Face], We) -> We'
%%  Dissolve the given faces.
faces([], We) -> We;
faces(Faces, #we{fs=Ftab0}=We) ->
    case gb_sets:is_empty(Faces) of
	true -> We;
	false when is_list(Faces) -> 
	    Complement = ordsets:subtract(gb_trees:keys(Ftab0), 
					  ordsets:from_list(Faces)),
	    dissolve_1(Faces, Complement, We);
	false ->
	    Complement = ordsets:subtract(gb_trees:keys(Ftab0), 
					  gb_sets:to_list(Faces)),
	    dissolve_1(Faces, Complement, We)
    end.

faces([], _, We) -> We;
faces(Faces,Complement,We) ->
    case gb_sets:is_empty(Faces) of
        true -> We;
        false -> dissolve_1(Faces, Complement,We)
    end.

dissolve_1(Faces, Complement, We0) ->
    We1 = optimistic_dissolve(Faces,Complement,We0#we{vc=undefined}),
    NewFaces = wings_we:new_items_as_ordset(face, We0, We1),
    We2 = wings_face:delete_bad_faces(NewFaces, We1),
    We = wings_we:rebuild(We2),
    case wings_we:is_consistent(We) of
	true ->
	    We;
	false ->
	    io:format("Dissolving would cause an inconsistent object structure.")
    end.

%% complement([Face], We) -> We'
%%  Dissolve all faces BUT the given faces. Also invalidate the
%%  mirror face if it existed and was dissolved.
complement(Fs0, #we{fs=Ftab0}=We0) when is_list(Fs0) ->
    Fs = ordsets:subtract(gb_trees:keys(Ftab0), ordsets:from_list(Fs0)),
    case faces(Fs, Fs0, We0) of
        #we{mirror=none}=We -> We;
        #we{mirror=Face,fs=Ftab}=We ->
            case gb_trees:is_defined(Face, Ftab) of
                false -> We;
                true -> We#we{mirror=none}
            end
    end;
complement(Fs, We) -> complement(gb_sets:to_list(Fs), We).

optimistic_dissolve(Faces0, Compl, We0) ->
    %% Optimistically assume that we have a simple region without
    %% any holes.
    case outer_edge_loop(Faces0, We0) of
	error ->
	    %% Assumption was wrong. We need to partition the selection
	    %% and dissolve each partition in turn.
	    Parts = wings_sel:face_regions(Faces0, We0),
	    complex_dissolve(Parts, We0);
	[_|_]=Loop ->
	    %% Assumption was correct.
	    simple_dissolve(Faces0, Compl, Loop, We0)
    end.

%% simple_dissolve(Faces, Loop, We0) -> We
%%  Dissolve a region of faces with no holes and no
%%  repeated vertices in the outer edge loop.

simple_dissolve(Faces0, Compl, Loop, We0) ->
    Faces = to_gb_set(Faces0),
    OldFace = gb_sets:smallest(Faces),
    Mat = wings_facemat:face(OldFace, We0),
    We1 = fix_materials(Faces, Compl, We0),
    #we{es=Etab0,fs=Ftab0,he=Htab0} = We1,
    {Ftab1,Etab1,Htab} = simple_del(Faces, Ftab0, Etab0, Htab0, We1),
    {NewFace,We2} = wings_we:new_id(We1),
    Ftab = gb_trees:insert(NewFace, hd(Loop), Ftab1),
    Last = lists:last(Loop),
    Etab = update_outer([Last|Loop], Loop, NewFace, Ftab, Etab1),
    We = We2#we{es=Etab,fs=Ftab,he=Htab},
    wings_facemat:assign(Mat, [NewFace], We).

fix_materials(Del,Keep,We) ->
    case gb_sets:size(Del) < length(Keep) of
	true ->
	    wings_facemat:delete_faces(Del,We);
	false ->
	    wings_facemat:keep_faces(Keep,We)
    end.

to_gb_set(List) when is_list(List) ->
    gb_sets:from_list(List);
to_gb_set(S) -> S.

%% Delete faces and inner edges for a simple region.
simple_del(Faces, Ftab0, Etab0, Htab0, We) ->
    case {gb_trees:size(Ftab0),gb_sets:size(Faces)} of
	{AllSz,FaceSz} when AllSz < 2*FaceSz ->
	    %% At least half of the faces are selected.
	    %% It is faster to find the edges for the
	    %% unselected faces.
	    UnselFaces = ordsets:subtract(gb_trees:keys(Ftab0),
					  gb_sets:to_list(Faces)),

	    UnselSet = sofs:from_external(UnselFaces, [face]),
	    Ftab1 = sofs:from_external(gb_trees:to_list(Ftab0),
				       [{face,edge}]),
	    Ftab2 = sofs:restriction(Ftab1, UnselSet),
	    Ftab = gb_trees:from_orddict(sofs:to_external(Ftab2)),

	    Keep0 = wings_face:to_edges(UnselFaces, We),
	    Keep = sofs:set(Keep0, [edge]),
	    Etab1 = sofs:from_external(gb_trees:to_list(Etab0),
				       [{edge,info}]),
	    Etab2 = sofs:restriction(Etab1, Keep),
	    Etab = gb_trees:from_orddict(sofs:to_external(Etab2)),
	    
	    Htab = simple_del_hard(Htab0, sofs:to_external(Keep), undefined),
	    {Ftab,Etab,Htab};
	{_,_} ->
	    Ftab = lists:foldl(fun(Face, Ft) ->
				       gb_trees:delete(Face, Ft)
			       end, Ftab0, gb_sets:to_list(Faces)),
	    Inner = wings_face:inner_edges(Faces, We),
	    Etab = lists:foldl(fun(Edge, Et) ->
				       gb_trees:delete(Edge, Et)
			       end, Etab0, Inner),
	    Htab = simple_del_hard(Htab0, undefined, Inner),
	    {Ftab,Etab,Htab}
    end.

simple_del_hard(Htab, Keep, Remove) ->
    case gb_sets:is_empty(Htab) of
	true -> Htab;
	false -> simple_del_hard_1(Htab, Keep, Remove)
    end.

simple_del_hard_1(Htab, Keep, undefined) ->
    gb_sets:intersection(Htab, gb_sets:from_ordset(Keep));
simple_del_hard_1(Htab, undefined, Remove) ->
    gb_sets:difference(Htab, gb_sets:from_ordset(Remove)).

%% complex([Partition], We0) -> We0
%%  The general dissolve.

complex_dissolve([Faces|T], We0) ->
    Face = gb_sets:smallest(Faces),
    Mat = wings_facemat:face(Face, We0),
    We1 = wings_facemat:delete_faces(Faces, We0),
    Parts = outer_edge_partition(Faces, We1),
    We = do_dissolve(Faces, Parts, Mat, We0, We1),
    complex_dissolve(T, We);
complex_dissolve([], We) -> We.

do_dissolve(Faces, Ess, Mat, WeOrig, We0) ->
    We1 = do_dissolve_faces(Faces, We0),
    Inner = wings_face:inner_edges(Faces, WeOrig),
    We2 = delete_inner(Inner, We1),
    #we{he=Htab0} = We = do_dissolve_1(Ess, Mat, We2),
    Htab = gb_sets:difference(Htab0, gb_sets:from_list(Inner)),
    We#we{he=Htab}.

do_dissolve_1([EdgeList|Ess], Mat, #we{es=Etab0,fs=Ftab0}=We0) ->
    {Face,We1} = wings_we:new_id(We0),
    Ftab = gb_trees:insert(Face, hd(EdgeList), Ftab0),
    Last = lists:last(EdgeList),
    Etab = update_outer([Last|EdgeList], EdgeList, Face, Ftab, Etab0),
    We2 = We1#we{es=Etab,fs=Ftab},
    We = wings_facemat:assign(Mat, [Face], We2),
    do_dissolve_1(Ess, Mat, We);
do_dissolve_1([], _Mat, We) -> We.

do_dissolve_faces(Faces, #we{fs=Ftab0}=We) ->
    Ftab = lists:foldl(fun(Face, Ft) ->
			       gb_trees:delete(Face, Ft)
		       end, Ftab0, gb_sets:to_list(Faces)),
    We#we{fs=Ftab}.

delete_inner(Inner, #we{es=Etab0}=We) ->
    Etab = lists:foldl(fun(Edge, Et) ->
			       gb_trees:delete(Edge, Et)
		       end, Etab0, Inner),
    We#we{es=Etab}.

update_outer([Pred|[Edge|Succ]=T], More, Face, Ftab, Etab0) ->
    #edge{rf=Rf} = R0 = gb_trees:get(Edge, Etab0),
    Rec = case gb_trees:is_defined(Rf, Ftab) of
	      true ->
		  ?ASSERT(false == gb_trees:is_defined(R0#edge.lf, Ftab)),
		  LS = succ(Succ, More),
		  R0#edge{lf=Face,ltpr=Pred,ltsu=LS};
	      false ->
		  ?ASSERT(true == gb_trees:is_defined(R0#edge.lf, Ftab)),
		  RS = succ(Succ, More),
		  R0#edge{rf=Face,rtpr=Pred,rtsu=RS}
	  end,
    Etab = gb_trees:update(Edge, Rec, Etab0),
    update_outer(T, More, Face, Ftab, Etab);
update_outer([_], _More, _Face, _Ftab, Etab) -> Etab.

succ([Succ|_], _More) -> Succ;
succ([], [Succ|_]) -> Succ.

%% outer_edge_loop(FaceSet,WingedEdge) -> [Edge] | error.
%%  Partition the outer edges of the FaceSet into a single closed loop.
%%  Return 'error' if the faces in FaceSet does not form a
%%  simple region without holes.
%%
%%  Equvivalent to
%%      case outer_edge_partition(FaceSet,WingedEdge) of
%%         [Loop] -> Loop;
%%         [_|_] -> error
%%      end.
%%  but faster.

outer_edge_loop(Faces, We) ->
    case lists:sort(collect_outer_edges(Faces, We)) of
	[] -> error;
	[{Key,Val}|Es0] ->
	    case any_duplicates(Es0, Key) of
		false ->
		    Es = gb_trees:from_orddict(Es0),
		    N = gb_trees:size(Es),
		    outer_edge_loop_1(Val, Es, Key, N, []);
		true -> error
	    end
    end.

outer_edge_loop_1({Edge,V}, _, V, 0, Acc) ->
    %% This edge completes the loop, and we have used all possible edges.
    [Edge|Acc];
outer_edge_loop_1({_,V}, _, V, _N, _) ->
    %% Loop is complete, but we haven't used all edges.
    error;
outer_edge_loop_1({_,_}, _, _, 0, _) ->
    %% We have used all possible edges, but somehow the loop
    %% is not complete. I can't see how this is possible.
    erlang:error(internal_error);
outer_edge_loop_1({Edge,Vb}, Es, EndV, N, Acc0) ->
    Acc = [Edge|Acc0],
    outer_edge_loop_1(gb_trees:get(Vb, Es), Es, EndV, N-1, Acc).

any_duplicates([{V,_}|_], V) -> true;
any_duplicates([_], _) -> false;
any_duplicates([{V,_}|Es], _) -> any_duplicates(Es, V).

%% outer_edge_partition(FaceSet, WingedEdge) -> [[Edge]].
%%  Partition the outer edges of the FaceSet. Each partion
%%  of edges form a closed loop with no repeated vertices.
%%    Outer edges are edges that have one face in FaceSet
%%  and one outside.
%%    It is assumed that FaceSet consists of one region returned by
%%  wings_sel:face_regions/2.

outer_edge_partition(Faces, We) ->
    F0 = collect_outer_edges(Faces, We),
    F = gb_trees:from_orddict(wings_util:rel2fam(F0)),
    partition_edges(F, []).

collect_outer_edges(Faces, We) when is_list(Faces) ->
    collect_outer_edges_1(Faces, gb_sets:from_list(Faces), We);
collect_outer_edges(Faces, We) ->
    collect_outer_edges_1(gb_sets:to_list(Faces), Faces, We).

collect_outer_edges_1(Fs0, Faces0, #we{fs=Ftab}=We) ->
    case {gb_trees:size(Ftab),gb_sets:size(Faces0)} of
	{AllSz,FaceSz} when AllSz < 2*FaceSz ->
	    Fs = ordsets:subtract(gb_trees:keys(Ftab), Fs0),
	    Faces = gb_sets:from_ordset(Fs),
	    Coll = collect_outer_edges_a(Faces),
	    wings_face:fold_faces(Coll, [], Fs, We);
	{_,_} ->
	    Coll = collect_outer_edges_b(Faces0),
	    wings_face:fold_faces(Coll, [], Fs0, We)
    end.

collect_outer_edges_a(Faces) ->
    fun(Face, _, Edge, #edge{ve=V,vs=OtherV,lf=Face,rf=Other}, Acc) ->
	    case gb_sets:is_member(Other, Faces) of
		false -> [{V,{Edge,OtherV}}|Acc];
		true -> Acc
	    end;
       (Face, _, Edge, #edge{ve=OtherV,vs=V,rf=Face,lf=Other}, Acc) ->
	    case gb_sets:is_member(Other, Faces) of
		false -> [{V,{Edge,OtherV}}|Acc];
		true -> Acc
	    end
    end.

collect_outer_edges_b(Faces) ->
    fun(Face, _, Edge, #edge{vs=V,ve=OtherV,lf=Face,rf=Other}, Acc) ->
	    case gb_sets:is_member(Other, Faces) of
		false -> [{V,{Edge,OtherV}}|Acc];
		true -> Acc
	    end;
       (Face, _, Edge, #edge{vs=OtherV,ve=V,rf=Face,lf=Other}, Acc) ->
	    case gb_sets:is_member(Other, Faces) of
		false -> [{V,{Edge,OtherV}}|Acc];
		true -> Acc
	    end
    end.

partition_edges(Es0, Acc) ->
    case gb_trees:is_empty(Es0) of
	true -> Acc;
	false ->
 	    {Key,Val,Es1} = gb_trees:take_smallest(Es0),
	    {Cycle,Es} = part_collect_cycle(Key, Val, Es1, []),
	    partition_edges(Es, [Cycle|Acc])
    end.

%% part_collect_cycle(Vertex, VertexInfo, EdgeInfo, Acc0) ->
%%    none | {[Edge],EdgeInfo}
%%  Collect the cycle starting with Vertex.
%%
%%  Note: This function can only return 'none' when called
%%  recursively.

part_collect_cycle(_, repeated, _, _) ->
    %% Repeated vertex - we are not allowed to go this way.
    %% Can only happen if we were called recursively because
    %% a fork was encountered.
    none;
part_collect_cycle(_Va, [{Edge,Vb}], Es0, Acc0) ->
    %% Basic case. Only one way to go.
    Acc = [Edge|Acc0],
    case gb_trees:lookup(Vb, Es0) of
	none ->
	    {Acc,Es0};
	{value,Val} ->
	    Es = gb_trees:delete(Vb, Es0),
	    part_collect_cycle(Vb, Val, Es, Acc)
    end;
part_collect_cycle(Va, [Val|More], Es0, []) ->
    %% No cycle started yet and we have multiple choice of
    %% edges out from this vertex. It doesn't matter which
    %% edge we follow, so we'll follow the first one.
    {Cycle,Es} = part_collect_cycle(Va, [Val], Es0, []),
    {Cycle,gb_trees:insert(Va, More, Es)};
part_collect_cycle(Va, Edges, Es0, Acc) ->
    %% We have a partially collected cycle and we have a
    %% fork (multiple choice of edges). Here we must choose
    %% an edge that closes the cycle without passing Va
    %% again (because repeated vertices are not allowed).
    Es = gb_trees:insert(Va, repeated, Es0),
    part_fork(Va, Edges, Es, Acc, []).
    
part_fork(Va, [Val|More], Es0, Acc, Tried) ->
    %% Try to complete the cycle by following this edge.
    case part_collect_cycle(Va, [Val], Es0, Acc) of
	none ->
	    %% Failure - try the next edge.
	    part_fork(Va, More, Es0, Acc, [Val|Tried]);
	{Cycle,Es} ->
	    %% Found a cycle. Update the vertex information
	    %% with all edges remaining.
	    {Cycle,gb_trees:update(Va, lists:reverse(Tried, More), Es)}
    end;
part_fork(_, [], _, _, _) ->
    %% None of edges were possible. Can only happen if this function
    %% was called recursively (i.e. if we hit another fork while
    %% processing a fork).
    none.
