/* PeerCheckTasks - TimerTask that checks for good/bad up/downloaders.
   Copyright (C) 2003 Mark J. Wielaard
 
   This file is part of Snark.
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.klomp.snarkxl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TimerTask;

/**
 * TimerTask that checks for good/bad up/downloader. Works together
 * with the PeerCoordinator to select which Peers get (un)choked.
 */
class PeerCheckerTask extends TimerTask {
    private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
    
    private final PeerCoordinator coordinator;
    
    PeerCheckerTask(PeerCoordinator coordinator) {
        this.coordinator = coordinator;
    }
    
    private Random random = new Random();
    
    public void run() {
        synchronized(coordinator.peers) {
            Iterator it = coordinator.peers.iterator();
            if ((!it.hasNext()) || coordinator.halted()) {
                coordinator.peerCount = 0;
                coordinator.interestedAndChoking = 0;
                coordinator.setRateHistory(0, 0);
                coordinator.uploaders = 0;
                if (coordinator.halted())
                    cancel();
                return;
            }
            
            // Calculate total uploading and worst downloader.
            long worstdownload = Long.MAX_VALUE;
            Peer worstDownloader = null;
            
            int peers = 0;
            int uploaders = 0;
            int downloaders = 0;
            int removedCount = 0;
            
            long uploaded = 0;
            long downloaded = 0;
            
            // Keep track of peers we remove now,
            // we will add them back to the end of the list.
            List removed = new ArrayList();
            int uploadLimit = coordinator.allowedUploaders();
            boolean overBWLimit = coordinator.overUpBWLimit();
            while (it.hasNext()) {
                Peer peer = (Peer)it.next();
                
                // Remove dying peers // and seeder/seeder 
                if (!peer.isConnected() || (peer.isCompleted() && coordinator.isCompleted())) {
                    it.remove();
                    coordinator.removePeerFromPieces(peer);
                    coordinator.peerCount = coordinator.peers.size();
                    
                    if((peer.isCompleted() && coordinator.isCompleted()))//seeder/seeder
                        peer.disconnect(false);
                    
                    continue;
                }
                
                peers++;
                
                if (!peer.isChoking())
                    uploaders++;
                if (!peer.isChoked() && peer.isInteresting())
                    downloaders++;
                
                long upload = peer.getUploaded();
                uploaded += upload;
                long download = peer.getDownloaded();
                downloaded += download;
                peer.setRateHistory(upload, download);
                peer.resetCounters();
                
                Snark.debug(peer + ":", Snark.DEBUG);
                Snark.debug(" ul: " + upload/KILOPERSECOND
                        + " dl: " + download/KILOPERSECOND
                        + " i: " + peer.isInterested()
                        + " I: " + peer.isInteresting()
                        + " c: " + peer.isChoking()
                        + " C: " + peer.isChoked(),
                        Snark.DEBUG);
                
                // Choke half of them rather than all so it isn't so drastic...
                // unless this torrent is over the limit all by itself.
                boolean overBWLimitChoke = upload > 0 &&
                        ((overBWLimit && random.nextBoolean()) ||
                        (coordinator.overUpBWLimit(uploaded)));
                
                //toDo remove the comments
                 //ups! forgetting to remove it after tests
               // if(!peer.isCompleted() && !coordinator.isCompleted()){// workaround see below
       /* fwd huh, is here a bug?!
        * when we choking a snark-seeder than the seeder stop uploading to us about the download = 0 stuff
        * howlong this is not fixed in the orig snark we have to watchout here.
        *
        * if this anybody cleanup one day than see also the workaround fix in methode peer.setChoking(boolean)
        *
        * maybe i misunderstood the source but i'm not quite sure. so i do not remove comments to remember me in time!
        * this need still a deeper look at
        */
                    // If we are at our max uploaders and we have lots of other
                    // interested peers try to make some room.
                    // (Note use of coordinator.uploaders)
                    if ((((coordinator.uploaders == uploadLimit
                            && coordinator.interestedAndChoking > 0)
                            || coordinator.uploaders > uploadLimit
                            || overBWLimitChoke)
                            && !peer.isChoking()) ||
                            coordinator.hasBadRatio(peer))//fwd ratio
                    {
                        // Check if it still wants pieces from us.
                        if (!peer.isInterested()) {
                            Snark.debug("Choke uninterested peer: " + peer,
                                    Snark.INFO);
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            
                            // Put it at the back of the list
                            it.remove();
                            removed.add(peer);
                        } else if (overBWLimitChoke) {
                            Snark.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
                                    Snark.INFO);
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            removedCount++;
                            
                            // Put it at the back of the list for fairness, even though we won't be unchoking this time
                            it.remove();
                            removed.add(peer);
                        } else if (peer.isInteresting() && peer.isChoked()) {
                            // If they are choking us make someone else a downloader
                            Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            removedCount++;
                            
                            // Put it at the back of the list
                            it.remove();
                            removed.add(peer);
                        } else if (!peer.isInteresting() && !coordinator.isCompleted()) {
                            // If they aren't interesting make someone else a downloader
                            Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            removedCount++;
                            
                            // Put it at the back of the list
                            it.remove();
                            removed.add(peer);
                        }
       /* fwd huh, is here a bug?!
        * when we choking a snark-seeder than the seeder stop uploading to us about the download = 0 stuff
        * howlong this is not fixed in the orig snark we have to watchout here.
        *
        * if this anybody cleanup one day than see also the workaround fix in methode peer.setChoking(boolean)
        * edit: 
        * the problem seems to be the "peer.isInteresting()". often it returns true even when we are completed.
        * it should returning false if we are completed!
        * toDo: set all peers setInteresting(false) when we are completed.
        * check the PeerState havePiece(int piece) methode for activities about that.
        * check also the opposite if the peer is completed and shouldn't have any interests in our pieces
        * if it still ?wants? or display a interest on our pieces. there it seems to be the same and wrong behaviour.
        * 
        */
                        else if (peer.isInteresting()
                        && !peer.isChoked()
                        && download == 0 && //toDo: this need still a deeper look at
                                !coordinator.isCompleted() //<---  this line need to be done in snark too (if we are the seeder than who should uploading something to us?!
                                && coordinator.hasBadRatio(peer) )//fwd fix workaround do not choke seeders of orig snark .. see comment above
                        {
                            //ng but di We are downloadidn't receive anything...
                            Snark.debug("Choke downloader that doesn't deliver:"
                                    + peer, Snark.DEBUG);
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            removedCount++;
                            
                            // Put it at the back of the list
                            it.remove();
                            removed.add(peer);
                        } else if (peer.isInteresting() && !peer.isChoked() &&
                                coordinator.hasBadRatio(peer) )//fwd ratio
                        {
                            peer.setChoking(true);
                            uploaders--;
                            coordinator.uploaders--;
                            removedCount++;
                            
                            // Put it at the back of the list
                            it.remove();
                            removed.add(peer);
                        } else if (peer.isInteresting() && !peer.isChoked() &&
                                download < worstdownload && !peer.isCompleted() ) {
                            // Make sure download is good if we are uploading
                            worstdownload = download;
                            worstDownloader = peer;
                        } else if (upload < worstdownload && (coordinator.isCompleted() || coordinator.isOnlyseed())) {
                            // Make sure upload is good if we are seeding
                            worstdownload = upload;
                            worstDownloader = peer;
                        }
                    }
                
                peer.retransmitRequests();
                peer.keepAlive();
                
            }
            
            // Resync actual uploaders value
            // (can shift a bit by disconnecting peers)
            coordinator.uploaders = uploaders;
            
            // Remove the worst downloader if needed. (uploader if seeding)
            if (((uploaders == uploadLimit
                    && coordinator.interestedAndChoking > 0)
                    || uploaders > uploadLimit)
                    && worstDownloader != null) {
                Snark.debug("Choke worst downloader: " + worstDownloader,
                        Snark.DEBUG);
                
                worstDownloader.setChoking(true);
                coordinator.uploaders--;
                removedCount++;
                
                // Put it at the back of the list
                coordinator.peers.remove(worstDownloader);
                coordinator.peerCount = coordinator.peers.size();
                removed.add(worstDownloader);
            }
            
            // Optimistically unchoke a peer
            if ((!overBWLimit) && !coordinator.overUpBWLimit(uploaded))
                coordinator.unchokePeer();
            
            // Put peers back at the end of the list that we removed earlier.
            coordinator.peers.addAll(removed);
            coordinator.peerCount = coordinator.peers.size();
            coordinator.interestedAndChoking += removedCount;
            
            // store the rates
            coordinator.setRateHistory(uploaded, downloaded);
            
        }
    }
}
