/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 * 
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, 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
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.kernel.impl.transaction;

import java.nio.ByteBuffer;

import javax.transaction.xa.Xid;

public class XidImpl implements Xid
{
    // Neo4j ('N' 'E'  'O') format identifier
    private static final int FORMAT_ID = 0x4E454E31; 

    private static final byte INSTANCE_ID[] = new byte[] { 'N', 'E', 'O', 'K',
        'E', 'R', 'N', 'L', '\0' };

    private static long nextSequenceId = 0;

    // next sequence number for global tx id
    private synchronized static long getNextSequenceId()
    {
        return nextSequenceId++;
    }

    // INSTANCE_ID + millitime(long) + seqnumber(long)
    private final byte globalId[];
    // branchId assumes Xid.MAXBQUALSIZE >= 4
    private final byte branchId[];
    private final int formatId;
    

    // resourceId.length = 4, unique for each XAResource
    static byte[] getNewGlobalId()
    {
        // create new global id ( [INSTANCE_ID][time][sequence] )
        byte globalId[] = new byte[INSTANCE_ID.length + 16];
        System.arraycopy( INSTANCE_ID, 0, globalId, 0, INSTANCE_ID.length );
        ByteBuffer byteBuf = ByteBuffer.wrap( globalId );
        byteBuf.position( INSTANCE_ID.length );
        long time = System.currentTimeMillis();
        long sequence = getNextSequenceId();
        byteBuf.putLong( time ).putLong( sequence );
        return globalId;
    }

    static boolean isThisTm( byte globalId[] )
    {
        if ( globalId.length < INSTANCE_ID.length )
        {
            return false;
        }
        for ( int i = 0; i < INSTANCE_ID.length; i++ )
        {
            if ( globalId[i] != INSTANCE_ID[i] )
            {
                return false;
            }
        }
        return true;
    }
    
    public XidImpl( byte globalId[], byte resourceId[] )
    {
        this( globalId, resourceId, FORMAT_ID );
    }

    // create xid for transaction with more than one XAResource enlisted
    public XidImpl( byte globalId[], byte resourceId[], int formatId )
    {
        if ( globalId.length > Xid.MAXGTRIDSIZE )
        {
            throw new IllegalArgumentException( "GlobalId length to long, "
                + globalId.length );
        }
        if ( resourceId.length > Xid.MAXBQUALSIZE )
        {
            throw new IllegalArgumentException(
                "BranchId (resource id) to long, " + resourceId.length );
        }
        this.globalId = globalId;
        this.branchId = resourceId;
        this.formatId = formatId;
    }

    public byte[] getGlobalTransactionId()
    {
        return globalId.clone();
    }

    public byte[] getBranchQualifier()
    {
        return branchId.clone();
    }

    public int getFormatId()
    {
        return FORMAT_ID;
    }

    public boolean equals( Object o )
    {
        if ( !(o instanceof Xid) )
        {
            return false;
        }
        byte otherGlobalId[] = ((Xid) o).getGlobalTransactionId();
        byte otherBranchId[] = ((Xid) o).getBranchQualifier();

        if ( globalId.length != otherGlobalId.length || 
            branchId.length != otherBranchId.length )
        {
            return false;
        }

        for ( int i = 0; i < globalId.length; i++ )
        {
            if ( globalId[i] != otherGlobalId[i] )
            {
                return false;
            }
        }
        for ( int i = 0; i < branchId.length; i++ )
        {
            if ( branchId[i] != otherBranchId[i] )
            {
                return false;
            }
        }
        return true;
    }

    private volatile int hashCode = 0;

    public int hashCode()
    {
        if ( hashCode == 0 )
        {
            int calcHash = 0;
            for ( int i = 0; i < 3 && i < globalId.length; i++ )
            {
                calcHash += globalId[globalId.length - i - 1] << i * 8;
            }
            if ( branchId.length > 0 )
            {
                calcHash += branchId[0] << 3 * 8;
            }
            hashCode = 3217 * calcHash;
        }
        return hashCode;
    }

    public String toString()
    {
        StringBuffer buf = new StringBuffer( "GlobalId[" );
        if ( globalId.length == (INSTANCE_ID.length + 8 + 8) )
        {
            for ( int i = 0; i < INSTANCE_ID.length - 1; i++ )
            {
                buf.append( (char) globalId[i] );
            }
            ByteBuffer byteBuf = ByteBuffer.wrap( globalId );
            byteBuf.position( INSTANCE_ID.length );
            long time = byteBuf.getLong();
            long sequence = byteBuf.getLong();
            buf.append( '|' );
            buf.append( time );
            buf.append( '|' );
            buf.append( sequence );
        }
        else
        {
            buf.append( "UNKNOWN_ID" );
        }
        buf.append( "], BranchId[ " );
        for ( int i = 0; i < branchId.length; i++ )
        {
            buf.append( branchId[i] + " " );
        }
        buf.append( "]" );
        return buf.toString();
    }
}