/*
 * Client.java
 *
 Copyright (C) 2008, 2009 fwd
 
   This file is part of I2PSnarkXL.
 
   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
 */

package org.i2p.i2psnarkxl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;// sort
import java.util.HashMap;
import java.util.Random;
import java.util.Set;// sort
import java.util.TreeSet;// sort
import java.util.Iterator;// sort

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import java.io.IOException;

import org.i2p.i2psnarkxl.peermanager.utils.BTPeerIDByteDecoder;
//import java.io.IOException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Base64;
import net.i2p.data.Destination;

import org.klomp.snarkxl.MetaInfo;
import org.klomp.snarkxl.Peer;
//import org.klomp.snarkxl.PeerCoordinator;
import org.klomp.snarkxl.Snark;
import org.klomp.snarkxl.SnarkManager;

import net.i2p.I2PAppContext;
import net.i2p.util.Log;

import org.i2p.i2psnarkxl.BitfieldImage;

public class Client extends Thread implements Comparable, Runnable{
    private Log _log = new Log(Client.class);
    private I2PAppContext _context;
    
    private static final Client _instance = new Client();
    public synchronized static final Client instance() { return _instance; }
    
    public static final java.util.List clientsXL = new ArrayList();
    public static final java.util.List bannedClientsXL = new ArrayList();
    public static final java.util.List generalBannedClientsXL = new ArrayList();
    //public static final java.util.List bitfieldsXL = new ArrayList();// add one for each torrent
    //private SnarkManager _manager;
    
    private boolean stop = true;
    // Identifying properties.
    private final I2PSocket sock;
    private Peer peer;
    private final byte[] clientID;
    private final Destination destination;
    //private final byte[] ourID;
    final MetaInfo metainfo;
    
    private static ColorModel colormodel = generateColorModel();
    private byte[] bufferedImageByte = null;
    //toDo: make'em all private
    long starttime = -1; //first seen
    long stoppedtime = 0L; //last seen
    long connectedtime = -1;
    long previous_connected_time = 0L;
    long current_connection_start = 0L;
    long banned_since = 0L;
    long general_banned_since = 0L;
    long last_seen_on_tracker = -1;
    long last_superseed_time = 0;
    int pingsByTrackerClient = 0;
    int reconnected = 0;
    int piecesClientFirstSeen = -1;
    int piecesClient = -1;
    
    String metainfohash;
    String clientIDhash;
    boolean isConnected = false;
    boolean hasPeer = false;
    boolean hasbadratio = false;
    boolean choking = false;
    boolean  isBitfieldInit = false;
    private boolean updateIsRunning = false;
    
    long downloadrate = 0;
    long uploadrate = 0;
    long download_total = 0;
    long upload_total = 0;
    long old_download_total = 0;
    long old_upload_total = 0;
    
    private Thread t;
    /** Creates a new instance of Client */
    private Client( final Peer _peer, final I2PSocket _sock, byte[] _clientID, MetaInfo _metainfo){
        this.peer = _peer;
        this.sock = _sock;
        this.clientID = _clientID;
        //ourID = _ourID;
        this.metainfo = _metainfo;
        //new
        // todo remove the unneccessaries
        clientIDhash = Base64.encode(clientID);
        metainfohash = Base64.encode(_metainfo.getInfoHash()) ;
        
        if(starttime == -1)
            this.starttime = System.currentTimeMillis();// first seen but proberly not connected
        
        last_superseed_time = System.currentTimeMillis();
        long _connectedtime = System.currentTimeMillis();// last connecting try
        if(this.sock != null) this.destination = this.sock.getPeerDestination();
        else destination = null;
        synchronized(instance().clientsXL) {
            if(!instance().clientsXL.contains(this)){
                this.connectedtime = _connectedtime;
                // add to head
                instance().clientsXL.add(0,this);
                
                start();
            }else{
                updateInstance();
            }
        }
    }
    
    //Client xlClient = new Client(peer, getI2PSocket(), getPeerID().getID(), metainfo);
    public Client(final Peer _peer ){
        peer = null;
        sock = null;
        clientID =null;
        metainfo = null;
        destination = null;
        clientIDhash = null;
        metainfohash = null ;
        stop = false;
        starttime = -1;
        connectedtime = -1;
        Client client = new Client(_peer, _peer.getI2PSocket(), _peer.getPeerID().getID(), _peer.getMetaInfo());
        client = null;
    }
    
    /*
     * this is just for testing if a peer is in our clientlist
     * NEVER return this as a client!
     * just compare intern with list objects and return the list object or null!
     * see getClientByPeer(Peer)
     */
    private Client(boolean test, String _clientIDhash, String _metainfohash/*Peer cur*/){
        sock = null;//cur.getI2PSocket();// not neccessary
        clientID = null; //cur.getPeerID().getID();// need for compare/equals // not more neccessary
        metainfo =  null;// cur.getMetaInfo();// need for compare/equals // not more neccessary
        //clientIDhash = Base64.encode(cur.getPeerID().getID());
        //metainfohash = Base64.encode(cur.getMetaInfo().getInfoHash()) ;
        clientIDhash = _clientIDhash;
        metainfohash = _metainfohash;
        peer = null;//cur;// not neccessary
        destination = null;//cur.getPeerID().getAddress();// need for compare/equals // not more neccessary
        stop = false;
        starttime = -1;
        connectedtime = -1;
    }
    
