#include "kadNodesListView.h"
#include "kademlia/routing/Contact.h"

#include "common/Format.h"
#include "Logger.h"

#include <wx/colour.h>
#include <wx/thread.h>
#include <algorithm>

typedef enum {TYPE, CONTACT_KAD_VERSION, UDP_READABLE, TCP_READABLE, LAST_SEEN, KAD_ID_STRING, KAD_ID, DISTANCE, UDP_ID } columns_enum ;

static columns_enum __sort_order = TYPE ;
static bool __reverse = false ;
//static wxMutex contacts_mutex ;

struct Compare {
        bool operator() ( const Kademlia::CContact & lhs, const Kademlia::CContact & rhs ) const {
                /*AddDebugLogLineN(logKadPrefs,
                		   CFormat ( wxT ( "sort --> Compare enter %x %x" ) ) % (int) &lhs % (int) &rhs  );
                */
                bool inf = true ;
                try {
                        switch ( __sort_order ) {
                        case TYPE         :
                                inf = __reverse ? ( rhs.GetType() < lhs.GetType() )
                                      : ( lhs.GetType() < rhs.GetType() );
                                break;
                        case CONTACT_KAD_VERSION :
                                inf = __reverse ? ( rhs.GetVersion() < lhs.GetVersion() )
                                      : ( lhs.GetVersion() < rhs.GetVersion() );
                                break;
                        case UDP_READABLE :
                                inf = __reverse ? ( rhs.GetUDPDest().humanReadable() < lhs.GetUDPDest().humanReadable() )
                                      : ( lhs.GetUDPDest().humanReadable() < rhs.GetUDPDest().humanReadable() );
                                break;
                        case TCP_READABLE :
                                inf = __reverse ? ( rhs.GetTCPDest().humanReadable() < lhs.GetTCPDest().humanReadable() )
                                      : ( lhs.GetTCPDest().humanReadable() < rhs.GetTCPDest().humanReadable() );
                                break;
                        case LAST_SEEN    :
                                inf = __reverse ? ( rhs.GetLastSeen() < lhs.GetLastSeen() )
                                      : ( lhs.GetLastSeen() < rhs.GetLastSeen() );
                                break;
                        case KAD_ID_STRING:
                        case KAD_ID       :
                                inf = __reverse ? ( rhs.GetClientID() < lhs.GetClientID() )
                                      : ( lhs.GetClientID() < rhs.GetClientID() );
                                break;
                        case DISTANCE     :
                                inf = __reverse ? ( rhs.GetDistance() < lhs.GetDistance() )
                                      : ( lhs.GetDistance() < rhs.GetDistance() );
                                break;
                        case UDP_ID       :
                                inf = __reverse ? ( rhs.GetUDPDest().toString() < lhs.GetUDPDest().toString() ) :
                                      ( lhs.GetUDPDest().toString() < rhs.GetUDPDest().toString() );
                                break;
                        }

                        /*AddDebugLogLineN(logKadPrefs,
                        		   CFormat ( wxT ( "sort --> Compare exit" ) )  );
                        */
                } catch (...) {
                        AddDebugLogLineC(logKadPrefs,
                                         CFormat ( wxT ( "sort --> Compare EXCEPTION" ) )  );
                }
                return inf;
        }
};


