anchor.diff:  -record(state, {
       active   :: boolean(),
       pending  :: pid(),
  -    requests :: gb_tree(),
  +    assigned :: gb_tree(),
       fetched  :: gb_tree(),
       stored   :: gb_tree()}).
   
  
positive.code:     #state{
          active=true,
          pending=Pending,
          requests=Requests} = State,
      Request = etorrent_utils:find(
          fun({{Index, _, _}, Peers}) ->
              (etorrent_pieceset:is_member(Index, Peerset))
              andalso (not lists:member(Pid, Peers))
          end, gb_trees:to_list(Requests)),
      case Request of
          false ->
              {reply, {ok, assigned}, State};
          {{Index, Offset, Length}=Chunk, Peers} ->
              etorrent_chunkstate:assigned(Index, Offset, Length, Pid, Pending),
              NewRequests = gb_trees:update(Chunk, [Pid|Peers], Requests),
              NewState = State#state{requests=NewRequests},
              {reply, {ok, [Chunk]}, NewState}
      end.
  
  
negative.code:     %% Load a request that was assigned by etorrent_progress
      #state{
          active=true,
          assigned=Assigned} = State,
      Chunk = {Index, Offset, Length},
      NewAssigned = add_assigned(Chunk, Pid, Assigned),
      NewState = State#state{assigned=NewAssigned},
      {noreply, NewState};
  
  handle_info({chunk, {dropped, Index, Offset, Length, Pid}}, State) ->
      #state{
          active=true,
          assigned=Assigned,
          fetched=Fetched,
          stored=Stored} = State,
      Chunk = {Index, Offset, Length},
      Isstored = is_stored(Chunk, Stored),
      Isfetched = Isstored orelse is_fetched(Chunk, Fetched),
      Wasfetched = Isfetched orelse has_fetched(Chunk, Pid, Fetched),
      NewState = if
          Isstored ->
              State;
          Isfetched and Wasfetched ->
              NewFetched = del_fetched(Chunk, Pid, Fetched),
              Stillfetched = is_fetched(Chunk, NewFetched),
              if  Stillfetched ->
                      State#state{fetched=NewFetched};
                  not Stillfetched  ->
                      NewAssigned = add_assigned(Chunk, Assigned),
                      State#state{assigned=NewAssigned, fetched=NewFetched}
              end;
          Isfetched ->
              State;
          true ->
              NewAssigned = del_assigned(Chunk, Pid, Assigned),
              State#state{assigned=NewAssigned}
      end,
      {noreply, NewState};
  
  handle_info({chunk, {fetched, Index, Offset, Length, Pid}}, State) ->
      #state{
          active=true,
          assigned=Assigned,
          fetched=Fetched,
          stored=Stored} = State,
      Chunk = {Index, Offset, Length},
      Isstored = is_stored(Chunk, Stored),
      Isfetched = Isstored orelse is_fetched(Chunk, Fetched),
      Notify = fun(Peer) ->
          etorrent_chunkstate:fetched(Index, Offset, Length, Pid, Peer)
      end,
      NewState = if
          Isstored ->
              State;
          Isfetched ->
              NewFetched = add_fetched(Chunk, Pid, Fetched),
              State#state{fetched=NewFetched};
          not Isfetched ->
              NewFetched = add_fetched(Chunk, Pid, Fetched),
              NewAssigned = del_assigned(Chunk, Assigned),
              [Notify(Peer) || Peer <- get_assigned(Chunk, Assigned)],
              State#state{ assigned=NewAssigned, fetched=NewFetched}
      end,
      {noreply, NewState};
  
  handle_info({chunk, {stored, Index, Offset, Length, Pid}}, State) ->
      #state{
          active=true,
          assigned=Assigned,
          fetched=Fetched,
          stored=Stored} = State,
      Chunk = {Index, Offset, Length},
      case has_fetched(Chunk, Pid, Fetched) of
          false ->
              {noreply, State};
          true ->
              NewState = State#state{
                  fetched=del_fetched(Chunk, Pid, Fetched),
                  stored=add_stored(Chunk, Stored)},
              {noreply, NewState}
      end.
  
  terminate(_, State) ->
      {ok, State}.
  
  code_change(_, State, _) ->
      {ok, State}.
  
  
  %% @doc Get the list of all assigned chunks
  -spec get_assigned(gb_tree()) -> [chunkspec()].
  get_assigned(Assigned) ->
      gb_trees:keys(Assigned).
  
  %% @doc Get the list of peers that has been assigned a chunk
  -spec get_assigned(chunkspec(), gb_tree()) -> [pid()].
  get_assigned(Chunk, Assigned) ->
      gb_trees:get(Chunk, Assigned).
  
  %% @doc Check if a peer has been assigned a chunk
  -spec has_assigned(chunkspec(), pid(), gb_tree()) -> boolean().
  has_assigned(Chunk, Pid, Assigned) ->
      has_peer(Chunk, Pid, Assigned).
  
  -spec add_assigned(chunkspec(), gb_tree()) -> gb_tree().
  add_assigned(Chunk, Assigned) ->
      gb_trees:insert(Chunk, [], Assigned).
  
  %% @doc Append a peer to the list of peers that has been assigned a chunk
  -spec add_assigned(chunkspec(), pid(), gb_tree()) -> gb_tree().
  add_assigned(Chunk, Pid, Assigned) ->
      add_peer(Chunk, Pid, Assigned).
  
  %% @doc Delete a peer from the list of peers that has been assigned a chunk
  -spec del_assigned(chunkspec(), pid(), gb_tree()) -> gb_tree().
  del_assigned(Chunk, Pid, Assigned) ->
      del_peer(Chunk, Pid, Assigned).
  
  %% @doc Delete all peers from the list of peers that has been assigned a chunk
  -spec del_assigned(chunkspec(), gb_tree()) -> gb_tree().
  del_assigned(Chunk, Assigned) ->
      gb_trees:delete(Chunk, Assigned).
  
  %% @doc Append a peer to the list of peers that has fetched a chunk
  -spec add_fetched(chunkspec(), pid(), gb_tree()) -> gb_tree().
  add_fetched(Chunk, Pid, Fetched) ->
      add_peer(Chunk, Pid, Fetched).
  
  %% @doc Delete a peer from the list of peers that has fetched a chunk
  -spec del_fetched(chunkspec(), pid(), gb_tree()) -> gb_tree().
  del_fetched(Chunk, Pid, Fetched) ->
      del_peer(Chunk, Pid, Fetched).
  
  %% @doc Check if a chunk is a member of the set of fetched chunks
  -spec is_fetched(chunkspec(), gb_tree()) -> boolean().
  is_fetched(Chunk, Fetched) ->
      gb_trees:is_defined(Chunk, Fetched)
      andalso length(gb_trees:get(Chunk, Fetched)) > 0.
  
  %% @doc Check if a peer has fetched a chunk
  -spec has_fetched(chunkspec(), pid(), gb_tree()) -> boolean().
  has_fetched(Chunk, Pid, Fetched) ->
      has_peer(Chunk, Pid, Fetched).
  
  
  %% @doc Add a chunk to the set of stored chunks
  -spec add_stored(chunkspec(), gb_set()) -> gb_set().
  add_stored(Chunk, Stored) ->
      gb_sets:insert(Chunk, Stored).
  
  %% @doc Check if a chunk is a member of the set of stored chunks
  -spec is_stored(chunkspec(), gb_set()) -> boolean().
  is_stored(Chunk, Stored) ->
      gb_sets:is_member(Chunk, Stored).
  
  %% @doc Add a peer to a set of peers that are associated with a chunk
  -spec add_peer(chunkspec(), pid(), gb_tree()) -> gb_tree().
  add_peer(Chunk, Pid, Requests) ->
      case gb_trees:lookup(Chunk, Requests) of
          none ->
              gb_trees:insert(Chunk, [Pid], Requests);
          {value, Pids} ->
              %% Assert that a peer only fetches a chunk once
              not lists:member(Pid, Pids) orelse error(badarg),
              gb_trees:update(Chunk, [Pid|Pids], Requests)
      end.
  
  %% @doc Delete a peer from a set of peers associated with a chunk
  -spec del_peer(chunkspec(), pid(), gb_tree()) -> gb_tree().
  del_peer(Chunk, Pid, Requests) ->
      case gb_trees:lookup(Chunk, Requests) of
          none ->
              Requests;
          {value, Pids} ->
              NewPids = Pids -- [Pid],
              gb_trees:update(Chunk, NewPids, Requests)
      end.
  
  %% @doc Check if a peer is a member of the set of peers associated with a chunk
  -spec has_peer(chunkspec(), pid(), gb_tree()) -> boolean().
  has_peer(Chunk, Pid, Requests) ->
      gb_trees:is_defined(Chunk, Requests)
      andalso lists:member(Pid, gb_trees:get(Chunk, Requests)).
  
  
  -ifdef(TEST).
  