    /** Creates a new instance of Client
     * is only called once to create the private _instance
     * private _instance is public reachable with Client.instance()
     */
    
    private Client(){
        _context = I2PAppContext.getGlobalContext();
        _log = _context.logManager().getLog(Client.class);
        //_manager = SnarkManager.instance();
        peer = null;
        sock = null;
        clientID =null;
        metainfo = null;
        destination = null;
        stop = false;
        starttime = System.currentTimeMillis();
    }
    
    
    private void updateInstance(){
        //have to contains this! else we are not here -> (clientsXL.indexOf(this))
        Client client = (Client)(instance().clientsXL.get(instance().clientsXL.indexOf(this)));
        long _connectedtime = System.currentTimeMillis();
        
        if(client != null){
            Peer _peer = client.peer;
            if(_peer != null){//damn ??? is this not thread safe?
                if(_peer.getI2PSocket() != null){
                    /*
                     * toDo check why we get sometimes 2 and more peers for this client
                     * some are bad and will fail after short time*
                     *
                     */
                    if(! _peer.getI2PSocket().isClosed() ){
                        return;
                    }
                }
                peerDisconnected(client, false);
                
            }
            // sock and metainfo are not updated. we need them to identify the clients about their first connections
            client.connectedtime = _connectedtime;
            client.peer = this.peer;
            client.reconnected++;
        }//todo else log .. better is better
        
    }
    
    /*
     * The vars in the Client.class are not the exact current values of the app
     * we have to refresh them while running to become uptodate.
     * The access to the static elements in this class is better performed than
     * the access elsewhere in other classes. For the templates (or a common gui) it's not neccessary to
     * get a exact value in time or on the fly. The exactness of values in time
     * we controll by the (thread) sleep() methode.
     * do not fly to the moon with this, you will crash ... but for a trip in space around the moon
     * it'll be ok.
     */
    private void update(){
        // do something
        boolean error = false;
        synchronized(instance().clientsXL) {
            if(instance().clientsXL.size() > 0 && instance().clientsXL.contains(this)){//user cleanup?; delete all? ; this
                Client client = ((Client)(instance().clientsXL.get(instance().clientsXL.indexOf(this))));
                if(client != null){
                    Peer _peer = client.peer;
                    if(_peer == null){//toDo make a cleanup methode.
                        //really? (thread safe?) toDo make a getPeerByClient(client) Methode and check all peers if they do not match!
                        //cleanup all
                        peerDisconnected(client, true);
                        return;// wait for next updateInstance() (reconnect with new peer)
                    /*
                     
                     
                        //store the goodies
                        // toDo:
                         // we might have lost the amount information that was delivered between the last sleep() till now
                         //
                     
                     **/
                    }
                    
                    // check if the peer is uptodate
                    //
                    /*
                     * todo check Why? when we stop a torrent but not with "stop all" the peer still returns that we are
                     * connected but the code shows this shouldn't be.
                     * see: PeerCoordinator halt() -> peer.disconnect() -> peer -> state = null
                     *
                     */
                    if(_peer.getI2PSocket() == null || _peer.getI2PSocket().isClosed()){//toDo make a cleanup methode.
                        //really? (thread safe?) toDo make a getI2PSocket(peer) Methode and check all peers if they do not match!
                        
                        //cleanup all
                        if(client.hasPeer){
                            //client.hasPeer = false; //taked it out for the second time. do not include it here again
                            
                            //capture connection time
                            if(client.isConnected){
                                //client.stoppedtime = System.currentTimeMillis();
                                if(current_connection_start > 1000L)
                                    client.previous_connected_time += System.currentTimeMillis() - client.current_connection_start;
                                
                                client.current_connection_start = 0L;
                                client.isConnected = false;
                            }
                            
                            //store the goodies
                        /*
                         * fixed
                         * store the goodies only if the peer is null!
                         */
                        }
                        client.downloadrate = 0;
                        client.uploadrate = 0;
                        client.choking = false;
                        if(client.peer != null){
                            long downS = client.peer.getDownloadTotal() ;
                            long upS   = client.peer.getDownloadTotal() ;
                            if(downS > 0L)
                                client.download_total = downS ;
                            if(upS > 0L)
                                client.upload_total = upS;
                        }
                        client.peer = null;
                        peerDisconnected(client, true);
                        return; // wait for next updateInstance() (reconnect with new peer)
                    }
                    
                    client.hasPeer = true;
                    
                    //capture connection time
                    if(client.isConnected != _peer.isConnected()){
                        client.isConnected = _peer.isConnected();
                        if(!client.isConnected){// switched from state connected to unconnected
                            //cleanup all
                            client.downloadrate = 0;
                            client.uploadrate = 0;
                            
                            //capture connection time
                            client.previous_connected_time += System.currentTimeMillis() - client.current_connection_start;
                            client.current_connection_start = 0L;
                        }else{// we are new connected
                            client.current_connection_start = System.currentTimeMillis();
                        }
                    }
                    
                    if(client.isConnected){//toDo make a refres methode
                        long _stoppedtime = _peer.getInactiveTime();
                        if(_stoppedtime > 0L){
                            client.stoppedtime = System.currentTimeMillis() - _stoppedtime;
                        }
                        client.downloadrate = _peer.getDownloadRate();
                        client.uploadrate = _peer.getUploadRate();
                        client.download_total = _peer.getDownloadTotal() ;
                        client.upload_total = _peer.getUploadTotal();
                        client.choking = _peer.isChoking();
                        //#############################---------------------------------------------------
                        if(//!client.choking &&
                                System.currentTimeMillis() - last_superseed_time > 20 * 1000 &&
                                !client.isCompleted() && _peer.isQueueEmty()){
                            superseed(client);
                            client.last_superseed_time = System.currentTimeMillis();
                        }
                        if(client.piecesClientFirstSeen == -1 && _peer.getCompleted() > 0 && //we might loss 1-2 pieces but we need to be sure the connection is working
                                System.currentTimeMillis() - client.current_connection_start > 2 * 60 * 1000){//first connect. give us some time to get all pieces
                            client.piecesClientFirstSeen = _peer.getCompleted();
                        }
                        //client.piecesClient = _peer.getCompleted();
                        if(_peer.getBitfield() != null && !isCompleted() && !isBitfieldInit && client.piecesClient > 0){
                            BitfieldImage bitfield = new BitfieldImage(_peer);
                            bitfield = null;
                            isBitfieldInit = true;
                        }
                        if(_peer.getCompleted() != client.piecesClient ){//todo make a oldPiecesCount and change only when old < now
                            // if(!client.isCompleted())// avoid from superseeders?? but we see the trackerlist!
                            // .. and checking for seeder/seeder. the Peer.hasSendBitfieldFirstTime does the job well ann ..
                            // normal snark did not active seeding and wait for requests. so .. no probs here
                            client.piecesClient = _peer.getCompleted();
                            if(_peer.getBitfield() != null && !isCompleted() ){
                                BitfieldImage bitfield = new BitfieldImage(_peer);
                                bitfield = null;
                            }
                            if(isCompleted()){// could happens here only once! (but you never knows what clients do!)
                                synchronized(BitfieldImage.instance().bitfieldsXL) {
                                    BitfieldImage.instance().refresh(client.metainfohash);
                                }
                            }
                            //}
                            //if(true){//test
                            //client.piecesClient = _peer.getCompleted();
                            try{
                                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                                ImageIO.write(client.getImageBar(_peer ,1,1), "png", byteArrayOutputStream);
                                client.bufferedImageByte = byteArrayOutputStream.toByteArray();
                                //client.bufferedImage = client.getImageBar(_peer ,1,1);//1,1 dummies real size is in methode self
                            } catch (IOException ioe) {
                                // _log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
                                //throw ioe;
                            }
                        }
                        if(client.isBanned())
                            _peer.disconnect(true);
                    }
                    client.hasbadratio = _peer.hasBadRatio();
                    
                    
                    
                }else{
                    error = true;
                }
            }else{
                error = true;
            }
        }
        if(error){
            synchronized(BitfieldImage.instance().bitfieldsXL) {
                BitfieldImage.instance().refresh(metainfohash);
            }
            halt();
        }
    }
    
