anchor.diff:  gui_max(BL) when BL < 10 -> 30;
   gui_max(BL) -> 3 * BL.
   
  -gui_response(_Tab,Reply,Gres) ->
  -    Reply ! Gres.
  -
  -gui_nop(Tab,GuiResult,#state{gs=GS,reply=Reply}=State) -> 
  -    ?Log("~p gui_nop () ~p ~p~n", [erlang:now(), GuiResult#gres.state, GuiResult#gres.loop]),
  -    #bs{bottom=BufBottom} = State#state.bs,
  -    #gs{top=GuiTop,bottom=GuiBottom}=GS,
  -    gui_response(Tab,Reply,GuiResult#gres{bufBottom=BufBottom,guiTop=GuiTop,guiBottom=GuiBottom}),
  +gui_response(_Tab,ReplyTo,Gres) ->
  +    ReplyTo ! Gres.
  +
  +gui_nop(Tab,GuiResult,#state{bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) -> 
  +    ?Log("gui_nop () ~p ~p~n", [GuiResult#gres.state, GuiResult#gres.loop]),
  +    gui_response(Tab,ReplyTo,GuiResult#gres{bufBot=BufBot,rawTop=RawTop,rawBot=RawBot}),
       State.
   
  -gui_replace(Tab,GuiTop,GuiBottom,GuiResult,#state{gs=GS,reply=Reply}=State) ->
  -    ?Log("~p gui_replace (~p ~p) ~p ~p~n", [erlang:now(), GuiTop, GuiBottom, GuiResult#gres.state, GuiResult#gres.loop]),
  -    #bs{bottom=BufBottom} = State#state.bs,
  -    Rows=gui_rows(Tab,GuiTop,GuiBottom,State),
  -    gui_response(Tab,Reply,GuiResult#gres{operation=rpl,rows=Rows,bufBottom=BufBottom,guiTop=GuiTop,guiBottom=GuiBottom}),
  -    State#state{gs=GS#gs{top=GuiTop,bottom=GuiBottom}}.
  -
  -gui_prepend(Tab,GuiTop,GuiBottom,GuiResult,#state{gl=GL,gs=GS,reply=Reply}=State) ->
  -    case GS#gs.top-1 of
  -        GuiBottom ->    ?Log("~p gui_prepend (~p ~p) ~p ~p~n", [erlang:now(), GuiTop, GuiBottom, GuiResult#gres.state, GuiResult#gres.loop]);
  -        GB ->           ?Log("~p gui_prepend (~p ~p) ~p ~p EXPECTED GuiBottom ~p~n", [erlang:now(), GuiTop, GuiBottom, GuiResult#gres.state, GuiResult#gres.loop, GB])
  -    end,
  -    #bs{bottom=BufBottom} = State#state.bs,
  -    NewGuiBottom = if 
  -        (GS#gs.bottom >= GuiTop+GL) ->  GuiTop+GL-1;
  -        true ->                         GS#gs.bottom
  -    end,
  -    Rows=gui_rows(Tab,GuiTop,GuiBottom,State),
  -    gui_response(Tab,Reply,GuiResult#gres{operation=prp,rows=Rows,bufBottom=BufBottom,guiTop=GuiTop,guiBottom=NewGuiBottom}),
  -    State#state{gs=GS#gs{top=GuiTop,bottom=NewGuiBottom}}.
  -
  -gui_append(Tab,GuiTop,GuiBottom,GuiResult,#state{gl=GL, gs=GS,reply=Reply}=State) ->
  -    case GS#gs.bottom+1 of
  -        GuiTop ->   ?Log("~p gui_append (~p ~p) ~p ~p~n", [erlang:now(), GuiTop, GuiBottom, GuiResult#gres.state, GuiResult#gres.loop]);
  -        GT ->       ?Log("~p gui_append (~p ~p) ~p ~p EXPECTED GuiTop ~p~n", [erlang:now(), GuiTop, GuiBottom, GuiResult#gres.state, GuiResult#gres.loop, GT])
  -    end,
  -    #bs{bottom=BufBottom} = State#state.bs,
  -    NewGuiTop = if 
  -        (GS#gs.top =< GuiBottom-GL) ->  GuiBottom-GL+1;
  -        true ->                         GS#gs.top
  -    end,
  -    Rows=gui_rows(Tab,GuiTop, GuiBottom, State),
  -    gui_response(Tab,Reply,GuiResult#gres{operation=app,rows=Rows,bufBottom=BufBottom,guiTop=NewGuiTop,guiBottom=GuiBottom}),
  -    State#state{gs=GS#gs{top=NewGuiTop,bottom=GuiBottom}}.
  +gui_replace(Tab,GuiTop,GuiBot,GuiResult,#state{bufBot=BufBot,replyTo=ReplyTo}=State) ->
  +    ?Log("gui_replace (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]),
  +    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  +    gui_response(Tab,ReplyTo,GuiResult#gres{operation=rpl,rows=Rows,bufBot=BufBot,rawTop=GuiTop,rawBot=GuiBot}),
  +    State#state{rawTop=GuiTop,rawBot=GuiBot}.
  +
  +gui_prepend(Tab,GuiTop,GuiBot,GuiResult,#state{gl=GL,bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) ->
  +    case RawTop-1 of
  +        GuiBot ->       ?Log("gui_prepend (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]);
  +        GB ->           ?Log("gui_prepend (~p ~p) ~p ~p EXPECTED RawBot ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop, GB])
  +    end,
  +    NewRawBot = if 
  +        (RawBot >= GuiTop+GL) ->        GuiTop+GL-1;
  +        true ->                         RawBot
  +    end,
  +    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  +    gui_response(Tab,ReplyTo,GuiResult#gres{operation=prp,rows=Rows,bufBot=BufBot,rawTop=GuiTop,rawBot=NewRawBot}),
  +    State#state{rawTop=GuiTop,rawBot=NewRawBot}.
  +
  +gui_append(Tab,GuiTop,GuiBot,GuiResult,#state{gl=GL,bufBot=BufBot,rawTop=RawTop,rawBot=RawBot,replyTo=ReplyTo}=State) ->
  +    case RawBot+1 of
  +        GuiTop ->   ?Log("gui_append (~p ~p) ~p ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop]);
  +        GT ->       ?Log("gui_append (~p ~p) ~p ~p EXPECTED RawTop ~p~n", [GuiTop, GuiBot, GuiResult#gres.state, GuiResult#gres.loop, GT])
  +    end,
  +    NewRawTop = if 
  +        (RawTop =< GuiBot-GL) ->        GuiBot-GL+1;
  +        true ->                         RawTop
  +    end,
  +    Rows=gui_rows(Tab,GuiTop,GuiBot,State),
  +    gui_response(Tab,ReplyTo,GuiResult#gres{operation=app,rows=Rows,bufBot=BufBot,rawTop=NewRawTop,rawBot=GuiBot}),
  +    State#state{rawTop=NewRawTop,rawBot=GuiBot}.
   
   gui_rows(_Tab,Top,Bottom,#state{rowFun=RowFun,tableId=TableId}) ->
  -    % ?Log("~p RowFun ~p~n", [erlang:now(),RowFun]),
  +    % ?Log("RowFun ~p~n", [RowFun]),
       Rows = ets:select(TableId,[{'$1',[{'>=',{element,1,'$1'},Top},{'=<',{element,1,'$1'},Bottom}],['$_']}]),
       [gui_row_expand(R, TableId, RowFun) || R <- Rows].
   
  
positive.code:     State1 = prefetch(SN,State0),          %% only when filling
      gui_nop(Tab,#gres{state=SN},State1).
  
  serve_fwd(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{bottom=GuiBottom}}=State0) ->
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,false,State0);
        (BufBottom =< BL) ->
            serve_first(Tab,SN,State0);
        (GuiBottom+BL+BL =< BufBottom) ->
            gui_append(Tab,GuiBottom+1,GuiBottom+BL,#gres{state=SN},State0);
        true ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_append(Tab,BufBottom-BL+1,BufBottom,#gres{state=SN},State1)
      end.
  
  serve_ffwd(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{bottom=GuiBottom}}=State0) ->
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,false,State0);
        (BufBottom =< BL) ->
            serve_fwd(Tab,SN,State0);
        (GuiBottom =< BL) ->
            serve_fwd(Tab,SN,State0);
        (GuiBottom+GuiBottom+BL =< BufBottom) ->
            gui_replace(Tab,GuiBottom+GuiBottom-BL+1,GuiBottom+GuiBottom,#gres{state=SN},State0);
        (GuiBottom+GuiBottom =< BufBottom) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_replace(Tab,GuiBottom+GuiBottom-BL+1,GuiBottom+GuiBottom,#gres{state=SN},State1);
        true ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_nop(Tab,#gres{state=SN,loop=GuiBottom+GuiBottom},State1)
      end.
  
  serve_first(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{top=GuiTop,bottom=GuiBottom}}=State0) ->
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,false,State0);
        (BufBottom >= BL+BL) and (GuiTop == 1) and (GuiBottom >= BL) ->
            gui_nop(Tab,#gres{state=SN},State0);
        (BufBottom >= BL+BL) and (GuiTop > 1) and (GuiTop =< BL+1) ->
            gui_prepend(Tab,1,GuiTop-1,#gres{state=SN},State0);
        (BufBottom >= BL+BL) ->
            gui_replace(Tab,1,BL,#gres{state=SN},State0);
        (BufBottom >= BL) and (GuiTop == 1) and (GuiBottom >= BL) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_nop(Tab,#gres{state=SN},State1);
        (BufBottom >= BL) and (GuiTop =< BL) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_replace(Tab,1,BL,#gres{state=SN},State1);
        (BufBottom >= BL) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_replace(Tab,1,BL,#gres{state=SN},State1);
        (BufBottom < BL) and (GuiTop == 1) and (GuiBottom == BufBottom) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_nop(Tab,#gres{state=SN},State1);
        (BufBottom < BL) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_replace(Tab,1,BufBottom,#gres{state=SN},State1)
      end.
  
  serve_last(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{top=GuiTop,bottom=GuiBottom}}=State0) ->
      State1 = prefetch(SN,State0),          %% only when filling
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,false,State0);
        (BufBottom =< BL) and (GuiTop == 1) and (GuiBottom == BufBottom) ->
            gui_nop(Tab,#gres{state=SN},State1);
        (BufBottom =< BL) ->
            gui_replace(Tab,1,BufBottom,#gres{state=SN},State1);
        (GuiBottom == BufBottom) and (GuiTop == BufBottom-BL+1) ->
            gui_nop(Tab,#gres{state=SN},State1);
        (GuiBottom >= BufBottom-BL+1) ->
            gui_append(Tab,GuiBottom+1,BufBottom,#gres{state=SN},State1);
        true ->
            gui_replace(Tab,BufBottom-BL+1,BufBottom,#gres{state=SN},State1)
      end.
  
  serve_bwd(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{top=GuiTop}}=State0) ->
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,true,State0);
        (BufBottom =< BL) ->
            serve_first(Tab,SN,State0);
        (GuiTop > BL) ->
            gui_prepend(Tab,GuiTop-BL,GuiTop-1,#gres{state=SN},State0);
        true ->
            serve_first(Tab,SN,State0)
      end.
  
  serve_fbwd(Tab,SN,#state{bl=BL,bs=#bs{bottom=BufBottom},gs=#gs{top=GuiTop}}=State0) ->
      if
        (BufBottom == 0) ->
            serve_empty(Tab,SN,true,State0);
        (GuiTop >= BL+BL) and (GuiTop div 2+BL == GuiTop-1)  ->
            gui_prepend(Tab,GuiTop div 2+1,GuiTop div 2+BL,#gres{state=SN},State0);
        (GuiTop >= BL+BL) ->
            gui_replace(Tab,GuiTop div 2+1,GuiTop div 2+BL,#gres{state=SN},State0);
        true ->
            serve_first(Tab,SN,State0)
      end.
  
  serve_target(Tab,SN,Target,#state{bl=BL, bs=#bs{bottom=BufBottom}, gs=#gs{top=GuiTop,bottom=GuiBottom}}=State0) ->
      if
        (BufBottom == 0) and (SN == complete) ->
            serve_empty(Tab,SN,true,State0);
        (BufBottom == 0) ->         
            %% no_data now, com back later 
            State1 = prefetch(SN,State0),          %% only when filling
            gui_nop(Tab,#gres{state=SN,loop=Target},State1);
        (Target =< 0) and (BufBottom+Target > 0) ->
            %% target given relative to BufBottom, retry with absolute target position 
            serve_target(Tab,SN,Target,State0);
        (Target =< 0)  ->
            serve_first(Tab,SN,State0);
        (Target =< BufBottom) and (BufBottom < BL) ->
            serve_first(Tab,SN,State0);
        (Target =< BufBottom) and (GuiTop > Target) ->
            %% serve block backward
            gui_replace(Tab,Target,Target+BL-1,#gres{state=SN},State0);
        (Target =< BufBottom) and (GuiBottom < Target) and (Target > BufBottom-BL-BL) ->
            %% serve block forward
            State1 = prefetch(SN,State0),          %% only when filling
            gui_replace(Tab,Target-BL+1,Target,#gres{state=SN},State1);
        (Target =< BufBottom) and (GuiBottom < Target) ->
            %% serve block forward
            gui_replace(Tab,Target-BL+1,Target,#gres{state=SN},State0);
        (Target > BufBottom) and (SN == completed) ->
            serve_last(Tab,SN,State0);
        (Target > BufBottom) ->
            State1 = prefetch(SN,State0),          %% only when filling
            gui_nop(Tab,#gres{state=SN,loop=Target},State1);
        true ->
  
negative.code:     % ).
  
  clear_data(#state{tableId=TableId}=State) -> 
      ?Log("clear_data~n", []),
      true = ets:delete_all_objects(TableId),    
      State#state{  bufTop = 0          
                  , bufBot = 0          
                  , dirtyTop = 99999999 
                  , dirtyBot = 0        
                  , dirtyCount = 0     
                  , rawTop = 0         
                  , rawBot = 0         
                  }. 
  
  append_data(SN, {[],_Complete},State) -> 
      NewPfc=State#state.pfc-1,
      case NewPfc of
          0 ->    ?Log("append_data -- count ~p~n", [0]);
          _ ->    ?Log("append_data -- count ~p PendingFetchCount ~p~n", [0, NewPfc])
      end,
      serve_stack(SN, State#state{pfc=NewPfc});
  append_data(SN, {Recs,_Complete},#state{tableId=TableId,bufBot=BufBot}=State) ->
      NewPfc=State#state.pfc-1,
      Count = length(Recs),
      NewBufBot = BufBot+Count,
      case NewPfc of
          0 ->    ?Log("append_data -- count ~p bufBottom ~p~n", [Count,NewBufBot]);
          _ ->    ?Log("append_data -- count ~p bufBottom ~p pfc ~p~n", [Count,NewBufBot,NewPfc])
      end,
      ets:insert(TableId, [list_to_tuple([I,nop|[R]])||{I,R}<-lists:zip(lists:seq(BufBot+1, NewBufBot), Recs)]),
      %% ToDo: insert into filter/sort table
      serve_stack(SN, State#state{pfc=NewPfc,bufBot=NewBufBot}).
  
  update_data(Tab,SN,ChangeList,State0) ->
      State1 = update_data_rows(ChangeList,State0),
      gui_nop(Tab,#gres{state=SN},State1).  %% ToDo: return list of Ids chosen by fsm for inserts
  
  update_data_rows([], State) -> State;
  update_data_rows([Ch|ChangeList], State0) ->
      State1 = update_data_row(Ch, State0),
      update_data_rows(ChangeList, State1).
  
  update_data_row([undefined,Fields], #state{tableId=TableId,bufBot=BufBot,dirtyTop=DT0,dirtyCount=DC0}=State0) ->
      Id = BufBot+1,          %% ToDo: map Fields to complete rows ("" for undefined fields)
      ets:insert(TableId, list_to_tuple([Id,ins,{}|Fields])),    
      State0#state{dirtyTop=int_min(DT0,Id),dirtyBot=Id,dirtyCount=DC0+1};
  update_data_row([Id,Op,Fields], #state{tableId=TableId}=State0) when is_integer(Id) ->
      OldRow = ets:lookup(TableId, Id),
      {O,State1} = case {element(2,OldRow),Op} of
          {nop,nop} ->    {nop,State0};
          {nop,_} ->      DT = int_min(State0#state.dirtyTop,Id),
                          DB = int_max(State0#state.dirtyBot,Id),
                          DC = State0#state.dirtyCount+1,
                          {Op, State0#state{dirtyTop=DT,dirtyBot=DB,dirtyCount=DC}};
          {_,nop} ->      DC = State0#state.dirtyCount-1,
                          {nop,State0#state{dirtyCount=DC}};
          {ins,upd} ->    {ins,State0};
          {ins,ins} ->    {ins,State0};
          {ins,del} ->    DC = State0#state.dirtyCount-1,
                          {nop,State0#state{dirtyCount=DC}};
          {del,del} ->    {del,State0};
          {del,upd} ->    {upd,State0};
          {upd,upd} ->    {upd,State0};        
          {upd,del} ->    {del,State0}        
      end,
      ets:insert(TableId, list_to_tuple([Id,O,element(2,OldRow)|Fields])),
      State1.
  
  int_min(A,B) when A=<B -> A;
  int_min(_,B) -> B.
  