/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.transaction;

import com.caucho.util.Alarm;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;

import javax.transaction.xa.Xid;

/**
 * Transaction identifier implementation.
 */
public class XidImpl implements Xid {
  private static L10N L = new L10N(XidImpl.class);

  public static final int GLOBAL_LENGTH = 28;

  private byte []_global;
  private byte []_local;

  /**
   * Creates a new transaction id.
   *
   * @param id the 64 bit number for the id.
   */
  public XidImpl(long serverId, long randomId)
  {
    _global = new byte[GLOBAL_LENGTH];
    _local = new byte[4];
    _local[0] = 1;

    // the global id has the requirement of being globally unique

    // first 4 identify as Resin.
    _global[0] = 'R';
    _global[1] = 'e';
    _global[2] = 's';
    _global[3] = 'n';

    // next 8 is the crc64 of the caucho.server-id
    _global[4] = (byte) (serverId >> 56);
    _global[5] = (byte) (serverId >> 48);
    _global[6] = (byte) (serverId >> 40);
    _global[7] = (byte) (serverId >> 32);
    _global[8] = (byte) (serverId >> 24);
    _global[9] = (byte) (serverId >> 16);
    _global[10] = (byte) (serverId >> 8);
    _global[11] = (byte) (serverId);

    // next 8 is the current timestamp
    long timestamp = Alarm.getCurrentTime();

    _global[12] = (byte) (timestamp >> 56);
    _global[13] = (byte) (timestamp >> 48);
    _global[14] = (byte) (timestamp >> 40);
    _global[15] = (byte) (timestamp >> 32);
    _global[16] = (byte) (timestamp >> 24);
    _global[17] = (byte) (timestamp >> 16);
    _global[18] = (byte) (timestamp >> 8);
    _global[19] = (byte) (timestamp);

    // next 8 is a 64-bit random long
    _global[20] = (byte) (randomId >> 56);
    _global[21] = (byte) (randomId >> 48);
    _global[22] = (byte) (randomId >> 40);
    _global[23] = (byte) (randomId >> 32);
    _global[24] = (byte) (randomId >> 24);
    _global[25] = (byte) (randomId >> 16);
    _global[26] = (byte) (randomId >> 8);
    _global[27] = (byte) (randomId);
  }

  XidImpl(XidImpl base, int branch)
  {
    _global = new byte[base._global.length];
    _local = new byte[4];
    _local[0] = (byte) (branch);

    System.arraycopy(base._global, 0, _global, 0, _global.length);
  }

  public XidImpl(byte []global, byte []local)
  {
    _global = new byte[global.length];
    _local = new byte[local.length];

    System.arraycopy(global, 0, _global, 0, global.length);
    System.arraycopy(local, 0, _local, 0, local.length);
  }

  XidImpl(byte []global)
  {
    _global = new byte[global.length];
    _local = new byte[4];

    System.arraycopy(global, 0, _global, 0, global.length);
    _local[0] = 1;
  }

  XidImpl(byte []global, int length)
  {
    _global = new byte[length];
    _local = new byte[4];

    System.arraycopy(global, 0, _global, 0, length);
    _local[0] = 1;
  }

  public int getFormatId()
  {
    return 1234;
  }

  public byte []getBranchQualifier()
  {
    return _local;
  }

  public byte []getGlobalTransactionId()
  {
    return _global;
  }

  boolean isSameServer(long serverId)
  {
    // first 4 identify as Resin.
    if (_global[0] != 'R'
        || _global[1] != 'e'
        || _global[2] != 's'
        || _global[3] != 'n') {
      return false;
    }

    // next 8 is the crc64 of the caucho.server-id
    if (_global[4] != (byte) (serverId >> 56)
        || _global[5] != (byte) (serverId >> 48)
        || _global[6] != (byte) (serverId >> 40)
        || _global[7] != (byte) (serverId >> 32)
        || _global[8] != (byte) (serverId >> 24)
        || _global[9] != (byte) (serverId >> 16)
        || _global[10] != (byte) (serverId >> 8)
        || _global[11] != (byte) (serverId)) {
      return false;
    }

    return true;
  }

  /**
   * Clones the xid.
   */
  public Object clone()
  {
    return new XidImpl(_global, _local);
  }

  /**
   * Returns hashCode.
   */
  public int hashCode()
  {
    byte []global = _global;

    int hash = 37;

    for (int i = global.length - 1; i >= 0; i--)
      hash = 65521 * hash + global[i];

    return hash;
  }

  /**
   * Returns equality.
   */
  public boolean equals(Object o)
  {
    if (! (o instanceof Xid))
      return false;

    Xid xid = (Xid) o;

    byte []global = xid.getGlobalTransactionId();
    byte []local = xid.getBranchQualifier();

    if (global.length != _global.length)
      return false;

    byte []selfGlobal = _global;
    byte []selfLocal = _local;

    for (int i = global.length - 1; i >= 0; i--) {
      if (global[i] != selfGlobal[i])
        return false;
    }

    for (int i = local.length - 1; i >= 0; i--) {
      if (local[i] != selfLocal[i])
        return false;
    }

    return true;
  }

  /**
   * Printable version of the transaction id.
   */
  public String toString()
  {
    CharBuffer cb = CharBuffer.allocate();

    cb.append("Xid[");

    byte []branch = getBranchQualifier();

    addByte(cb, branch[0]);

    cb.append(":");

    byte []global = getGlobalTransactionId();
    for (int i = 24; i < 28; i++)
      addByte(cb, global[i]);

    cb.append("]");

    return cb.close();
  }

  /**
   * Adds hex for debug
   *
   * @param cb the character buffer for the new value
   * @param b the byte value
   */
  static private void addByte(CharBuffer cb, int b)
  {
    b = b & 0xff;

    int h = (b / 16) & 0xf;
    int l = b & 0xf;

    if (h >= 10)
      cb.append((char) ('a' + h - 10));
    else
      cb.append((char) ('0' + h));

    if (l >= 10)
      cb.append((char) ('a' + l - 10));
    else
      cb.append((char) ('0' + l));
  }
}