    public void superseed(Client client){
        //XL -------------------- todo make superseed.class and move it there
        int rp = -1;
        Snark snark = SnarkManager.instance().getTorrentByMetaInfoHash(net.i2p.data.Base64.encode(metainfo.getInfoHash()));
        if(snark != null && snark.storage.isSuperseed){
            synchronized(snark.storage.sendPieceXL){
                if(client != null && client.isConnected && client.hasPeer && client.getBitmap() != null)
                rp = snark.storage.getRandomPiece(client.getBitmap().clone());
            }
            
            if(rp != -1 ){
                // client hasn't this piece, so we could announce that we have the piece'
                client.peer.have(rp);
                client.last_superseed_time = System.currentTimeMillis();
                // _log.error("superseed: " + client.getClientName() + " have(" + rp + ") ");
            }          
            
        }
        //-------------end XL -------------------------
    }
    
     /*
      * store goodies after peer == null or disconnected
      *
      * toDo: count all peers of Snark and compare the count with clients peer count
      * it seems that not all peers of snark becomes "null" after disconnecting
      * this might explain the rarely random increasing usage of resources by snark
      * proberly they been sorted out by kinda Hashmap situation and never truly removed
      * hint: count how often this routine is called with isUpdate == false;
      * on normal cirumstances this shouldn't happen but it happens all the times with reconnections!
      */
    