KadNodesListView::KadNodesListView ( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name )
        :
        wxListView ( parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL, validator, name )
{
        wxListItem itemCol;
        itemCol.SetText ( _ ( "node type" ) );
        itemCol.SetAlign ( wxLIST_FORMAT_CENTRE );
        InsertColumn ( 0, itemCol );

        itemCol.SetText ( _ ( "version" ) );
        itemCol.SetAlign ( wxLIST_FORMAT_CENTRE );
        InsertColumn ( 1, itemCol );

        itemCol.SetText ( _ ( "  id  " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_CENTRE );
        InsertColumn ( 2, itemCol );

        itemCol.SetText ( _ ( "stream id" ) );
        itemCol.SetAlign ( wxLIST_FORMAT_CENTRE );
        InsertColumn ( 3, itemCol );

        itemCol.SetText ( _ ( "            last time seen            " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_CENTRE );
        InsertColumn ( 4, itemCol );

        itemCol.SetText ( _ ( " kad id " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_LEFT );
        InsertColumn ( 5, itemCol );

        itemCol.SetText ( _ ( " binary id " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_LEFT );
        InsertColumn ( 6, itemCol );

        itemCol.SetText ( _ ( " distance " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_LEFT );
        InsertColumn ( 7, itemCol );

        itemCol.SetText ( _ ( "   key   " ) );
        itemCol.SetAlign ( wxLIST_FORMAT_LEFT );
        InsertColumn ( 8, itemCol );

        SetTextColour ( *wxBLUE );

        for ( int i=0; i<9; i++ )
                SetColumnWidth ( i, wxLIST_AUTOSIZE_USEHEADER );

        SetItemCount ( 0 );
        Show();
}



void KadNodesListView::AddNode( Kademlia::CContact * contact )
{
        Kademlia::CContact node = * contact ;
        delete contact ;

        CUInt128 kad_id = node . GetClientID() ;

        AddDebugLogLineN(logKadPrefs, CFormat ( wxT ( "KadNodesListView::AddNode" ) ) );

        // is this node being added or updated ?
        bool updated = _contacts_map.count ( kad_id );

        if ( updated ) {
                int i = _contacts_map[ kad_id ] ;
                AddDebugLogLineN(logKadPrefs,
                                 CFormat ( wxT ( "Updating contact n°%ld" ) ) % i );
                _contacts_list[ i ] = node ;
        } else {
                AddDebugLogLineN(logKadPrefs,
                                 CFormat ( wxT ( "Adding unknown contact n°%ld" ) ) % _contacts_list.size() );
                _contacts_map[ kad_id ] = _contacts_list . size() ;
                _contacts_list . push_back ( node ) ;
        }
        SortItems();
}

void KadNodesListView::DeleteNode( Kademlia::CContact * contact )
{
        Kademlia::CContact node = * contact ;
        delete contact ;

        CUInt128 kad_id = node . GetClientID() ;

        wxASSERT ( _contacts_map.count ( kad_id ) );
        unsigned int i = _contacts_map[ kad_id ];
        _contacts_list . erase ( _contacts_list.begin() + i ) ;
        _contacts_map  . erase ( kad_id );
        while ( i<_contacts_list.size() ) {
                _contacts_map[ _contacts_list[i].GetClientID() ]-- ;
                i++ ;
        }
        SortItems();
}

void KadNodesListView::SortItems()
{
        //wxMutexLocker lock( contacts_mutex ) ;

        std::vector<Kademlia::CContact> old_list = _sorted_contacts_list ;
        _sorted_contacts_list = _contacts_list ;

        std::sort ( _sorted_contacts_list.begin(), _sorted_contacts_list.end(), Compare() );

        if (_sorted_contacts_list.size() != old_list.size()) {
                SetItemCount ( _sorted_contacts_list.size() );
        }
        for (unsigned int i=0 ; i<_sorted_contacts_list.size(); i++) {
                bool refresh_it = false ;
                if (i>=old_list.size()) {
                        refresh_it = true ;
                } else {
                        Kademlia::CContact current = _sorted_contacts_list.at(i);
                        Kademlia::CContact old     = old_list.at(i);
                        if ( ( current.GetType() != old.GetType() )
                                        || ( current.GetVersion() != old.GetVersion() )
                                        || ( current.GetUDPDest() != old.GetUDPDest() )
                                        || ( current.GetTCPDest() != old.GetTCPDest() )
                                        || ( current.GetLastSeen() != old.GetLastSeen() )
                                        || ( current.GetClientID() != old.GetClientID() )
                                        || ( current.GetDistance() != old.GetDistance() ) ) {
                                refresh_it = true;
                        }
                }
                if (refresh_it) {
                        RefreshItem( i );
                }
        }
}



wxString KadNodesListView::OnGetItemText ( long line, long col ) const
{
        //wxMutexLocker lock( contacts_mutex ) ;
        unsigned int item = (unsigned int) line, column = (unsigned int) col ;
        if (item >= _sorted_contacts_list.size()) return wxEmptyString ;

        Kademlia::CContact node = _sorted_contacts_list.at ( item ) ;

        switch ( column ) {
        case TYPE         :  return wxString() << node.GetType();
        case CONTACT_KAD_VERSION :  return wxString() << node.GetVersion();
        case UDP_READABLE :  return node.GetUDPDest().humanReadable() ;
        case TCP_READABLE :  return node.GetTCPDest().humanReadable() ;
        case LAST_SEEN    : {
                time_t lastSeen = node.GetLastSeen() ;
                return ( lastSeen==0 ? wxString() << _ ( "never" ) : wxDateTime ( lastSeen ).Format ( wxT ( "%c" ) ) ) ;
        }
        case KAD_ID_STRING:  return node.GetClientIDString() ;
        case KAD_ID       :  return node.GetClientID().ToBinaryString().Mid ( 0,12 ) ;
        case DISTANCE     :  return node.GetDistance().ToBinaryString().Mid ( 0,12 ) ;
        case UDP_ID       :  return node.GetUDPDest().toString() ;
        default           :  return ( wxString(wxT("BAD REQUEST")) );
        }
}
wxListItemAttr * KadNodesListView::OnGetItemAttr ( long line ) const
{
        //wxMutexLocker lock( contacts_mutex ) ;
        static wxListItemAttr attr ;
        unsigned int item = (unsigned int) line ;

        if (item >= _sorted_contacts_list.size()) return NULL ;
        Kademlia::CContact node = _sorted_contacts_list.at ( item ) ;

        // set the colour according to type
        wxColour colour ( 0,0,0 ) ;
        switch ( node.GetType() ) {
        case 0 : colour = wxColour ( 0,0,255 ) ; break;
        case 1 : colour = wxColour ( 85,0,170 ); break;
        case 2 : colour = wxColour ( 170,0, 85 ) ; break;
        case 3 : colour = wxColour ( 255,0,  0 ) ; break;
        default: break;
        }
        attr.SetTextColour ( colour );
        return &attr ;
}

void KadNodesListView::OnColumnLClick ( wxListEvent & event )
{
        AddDebugLogLineN(logKadPrefs,
                         CFormat ( wxT ( "ColumnLClick enter °%d" ) ) % (int) event.GetColumn() );
        wxASSERT ( event.GetColumn() >= 0 && event.GetColumn() <= UDP_ID );
        if ( __sort_order == event.GetColumn() ) {
                __reverse = ! __reverse ;
        } else {
                __sort_order = ( columns_enum ) event.GetColumn() ;
        }
        {
                //wxMutexLocker lock( contacts_mutex ) ;
                AddDebugLogLineN(logKadPrefs,
                                 CFormat ( wxT ( "ColumnLClick sort °%d" ) ) % (int) event.GetColumn() );
                /*for (unsigned int i=0; i<_sorted_contacts_list.size(); i++)
                {
                	_sorted_contacts_list.at(i).GetDataMutex().Lock();
                }*/
                std::sort ( _sorted_contacts_list.begin(), _sorted_contacts_list.end(), Compare() );
                /*for (unsigned int i=0; i<_sorted_contacts_list.size(); i++)
                {
                	_sorted_contacts_list.at(i).GetDataMutex().Unlock();
                }*/
                AddDebugLogLineN(logKadPrefs,
                                 CFormat ( wxT ( "ColumnLClick refresh items °%d" ) ) % (int) event.GetColumn() );
                RefreshItems ( 0, _sorted_contacts_list.size()-1 ) ;
                AddDebugLogLineN(logKadPrefs,
                                 CFormat ( wxT ( "ColumnLClick exit °%d" ) ) % (int) event.GetColumn() );
        }
}


BEGIN_EVENT_TABLE ( KadNodesListView, wxListView )
        EVT_LIST_COL_CLICK ( -1, KadNodesListView::OnColumnLClick )
END_EVENT_TABLE()
