package net.md_5.bungee.tablist;

import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.HashSet;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.tab.TabAPI;
import net.md_5.bungee.api.tab.TabListAdapter;
import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem;

public class Custom extends TabListAdapter implements TabAPI
{

    private static final int ROWS = 20;
    private static final int COLUMNS = 3;
    private static final char[] FILLER = new char[]
    {
        '1', '2', '2', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };
    private static final int MAX_LEN = 16;
    /*========================================================================*/
    private final Collection<String> sentStuff = new HashSet<>();
    /*========================================================================*/
    private String[][] sent = new String[ ROWS ][ COLUMNS ];
    private String[][] slots = new String[ ROWS ][ COLUMNS ];

    @Override
    public synchronized String setSlot(int row, int column, String text)
    {
        return setSlot( row, column, text, true );
    }

    @Override
    public synchronized String setSlot(int row, int column, String text, boolean update)
    {
        Preconditions.checkArgument( row > 0 && row <= ROWS, "row out of range" );
        Preconditions.checkArgument( column > 0 && column <= COLUMNS, "column out of range" );

        if ( text != null )
        {
            Preconditions.checkArgument( text.length() <= MAX_LEN, "text must be <= %s chars", MAX_LEN );
            Preconditions.checkArgument( !ChatColor.stripColor( text ).isEmpty(), "Text cannot consist entirely of colour codes" );

            text = attempt( text );
            sentStuff.add( text );
        } else
        {
            sentStuff.remove( text );
        }

        slots[--row][--column] = text;
        if ( update )
        {
            update();
        }
        return text;
    }

    private String attempt(String s)
    {
        for ( char c : FILLER )
        {
            String attempt = s + Character.toString( ChatColor.COLOR_CHAR ) + c;
            if ( !sentStuff.contains( attempt ) )
            {
                return attempt;
            }
        }
        if ( s.length() <= MAX_LEN - 4 )
        {
            return attempt( s + Character.toString( ChatColor.COLOR_CHAR ) + FILLER[0] );
        }
        throw new IllegalArgumentException( "List already contains all variants of string" );
    }

    @Override
    public synchronized void update()
    {
        clear();
        for ( int i = 0; i < ROWS; i++ )
        {
            for ( int j = 0; j < COLUMNS; j++ )
            {
                String text = ( slots[i][j] != null ) ? slots[i][j] : new StringBuilder().append( base( i ) ).append( base( j ) ).toString();
                sent[i][j] = text;
                getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( text, true, (short) 0 ) );
            }
        }
    }

    @Override
    public synchronized void clear()
    {
        for ( int i = 0; i < ROWS; i++ )
        {
            for ( int j = 0; j < COLUMNS; j++ )
            {
                if ( sent[i][j] != null )
                {
                    String text = sent[i][j];
                    sent[i][j] = null;
                    getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( text, false, (short) 9999 ) );
                }
            }
        }
    }

    @Override
    public synchronized int getRows()
    {
        return ROWS;
    }

    @Override
    public synchronized int getColumns()
    {
        return COLUMNS;
    }

    @Override
    public synchronized int getSize()
    {
        return ROWS * COLUMNS;
    }

    @Override
    public boolean onListUpdate(String name, boolean online, int ping)
    {
        return false;
    }

    private static char[] base(int n)
    {
        String hex = Integer.toHexString( n + 1 );
        char[] alloc = new char[ hex.length() * 2 ];
        for ( int i = 0; i < alloc.length; i++ )
        {
            if ( i % 2 == 0 )
            {
                alloc[i] = ChatColor.COLOR_CHAR;
            } else
            {
                alloc[i] = hex.charAt( i / 2 );
            }
        }
        return alloc;
    }
}

@@ -526,16 +526,21 @@
 %0A       
+ '0',
  '1', '2
@@ -1006,16 +1006,64 @@
 LUMNS %5D;
+%0A    private int rowLim;%0A    private int colLim;
 %0A%0A    @O
@@ -1767,25 +1767,24 @@
 r codes%22 );%0A
-%0A
             
@@ -1833,32 +1833,175 @@
 uff.add( text );
+%0A%0A            if ( rowLim %3C row %7C%7C colLim %3C column )%0A            %7B%0A                rowLim = row;%0A                colLim = column;%0A            %7D
 %0A        %7D else%0A
@@ -2824,36 +2824,38 @@
  int i = 0; i %3C 
-ROWS
+rowLim
 ; i++ )%0A        
@@ -2881,39 +2881,38 @@
  int j = 0; j %3C 
-COLUMNS
+colLim
 ; j++ )%0A        
@@ -3315,20 +3315,22 @@
  0; i %3C 
-ROWS
+rowLim
 ; i++ )%0A
@@ -3372,23 +3372,22 @@
  0; j %3C 
-COLUMNS
+colLim
 ; j++ )%0A

Set a row limit to stop at, we should shrink this after a while. Meh api is good enough for me