    private void peerDisconnected(Client client, boolean isUpdate){
        int weakup = 0;
        while(client.isUpdateRunning() && !isUpdate){//toDo make a methode
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e2) {
                e2.printStackTrace();
            }
            weakup ++;
            /*
             * we don't want stay forever
             */
            if(weakup >= 10)break;// one sec for a update should be more than enough toDo: log warn, something goes wrong
        }
        if(true){//cleanup methode.
            //really? (thread safe?) toDo make a getPeerByClient(client) Methode and check all peers if they do not match!
            //cleanup all
            if(client.hasPeer){
                client.hasPeer = false;
                
                //capture connection time
                if(client.isConnected){
                    
                    if(client.current_connection_start > 1000L)
                        client.previous_connected_time += System.currentTimeMillis() - client.current_connection_start;
                    
                    client.current_connection_start = 0L;
                    client.isConnected = false;
                }
                if(client.peer != null){
                    long downS = client.peer.getDownloadTotal() ;
                    long upS   = client.peer.getDownloadTotal() ;
                    if(downS > 0L)
                        client.download_total = downS ;
                    if(upS > 0L)
                        client.upload_total = upS;
                }
                
                //store the goodies
                        /* toDo:
                         * if peer == null:  we might have lost the amount information that was delivered between the last sleep() till now
                         */
                
                client.old_download_total += client.download_total ;
                client.old_upload_total += client.upload_total ;
            }
            client.downloadrate = 0;
            client.uploadrate = 0;
            client.choking = false;
            synchronized(BitfieldImage.instance().bitfieldsXL) {
                BitfieldImage.instance().refresh(client.metainfohash);
            }
        }
    }
    
    private boolean isUpdateRunning(){
        return updateIsRunning;
    }
    public boolean isConnected(){
        return isConnected;
    }
    
    /*
     * returns 0 if the client is not banned
     * returns 1 if the client is banned for all connections
     * returns 2 if the client is banned for one or more torrents
     **/
    public int getBanned(){
        if(instance().generalBannedClientsXL.contains((String)this.getClientAddress())){
            return 1;
        }
        if(instance().bannedClientsXL.contains(this)){
            return 2;
        }
        return 0;
    }
    
    /**
     * Whether or not we are choking the peer.
     */
    public boolean isChoking() {
        return choking;
    }
    /*
     * returns if the client is banned for this torrent
     **/
    public boolean isBanned(){
        if(instance().bannedClientsXL.contains(this)){
            return true;
        }
        if(instance().generalBannedClientsXL.contains((String)this.getClientAddress())){
            return true;
        }
        return false;
    }
    
    /*
     * remove the client from the Clientslist
     * the thread care about itself to cleanup and stopping
     * toDo: just check if the global threads count truly decrease. Murphy?!
     */
    public void removeFromList(){
        int weakup = 0;
        while(isUpdateRunning() ){//toDo make a methode
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e2) {
                e2.printStackTrace();
            }
            weakup ++;
            /*
             * we don't want stay forever
             */
            if(weakup >= 10)break;// one sec for a update should be more than enough toDo: log warn, something goes wrong
        }
        instance().clientsXL.remove(this);
        //instance().clientsXL.remove((Client)(instance().clientsXL.get(instance().clientsXL.indexOf(this))));
        //instance().clientsXL.add(0,this);
    }
    
    public void ban(boolean general){
        if(general){
            instance().generalBannedClientsXL.add(0,(String)this.getClientAddress());
            general_banned_since = System.currentTimeMillis();
        }else{
            instance().bannedClientsXL.add(0,this);
            banned_since = System.currentTimeMillis();
        }
        //instance().clientsXL.add(0,this);
    }
    
    public void unBan(boolean general){
        if(general){
            if(instance().generalBannedClientsXL.contains((String)this.getClientAddress())){
                instance().generalBannedClientsXL.remove((String)(instance().generalBannedClientsXL.get(instance().generalBannedClientsXL.indexOf((String)this.getClientAddress()))));
                general_banned_since = 0L;
            }
            
        }else if(instance().bannedClientsXL.contains(this)){
            instance().bannedClientsXL.remove((Client)(instance().bannedClientsXL.get(instance().bannedClientsXL.indexOf(this))));
            banned_since = 0L;
        }
        //instance().clientsXL.add(0,this);
    }
    
    public boolean hasBadRatio(){
        //toDo: check if we are seeding
        //  if(isCompleted()) return false;// we are seeding
        return hasbadratio;
    }
    
    
    public boolean hasPeer(){
        return hasPeer;
    }
    
    public boolean isCompleted(){
        if (getCompletedPercent() >= 100)
            return true;
        return false;
    }
    
    /*
     *
     * called by Peer.class disconnect()
     *
     */
    public void setPiecesCount(int count){
        int weakup = 0;
        while(isUpdateRunning() ){//toDo make a methode
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e2) {
                e2.printStackTrace();
                if(count > piecesClient){
                    piecesClient = count;
                }
                return;
            }
            weakup ++;
            /*
             * we don't want stay forever
             */
            if(weakup >= 10)break;// one sec for a update should be more than enough toDo: log warn, something goes wrong
        }
        if(count > piecesClient){
            piecesClient = count;
        }
    }
    
    /*
     *
     * ping by TrackerClient.class
     * client is seen in trackerlist
     */
    public void pingByTrackerList(){
        pingsByTrackerClient++;
        last_seen_on_tracker = System.currentTimeMillis();
    }
    
    public int getTrackerPings(){
        return pingsByTrackerClient;
    }
    
    
    // methode need not to be syncronized, we do nothing important here with the objects! have not to be thread safe.
    public Client getClientByPeer(Peer cur){
        boolean test = true;//dummy
        Client client;
        if(cur != null)
            if(cur.getPeerID() != null)
                if(cur.getPeerID().getID() !=null)// need for compare/equals
                    if(cur.getMetaInfo() !=null)// need for compare/equals
                        if(cur.getMetaInfo().getInfoHash() != null){
            //if(cur.getPeerID().getAddress() !=null){// need for compare/equals
            
            synchronized(instance().clientsXL) {
                client = new Client(test, Base64.encode(cur.getPeerID().getID()),Base64.encode(cur.getMetaInfo().getInfoHash()));//NEVER,NEVER,NEVER return this Object! just compare it!
                //client = new Client(test, cur);//NEVER,NEVER,NEVER return this Object! just compare it!
                if(instance().clientsXL.contains(client)){
                    int index = instance().clientsXL.indexOf(client);
                    client = null;
                    return (Client)instance().clientsXL.get(index);
                }
            }
                        }
        client = null;
        return null;
    }
    
    // methode need not to be syncronized, we do nothing important here with the objects! have not to be thread safe.
    public Client getClientByHash(String id_hash, String metainfo_hash){
        boolean test = true;//dummy
        Client client;
        if(id_hash != null)
            if(metainfo_hash != null){
            //if(cur.getPeerID().getAddress() !=null){// need for compare/equals
            
            synchronized(instance().clientsXL) {
                client = new Client(test, id_hash, metainfo_hash);//NEVER,NEVER,NEVER return this Object! just compare it!
                //client = new Client(test, cur);//NEVER,NEVER,NEVER return this Object! just compare it!
                if(instance().clientsXL.contains(client)){
                    int index = instance().clientsXL.indexOf(client);
                    client = null;
                    return (Client)instance().clientsXL.get(index);
                }
            }
            }
        client = null;
        return null;
    }
    
    public byte[] getBitmap(){
        if(peer != null)
            return peer.getBitfield();
        return null;
    }
    public float getCompletedPercent(){
        float pct = (float) (100.0 * (float) piecesClient / metainfo.getPieces());
        
        if(pct < 0)pct = 0;// last piece problem
        return pct;
    }
    
    public long getUploadRate() {
        return uploadrate;
    }
    
    public long getDownloadRate() {
        return downloadrate;
    }
    
    public long getDownloadTotal() {
        /*
         * toDo: isConnected() could be the wrong question
         * make a isSocketClosed() methode to be sure to get the right values
         * fixed; take out comment in time
         * we do not update anymore howlong a peer is != null
         */
        if(!hasPeer() ){// print out of double session amount after peer == null / closed socked:  fixed
            return old_download_total;
        }
        return download_total + old_download_total;
    }
    
    public long getUploadTotal() {
        /*
         * toDo: isConnected() could be the wrong question
         * make a isSocketClosed() methode to be sure to get the right values
         * fixed; take out comment in time
         * we do not update anymore howlong a peer is != null
         */
        if(!hasPeer() ){// print out of double session amount after peer == null / closed socked:  fixed
            return old_upload_total;
        }
        return upload_total + old_upload_total;
    }
    
    public long getUploadTotalFromOther() {
        //methode differs  +- size of last piece
        long value = 0L;
        int pieceLength = metainfo.getPieceLength(0);
        if(piecesClientFirstSeen > -1 && piecesClientFirstSeen < piecesClient){//dataloss?! if piecesClient is < piecesClientFirstSeen
            value = (long)( (long) piecesClient - (long) piecesClientFirstSeen) * (long) pieceLength  ;
            value -= upload_total + old_upload_total;
            if(value < 0L)value = 0L; // dataloss strikes again! //
        }
        return value;
    }
    
    public long getDownloadTotalSession() {
        return download_total ;
    }
    
    public long getUploadTotalSession() {
        return upload_total ;
    }
    
    /*
     * since we got this methode i see a problem of reconnections
     * the peers etablishing a lot of reconnections that are not neccessary.
     * they are just short but it's a lot to do for the tunnels.
     * most unneccessaries are the seeder/seeder connections.
     * half is started by us and half from each client.
     * the sum douples for each tracker each client is connected with
     * lets see how we can use this client.class to avoid of that.
     * it's a good step to increase the performence.
     * let us begin to sort out peers before we start a request.
     * a good point for a first step is peers.add(new Peer(peerID, my_id, metainfo));
     */
    public int getReconnections() {
        return reconnected;
    }
    
    public String getClientName(){
        String value="null";
        value = getClientIDString().substring(5, 9);
        return value;
    }
    
    //getClientSoft
    public String getClientSoft(){
        String value = getClientDescription( clientID );
        return value;
    }
    
    public String getClientAddress(){
        String value = destination.toBase64();
        return value;
    }
    
    public String getClientTorrentName(){
        String value = metainfo.getName();
        return value;
    }
    
    // last_seen_on_tracker
    //REM: if we change this to deliver a LONG than to not forget to substracting System.currentTimeMillis()!
    public String getClientLastSeenByTrackerRequest(){
        if(!(last_seen_on_tracker > 0L))
            return "Never";
        return getFormatTimeStemp(last_seen_on_tracker);
    }
    
    //overall_connected_time +- thread.sleep(?)'s in run
    // all time connections of this i2p session
    //REM: if we change this to deliver a LONG than to not forget to substracting System.currentTimeMillis()!
    public String getClientConnectionTime(){
        long value = 0L;
        if(current_connection_start > 1000L){
            value = current_connection_start;
        }else
            value = System.currentTimeMillis();
        value -= previous_connected_time;
        return getFormatTimeStemp(value);
    }
    
    //current_connection_time +- thread.sleep(?) in run
    /* How long we are connected over current peer
     * since current peer.isConnected() == true
     */
    //REM: if we change this to deliver a LONG than to not forget to substracting System.currentTimeMillis()!
    public String getClientConnectedSince(){
        long value = 0L;
        if(current_connection_start > 1000L){
            value = current_connection_start;
        }else
            value = System.currentTimeMillis();
        return getFormatTimeStemp(value);
    }
    
    public String getLastSeen(){
        if(!(stoppedtime > 0L))
            return "Never";
        return getFormatTimeStemp(stoppedtime);
    }
    
    public String getClientKnownSince(){
        return getFormatTimeStemp(starttime);
    }
    
    public String getClientLastTry(){
        return getFormatTimeStemp(connectedtime);
    }
    
    private String getFormatTimeStemp(long value){
        long hours = ((System.currentTimeMillis() - value) / ((1000) * 60 * 60));
        long minutes = (((System.currentTimeMillis() - value)  - (hours * ((1000) * 60 * 60))) / ((1000) * 60 ));
        long seconds = (((System.currentTimeMillis() - value) - (hours * ((1000) * 60 * 60) + minutes * ((1000) * 60 ))) / ((1000) )) ;
        
        if(seconds >= 60){
            seconds -= 60;
            minutes++;
        }
        if(minutes >= 60){
            minutes -= 60;
            hours++;
        }
        
        StringBuffer timeStemp = new StringBuffer();
        
        if(hours < 10)
            timeStemp.append("0");
        timeStemp.append("" + hours);
        timeStemp.append(":");
        if(minutes < 10)
            timeStemp.append("0");
        timeStemp.append("" + minutes);
        timeStemp.append(":");
        if(seconds < 10)
            timeStemp.append("0");
        timeStemp.append("" + seconds);
        
        return timeStemp.toString();
    }
    /**
     * Returns the String "id@address" where id is the base64 encoded id
     * and address is the base64 dest (was the base64 hash of the dest) which
     * should match what the bytemonsoon tracker reports on its web pages.
     */
    public String getClientIDString() {
        int nonZero = 0;
        for (int i = 0; i < clientID.length; i++) {
            if (clientID[i] != 0) {
                nonZero = i;
                break;
            }
        }
        return Base64.encode(clientID, nonZero, clientID.length-nonZero).substring(0,4) + "@" + destination.toBase64().substring(0,6);
    }
    
    /*
     *
     *
     */
    public synchronized void run() {
        assert(t == Thread.currentThread());
        while(!stop) {
            sleep(4000);
            if(clientsXL.size() > 0 ){
                // do something
                updateIsRunning = true;
                update();
                updateIsRunning = false;
            }
        }
        
    }
    
    private synchronized void sleep(int value){
        try {
            //Random randB = new Random(System.currentTimeMillis());//fwd save CPU power
            //Thread.sleep(value + (Math.abs(randB.nextInt()) % 4000));
            Thread.sleep(value + instance().clientsXL.size()*50);//sleep 0,5 sec more foreach 10 clients
        } catch (InterruptedException e2) {
            e2.printStackTrace();
        }
    }
    
    /*
     * toDo recheck if we are in list clientsXL else add this to list
     */
    public synchronized void start() {
        stop = false;
        assert(t == null);
        t = new Thread(this, "Client Manager");
        t.setDaemon(true);
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();
    }
    
    /*
     * toDo recheck if we are in list clientsXL and remove this from list
     * rem: toDo a removedclientslist to restart them?!
     */
    public synchronized void halt() {
        //toDo implement a user setting or set RuntimeException to false
        if (false) throw new RuntimeException("Client Manager forced to stop! ( " + this.getClientIDString() + " )");
        stop = true;
        
        Thread thread = t;
        if (thread != null)
            thread.interrupt();
    }
    
    /**
     * Two Clients are equal when they have the same id, address and torrent.
     *
     * this didn't handle the case of two or more clients (eg. two or more Azureus)
     * on same Address with same torrent (yes! Makes no sence but this is possible.)
     * they would be treated as one client
     * the diff you might readout with different count and place of pieces in bitmap
     * or in case of snark clients in the variance of the random 20 bytes when initiate a new destination
     */
    public boolean equals(Object o) {
        Client p = (Client)o;
        //boolean isID = idencode(clientID).equals(p.idencode(clientID));
        // boolean isAddress = destination.calculateHash().toBase64().equals(p.destination.calculateHash().toBase64());
        //boolean isTorrent = metainfo.getInfoHash().equals(p.metainfo.getInfoHash());
        boolean isID = clientIDhash.equals(p.clientIDhash);
        boolean isTorrent = metainfohash.equals(p.metainfohash);
        if(isID /*&& isAddress*/ && isTorrent){
            return true;
        }else{
            return false;
        }
    }
    /**
     * Compares the clients.
     */
    public int compareTo(Object o) {
        Client p = (Client)o;
        //boolean isID = idencode(clientID).equals(p.idencode(clientID));
        // boolean isAddress = destination.calculateHash().toBase64().equals(p.destination.calculateHash().toBase64());
        //boolean isTorrent = metainfo.getInfoHash().equals(p.metainfo.getInfoHash());
        boolean isID = clientIDhash.equals(p.clientIDhash);
        boolean isTorrent = metainfohash.equals(p.metainfohash);
        if(isID /*&& isAddress*/ && isTorrent){
            return 0;
        }else{
            return 1;
        }
    }
    
    private byte[] getClientID(){
        return clientID;
    }
    
    public String getClientIDHash(){
        return clientIDhash;
    }
    
    public String getClientMetaInfoHash(){
        return this.metainfohash;
    }
    
    private byte[] getOurID(){
        return clientID;
    }
    
    private MetaInfo getMetaInfo(){
        return metainfo;
    }
    
    /**
     * Returns Destination
     */
    public Destination getDestination() {
        return destination;
    }
    /**
     * Returns socket (for debug printing)
     */
    public String getSocket() {
        return sock.toString();
    }
    
    /**
     * Returns socket
     */
    public I2PSocket getI2PSocket() {
        return sock;
    }
    /**
     * Encode an id as a hex encoded string and remove leading zeros.
     */
    public String idencode(byte[] bs) {
        boolean leading_zeros = true;
        
        StringBuffer sb = new StringBuffer(bs.length*2);
        for (int i = 0; i < bs.length; i++) {
            int c = bs[i] & 0xFF;
            if (leading_zeros && c == 0)
                continue;
            else
                leading_zeros = false;
            
            if (c < 16)
                sb.append('0');
            sb.append(Integer.toHexString(c));
        }
        
        return sb.toString();
    }
    
    /**
     * Get a client description (name and version) from the given peerID byte array.
     * @param peer_id peerID sent in handshake
     * @return description
     */
    public String getClientDescription( byte[] peer_id ) {
        return BTPeerIDByteDecoder.decode( peer_id );
    }
    
    /*
     *
     */
    public List sortByName(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientName().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
      /*
       *
       */
    public List sortByBTClient(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientSoft().toLowerCase()+((Client)(list.get(ai))).getClientName().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByTorrent(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientTorrentName().toLowerCase()+((Client)(list.get(ai))).getClientName().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
     /*
      *
      */
    public List sortByConnectionTime(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientConnectionTime().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByKnownSince(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientKnownSince().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByLastSeen(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getLastSeen().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
      /*
       *
       */
    public List sortByConnectedSince(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(((Client)(list.get(ai))).getClientConnectedSince().toLowerCase()+ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
     /*
      *
      */
    public List sortByUploadTotal(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getUploadTotal() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
      /*
       * the UFO sort ;)
       */
    public List sortByUploadTotalFromOther(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getUploadTotalFromOther() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
     /*
      *
      */
    public List sortByDownloadTotal(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getDownloadTotal() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByUploadRate(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getUploadRate() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByDownloadRate(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getDownloadRate() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
     /*
      *
      */
    public List sortByReconnections(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put(getSizeFormatedLongString(((Client)(list.get(ai))).getReconnections() , ai),list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
     /*
      *
      */
    public List sortByBanned(boolean descending) {
        
        Map peersTmp = new HashMap();
        List list = instance().clientsXL;
        for(int ai = 0;ai < list.size();ai++){
            peersTmp.put("" + ((Client)(list.get(ai))).isBanned() + "" + ai,list.get(ai));// + ai because else we lost clients with same name =name1;name5;name34 ..
        }
        
        Set peersSet =  peersTmp.keySet();
        TreeSet peerTS = new TreeSet(peersSet); // sorts it alphabetically by Hash
        ArrayList rv = new ArrayList(peerTS.size());
        Iterator pts;
        if(descending)
            pts = peerTS.descendingIterator();
        else
            pts = peerTS.iterator();
        
        for (Iterator iter = pts; iter.hasNext(); ) {
            String name = (String )iter.next();
            rv.add((Client)(peersTmp.get(name)));
        }
        return rv;
    }
    
    private String getSizeFormatedLongString(long value, int loop){
        // needed for sorting integers and long as string in a hashmap
        // (hashmap lost/overwrite none unique elements) that took a while to figure it out!
        // else we loss the right range and without loop some values that are same numbers
        // bit, byte, kb, mb, gb, 1, 10, 100 ... >
         /* 000001
          * 000010
          * 000100
          * 001000
          * ...
          **/
        String var = "" + value;
        String varLoop = "" + loop; // the loop caring that we get in all cases a unique value
        StringBuffer formatedstring = new StringBuffer();
        StringBuffer formatedloop = new StringBuffer();
        int max = 33 ;// how long could it be? 33 should be enough for all cases.
        
        for(int i=0; i < (4 - varLoop.length() ); i++)// place for 9.999 clients ;)
            formatedloop.append("0");
        
        formatedloop.append(varLoop);
        
        for(int i=0; i < (max - var.length() ); i++)
            formatedstring.append("0");
        
        formatedstring.append(var);
        formatedstring.append(formatedloop);
        
        return formatedstring.toString();
    }
    
    /**
     * Primarily used for serializing this key (not yet)
     */
    public String toString() {
        String ENDLINE="\n";
        String SEMI="; ";
        
        StringBuffer output = new StringBuffer("");
        if(clientID != null)
            output.append(getClientDescription( clientID ));
        else output.append("null");
        output.append(SEMI);
        
        if(clientID != null)
            output.append(getClientName());
        else output.append("null");
        output.append(SEMI);
        
        /*
        if(clientID != null)
            output.append(idencode(clientID));
        else output.append("null");
        output.append(SEMI);
         */
        
        if(sock != null)
            if(destination != null)
                output.append("" + destination.calculateHash().toBase64());
            else output.append("null");
        else output.append("null");
        output.append(SEMI);
        
//        output.append(idencode(ourID));
//        output.append(SEMI);
        
        if(metainfo != null)
            output.append(metainfo.getName());
        else output.append("null");
        output.append(SEMI);
        
        output.append("Known since: " + ((System.currentTimeMillis() - starttime)/ (1000)) );
        output.append(SEMI);
        
        output.append("last connection try: " + ((System.currentTimeMillis() - connectedtime)/ (1000)) );
        output.append(SEMI);
        
        output.append(ENDLINE);
        return output.toString();
    }
    
    public byte[] getImageBarByte(){
        /* byte[] bufImageByte = null;
        synchronized(instance().clientsXL) {
                if(instance().clientsXL.contains(this)){
                    bufImageByte =((Client) instance().clientsXL.get(instance().clientsXL.indexOf(this))).bufferedImage;
                }
            }*/
        if(bufferedImageByte == null)return null;
        
        return bufferedImageByte.clone();
    }
    private BufferedImage getImageBarNull(int width, int height){
        
        BufferedImage bufImage= new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        
        Graphics2D g2d = bufImage.createGraphics();
        if(isCompleted()){
            g2d.setColor(Color.blue);
        }else
            g2d.setColor(Color.white);
        
        g2d.fill(new Rectangle2D.Float(0, 0, bufImage.getWidth(), bufImage.getHeight()));
        g2d.dispose();
        //g2.setBackground(Color.blue);
        return bufImage;// Image Toolkit.getDefaultToolkit().createImage(bufImage.getSource());
    }
    
    private BufferedImage getImageBar(Peer _peer, int width, int height){
        //GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        //GraphicsDevice gs = ge.getDefaultScreenDevice();
        //GraphicsConfiguration gc = gs.getDefaultConfiguration();
        
        //BufferedImage bufferedImage = gc.createCompatibleImage(width, height, Transparency.BITMASK);
        //BufferedImage bufferedImage = gc.createCompatibleImage(width, height, Transparency.OPAQUE);
        // Create a buffered image that supports transparency
        
        //byte[] pixels = new byte[w * h];
        // Generate the pixel data;
        //byte[] pixels = generatePixels(width, height, loc);
        // Create a data buffer using the byte buffer of pixel data.
        // The pixel data is not copied; the data buffer uses the byte buffer array.
        // DataBuffer dbuf = new DataBufferByte(pixels, width*height, 0);
        // The number of banks should be 1
        //    int numBanks = dbuf.getNumBanks(); // 1
        // Prepare a sample model that specifies a storage 4-bits of
        // pixel datavd in an 8-bit data element
        //int bitMasks[] = new int[]{(byte)0xf};
        //SampleModel sampleModel = new SinglePixelPackedSampleModel(
        //   DataBuffer.TYPE_BYTE, width, height, bitMasks);
        
        // Create a raster using the sample model and data buffer
        //WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null);
        
        // Combine the color model and raster into a buffered image
        // image = new BufferedImage(colorModel, raster, false, null);//new java.util.Hashtable());
        if(_peer == null || isCompleted())return getImageBarNull(width , height);
        byte [] b = _peer.getBitfield();
        if(b == null)return getImageBarNull(width , height);
        
        byte[] pixels = b;
        DataBuffer dbuf = new DataBufferByte(pixels, pixels.length*1, 0);
        int numBanks = dbuf.getNumBanks(); // 1
        int bitMasks[] = new int[]{(byte)0xf};//0xf
        SampleModel sampleModel = new SinglePixelPackedSampleModel(
                DataBuffer.TYPE_BYTE, pixels.length*1, 1, bitMasks);
        WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null);
        
        BufferedImage bufImage = new BufferedImage(colormodel, raster, false, null);//new java.util.Hashtable());
        
        return bufImage;// Image Toolkit.getDefaultToolkit().createImage(bufferedImage.getSource());
    }
    
    private static final ColorModel generateColorModel() {
        // Generate 16-color model
        byte[] r = new byte[9];
        byte[] g = new byte[9];
        byte[] b = new byte[9];
        
        b[8] =  (byte)180;
        b[7] =  (byte)190;
        b[6] =  (byte)200;
        b[5] =  (byte)210;
        b[4] =  (byte)220;
        b[3] =  (byte)230;
        b[2] =  (byte)240;
        b[1] =  (byte)250;
        b[0] =  (byte)254;
        
        g[8] =  (byte)110;
        g[7] =  (byte)120;
        g[6] =  (byte)140;
        g[5] =  (byte)160;
        g[4] =  (byte)180;
        g[3] =  (byte)200;
        g[2] =  (byte)220;
        g[1] =  (byte)240;
        g[0] =  (byte)254;
        
        r[8] =  (byte)50;
        r[7] =  (byte)60;
        r[6] =  (byte)90;
        r[5] =  (byte)130;
        r[4] =  (byte)160;
        r[3] =  (byte)180;
        r[2] =  (byte)210;
        r[1] =  (byte)240;
        r[0] =  (byte)254;
        
        
/*        r[8] =  (byte)128; g[8] =  (byte)128; b[8] =  (byte)0;
        r[9] =  (byte)144; g[9] =  (byte)144; b[9] =  (byte)0;
        r[10] = (byte)160; g[10] = (byte)160; b[10] = (byte)0;
        r[11] = (byte)176; g[11] = (byte)176; b[11] = (byte)0;
        r[12] = (byte)192; g[12] = (byte)192; b[12] = (byte)0;
        r[13] = (byte)208; g[13] = (byte)208; b[13] = (byte)0;
        r[14] = (byte)226; g[14] = (byte)226; b[14] = (byte)0;
        r[15] = (byte)242; g[15] = (byte)242; b[15] = (byte)0;
 */
        return new IndexColorModel(1, 9, r, g, b);
    }
}
