// 
// Decompiled by Procyon v0.6.0
// 

package org.bson.types;

import java.security.SecureRandom;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
import org.bson.assertions.Assertions;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.Serializable;

public final class ObjectId implements Comparable<ObjectId>, Serializable
{
    private static final long serialVersionUID = 1L;
    private static final int OBJECT_ID_LENGTH = 12;
    private static final int LOW_ORDER_THREE_BYTES = 16777215;
    private static final int RANDOM_VALUE1;
    private static final short RANDOM_VALUE2;
    private static final AtomicInteger NEXT_COUNTER;
    private static final char[] HEX_CHARS;
    private final int timestamp;
    private final int counter;
    private final int randomValue1;
    private final short randomValue2;
    
    public static ObjectId get() {
        return new ObjectId();
    }
    
    public static ObjectId getSmallestWithDate(final Date date) {
        return new ObjectId(dateToTimestampSeconds(date), 0, (short)0, 0, false);
    }
    
    public static boolean isValid(final String hexString) {
        if (hexString == null) {
            throw new IllegalArgumentException();
        }
        final int len = hexString.length();
        if (len != 24) {
            return false;
        }
        for (int i = 0; i < len; ++i) {
            final char c = hexString.charAt(i);
            if (c < '0' || c > '9') {
                if (c < 'a' || c > 'f') {
                    if (c < 'A' || c > 'F') {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    public ObjectId() {
        this(new Date());
    }
    
    public ObjectId(final Date date) {
        this(dateToTimestampSeconds(date), ObjectId.NEXT_COUNTER.getAndIncrement() & 0xFFFFFF, false);
    }
    
    public ObjectId(final Date date, final int counter) {
        this(dateToTimestampSeconds(date), counter, true);
    }
    
    public ObjectId(final int timestamp, final int counter) {
        this(timestamp, counter, true);
    }
    
    private ObjectId(final int timestamp, final int counter, final boolean checkCounter) {
        this(timestamp, ObjectId.RANDOM_VALUE1, ObjectId.RANDOM_VALUE2, counter, checkCounter);
    }
    
    private ObjectId(final int timestamp, final int randomValue1, final short randomValue2, final int counter, final boolean checkCounter) {
        if ((randomValue1 & 0xFF000000) != 0x0) {
            throw new IllegalArgumentException("The machine identifier must be between 0 and 16777215 (it must fit in three bytes).");
        }
        if (checkCounter && (counter & 0xFF000000) != 0x0) {
            throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes).");
        }
        this.timestamp = timestamp;
        this.counter = (counter & 0xFFFFFF);
        this.randomValue1 = randomValue1;
        this.randomValue2 = randomValue2;
    }
    
    public ObjectId(final String hexString) {
        this(parseHexString(hexString));
    }
    
    public ObjectId(final byte[] bytes) {
        this(ByteBuffer.wrap(Assertions.isTrueArgument("bytes has length of 12", bytes, Assertions.notNull("bytes", bytes).length == 12)));
    }
    
    public ObjectId(final ByteBuffer buffer) {
        Assertions.notNull("buffer", buffer);
        Assertions.isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= 12);
        this.timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get());
        this.randomValue1 = makeInt((byte)0, buffer.get(), buffer.get(), buffer.get());
        this.randomValue2 = makeShort(buffer.get(), buffer.get());
        this.counter = makeInt((byte)0, buffer.get(), buffer.get(), buffer.get());
    }
    
    public byte[] toByteArray() {
        final ByteBuffer buffer = ByteBuffer.allocate(12);
        this.putToByteBuffer(buffer);
        return buffer.array();
    }
    
    public void putToByteBuffer(final ByteBuffer buffer) {
        Assertions.notNull("buffer", buffer);
        Assertions.isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= 12);
        buffer.put(int3(this.timestamp));
        buffer.put(int2(this.timestamp));
        buffer.put(int1(this.timestamp));
        buffer.put(int0(this.timestamp));
        buffer.put(int2(this.randomValue1));
        buffer.put(int1(this.randomValue1));
        buffer.put(int0(this.randomValue1));
        buffer.put(short1(this.randomValue2));
        buffer.put(short0(this.randomValue2));
        buffer.put(int2(this.counter));
        buffer.put(int1(this.counter));
        buffer.put(int0(this.counter));
    }
    
    public int getTimestamp() {
        return this.timestamp;
    }
    
    public Date getDate() {
        return new Date(((long)this.timestamp & 0xFFFFFFFFL) * 1000L);
    }
    
    public String toHexString() {
        final char[] chars = new char[24];
        int i = 0;
        for (final byte b : this.toByteArray()) {
            chars[i++] = ObjectId.HEX_CHARS[b >> 4 & 0xF];
            chars[i++] = ObjectId.HEX_CHARS[b & 0xF];
        }
        return new String(chars);
    }
    
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        final ObjectId objectId = (ObjectId)o;
        return this.counter == objectId.counter && this.timestamp == objectId.timestamp && this.randomValue1 == objectId.randomValue1 && this.randomValue2 == objectId.randomValue2;
    }
    
