package basics.random;

import java.security.SecureRandom;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import basics.caching.ExpiringCache;
import basics.testing.Test;
import basics.time.Chrono;
import basics.utl.SysUtl;

/**
 * Create ID's based on current time and random alphanumeric signs.
 * The time are code to a base of 36. The first 6 signs of the ID thus contain
 * the creating time which can be decoded.
 * The default width is 11 signs (6 for time, 5 random signs).
 * The generated id's are cached (cache size is set to 100.000 entries) 
 * to avoid dublicate keys when generating thousands of id's in a short time.
 * The generation speed is about 40.000 keys per second (macbook dualcore). 
 * @author kst 
 */
public class TimeCodedID
{
public static void main(String [] aa)
{
   int i = Integer.parseInt(aa[0]);
   String u = TimeCodedID.create(i);
   System.out.println(u + " " + (u.length() == i ? "" : " LEN: " + u.length()));
}

public static synchronized String create()
{
   return create(DEFAULTWIDTH);
}

public static synchronized String create(int width)
{
   TimeCodedID u1 = new TimeCodedID(width);
   String t = u1.doCreate();
   while(cache.get(t) != null)
   {
      SysUtl.trace(t + " was in cache.");
      t = u1.doCreate();
   }
   cache.put(t, ""); //we only need the key
   return t;
}

private static final int    BASE         = 36;
private static final String base         = "0123456789abcdefghijklmnopqrstuvwxyz";
private static final int    STARTYEAR    = 2000 - 1900;
private static final int    MIN          = 6;
public static final int     DEFAULTWIDTH = 11;
public static String        SEPARATOR    = ".";
private int                 randSigns    = MIN;
public static int           CACHESIZE    = 100 * 1024;
static ExpiringCache        cache        = new ExpiringCache(CACHESIZE, CACHESIZE, 2500);

private TimeCodedID(int width)
{
   width = width < MIN ? MIN : width;
   this.randSigns = width - (6 + 1); // separator
}

private String doCreate()
{
   StringBuffer sb = new StringBuffer();
   String d = encodeDate();
   sb.append(d);
   if(randSigns > 0)
   {
      sb.append(SEPARATOR);
      getRandom(sb, randSigns);
   }
   return sb.toString();
}

@SuppressWarnings("deprecation")
private String encodeDate()
{
   StringBuffer sb = new StringBuffer();
   Date d = new Date();
   sb.append(ritoa(d.getYear() - STARTYEAR));
   sb.append(ritoa(d.getMonth() + 1));
   sb.append(ritoa(d.getDate()));
   int h = d.getHours();
   int m = d.getMinutes();
   int s = d.getSeconds();
   int x = h * 3600;
   x += m * 60;
   x += s;
   sb.append(ritoa(x));
   return sb.toString();
}

private void getRandom(StringBuffer sb, int width)
{
   // Random r = new Random();
   SecureRandom r = new SecureRandom();
   for(int i = 0; i < width; i++)
   {
      int x = r.nextInt(BASE);
      sb.append(base.charAt(x));
   }
}

private static synchronized String ritoa(int i)
{
   int x = i / BASE;
   int y = i % BASE;
   String ys = aBase(y);
   if(x > 0)
   {
      return ritoa(x) + ys;
   }
   return ys;
}

private static synchronized String aBase(int i)
{
   return base.substring(i, i + 1);
}

@SuppressWarnings("deprecation")
public static String decode(String ds, boolean cutBehindTime)
{
   Date d = new Date();
   int x = ds.indexOf(SEPARATOR);
   int i = atoi(ds.substring(0, 1));
   d.setYear(STARTYEAR + i);
   i = atoi(ds.substring(1, 2)) - 1;
   d.setMonth(i);
   d.setDate(atoi(ds.substring(2, 3)) - 1);
   if(x == -1)
   {
      i = atoi(ds.substring(3));
   }
   else
   {
      i = atoi(ds.substring(3, x));
   }
   int s = i % 60;
   i -= s;
   int h = (i / 3600);
   int m = (i - (h * 3600)) / 60;
   d.setHours(h);
   d.setMinutes(m);
   d.setSeconds(s);
   StringBuffer sb = new StringBuffer();
   sb.append(d.getYear() + 1900);
   sb.append("-");
   sb.append(d.getMonth() + 1);
   sb.append("-");
   sb.append(d.getDate());
   sb.append(" ");
   sb.append(h);
   sb.append(":");
   sb.append(m);
   sb.append(":");
   sb.append(s);
   Chrono c = new Chrono(sb.toString());
   sb = new StringBuffer();
   sb.append(c.toString());
   if(!cutBehindTime)
   {
      if(x > -1)
      {
         sb.append(SEPARATOR);
         sb.append(ds.substring(x + 1));
      }
   }
   return sb.toString();
}

private static int atoi(String a)
{
   int x = 0;
   int b = 1;
   for(int i = a.length() - 1; i >= 0; i--)
   {
      String c = a.substring(i, i + 1);
      int y = (iBase(c) * b);
      x += y;
      b = b == 1 ? BASE : b * BASE;
   }
   return x;
}

private static int iBase(String a)
{
   return base.indexOf(a);
}

public static void unittest()
{
   Set<String> buffer = new HashSet<String>();
   int max = 1000;
   for(int i = 0; i < max; i++)
   {
      String c = TimeCodedID.create();
      Test.assertFalse(buffer.contains(c), c + " was generated before!");
      buffer.add(c);
   }
}
}