    @Override
    public int hashCode() {
        int result = this.timestamp;
        result = 31 * result + this.counter;
        result = 31 * result + this.randomValue1;
        result = 31 * result + this.randomValue2;
        return result;
    }
    
    @Override
    public int compareTo(final ObjectId other) {
        if (other == null) {
            throw new NullPointerException();
        }
        final byte[] byteArray = this.toByteArray();
        final byte[] otherByteArray = other.toByteArray();
        for (int i = 0; i < 12; ++i) {
            if (byteArray[i] != otherByteArray[i]) {
                return ((byteArray[i] & 0xFF) < (otherByteArray[i] & 0xFF)) ? -1 : 1;
            }
        }
        return 0;
    }
    
    @Override
    public String toString() {
        return this.toHexString();
    }
    
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
    
    private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }
    
    private static byte[] parseHexString(final String s) {
        if (!isValid(s)) {
            throw new IllegalArgumentException("invalid hexadecimal representation of an ObjectId: [" + s + "]");
        }
        final byte[] b = new byte[12];
        for (int i = 0; i < b.length; ++i) {
            b[i] = (byte)Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
        }
        return b;
    }
    
    private static int dateToTimestampSeconds(final Date time) {
        return (int)(time.getTime() / 1000L);
    }
    
    private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) {
        return b3 << 24 | (b2 & 0xFF) << 16 | (b1 & 0xFF) << 8 | (b0 & 0xFF);
    }
    
    private static short makeShort(final byte b1, final byte b0) {
        return (short)((b1 & 0xFF) << 8 | (b0 & 0xFF));
    }
    
    private static byte int3(final int x) {
        return (byte)(x >> 24);
    }
    
    private static byte int2(final int x) {
        return (byte)(x >> 16);
    }
    
    private static byte int1(final int x) {
        return (byte)(x >> 8);
    }
    
    private static byte int0(final int x) {
        return (byte)x;
    }
    
    private static byte short1(final short x) {
        return (byte)(x >> 8);
    }
    
    private static byte short0(final short x) {
        return (byte)x;
    }
    
    static {
        NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());
        HEX_CHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        try {
            final SecureRandom secureRandom = new SecureRandom();
            RANDOM_VALUE1 = secureRandom.nextInt(16777216);
            RANDOM_VALUE2 = (short)secureRandom.nextInt(32768);
        }
        catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private static class SerializationProxy implements Serializable
    {
        private static final long serialVersionUID = 1L;
        private final byte[] bytes;
        
        SerializationProxy(final ObjectId objectId) {
            this.bytes = objectId.toByteArray();
        }
        
        private Object readResolve() {
            return new ObjectId(this.bytes);
        }
    }
}
