/* Copyright (C) 2004 - 2008  db4objects Inc.  http://www.db4o.com

This file is part of the db4o open source object database.

db4o is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o 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 General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */
package com.db4o.ext;

import com.db4o.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.activation.*;
import com.db4o.query.*;
import com.db4o.types.*;


/**
 * Class to identify a database by it's signature.
 * <br><br>db4o UUID handling uses a reference to the Db4oDatabase object, that
 * represents the database an object was created on.
 * 
 * @persistent
 * @exclude
 */
public class Db4oDatabase implements Db4oType, Internal4{
    
    public static final Db4oDatabase STATIC_IDENTITY = Debug.staticIdentity ? new Db4oDatabase(new byte[] {(byte)'d' , (byte)'e', (byte)'b', (byte)'u', (byte)'g'}, 1) : null;
    
    public static final int STATIC_ID = -1;

    /**
     * Field is public for implementation reasons, DO NOT TOUCH!
     */
    public byte[] i_signature;
    
    /**
     * Field is public for implementation reasons, DO NOT TOUCH!
     * 
     * This field is badly named, it really is the creation time.
     */
    // TODO: change to _creationTime with PersistentFormatUpdater
    public long i_uuid;
    
    private static final String CREATIONTIME_FIELD = "i_uuid"; 

    
    /**
     * cached ObjectContainer for getting the own ID.
     */
    private transient ObjectContainerBase i_stream;
    
    /**
     * cached ID, only valid in combination with i_objectContainer
     */
    private transient int i_id;
    
    /**
     * constructor for persistence
     */
    public Db4oDatabase(){
    }
    
    /**
     * constructor for comparison and to store new ones
     */
    public Db4oDatabase(byte[] signature, long creationTime){
    	// FIXME: make sure signature is null
        i_signature = signature;
        i_uuid = creationTime;
    }
    
    /**
     * generates a new Db4oDatabase object with a unique signature.
     */
    public static Db4oDatabase generate() {
        if(Debug.staticIdentity){
            return STATIC_IDENTITY;
        }
        StatefulBuffer writer = new StatefulBuffer(null, 300);
        new LatinStringIO().write(writer, SignatureGenerator.generateSignature());
        return new Db4oDatabase(
                writer.getWrittenBytes(),
                System.currentTimeMillis());

    }
    
    /**
     * comparison by signature.
     */
    public boolean equals(Object obj) {
    	if(obj==this) {
    		return true;
    	}
    	if(obj==null||this.getClass()!=obj.getClass()) {
    		return false;
    	}
        Db4oDatabase other = (Db4oDatabase)obj;
        if (null == other.i_signature || null == this.i_signature) {
        	return false;
        }
		return Arrays4.areEqual(other.i_signature, this.i_signature);
    }

    public int hashCode() {
    	return i_signature.hashCode();
    }
    
	/**
	 * gets the db4o ID, and may cache it for performance reasons.
	 * 
	 * @return the db4o ID for the ObjectContainer
	 */
    public int getID(Transaction trans) {
        if(Debug.staticIdentity){
            return STATIC_ID; 
        }
        ObjectContainerBase stream = trans.container();
        if(stream != i_stream) {
            i_stream = stream;
            i_id = bind(trans);
        }
        return i_id;
    }
    
    public long getCreationTime(){
        return i_uuid;
    }
    
    /**
     * returns the unique signature 
     */
    public byte[] getSignature(){
        return i_signature;
    }
    
    public String toString(){
        return "db " + i_signature;
    }
    
    public boolean isOlderThan(Db4oDatabase peer){
		
		if(peer == this) 
			throw new IllegalArgumentException(); 
        
        if(i_uuid != peer.i_uuid){
            return i_uuid < peer.i_uuid;
        }
        
        // the above logic has failed, both are the same
        // age but we still want to distinguish in some 
        // way, to have an order in the ReplicationRecord
        
        // The following is arbitrary, it only needs to
        // be repeatable.
        
        // Let's distinguish by signature length 
        
        if(i_signature.length != peer.i_signature.length ){
            return i_signature.length < peer.i_signature.length;
        }
        
        for (int i = 0; i < i_signature.length; i++) {
            if(i_signature[i] != peer.i_signature[i]){
                return i_signature[i] < peer.i_signature[i];
            }
        }
        
        // This should never happen.
        
        // FIXME: Add a message and move to Messages.
        // 
        throw new RuntimeException();
    }
    
    /**
     * make sure this Db4oDatabase is stored. Return the ID.  
     */
    public int bind(Transaction trans){
        ObjectContainerBase stream = trans.container();
        Db4oDatabase stored = (Db4oDatabase)stream.db4oTypeStored(trans,this);
        if (stored == null) {
        	return storeAndGetId(trans);
        }
        if(stored == this){
            return stream.getID(trans, this);
        }
        if(i_uuid == 0){
            i_uuid = stored.i_uuid;
        }
        stream.showInternalClasses(true);
        try {
	        int id = stream.getID(trans, stored);
	        stream.bind(trans, this, id);
	        return id;
	    } finally {
            stream.showInternalClasses(false);
        }
    }

	private int storeAndGetId(Transaction trans) {
		ObjectContainerBase stream = trans.container();
		stream.showInternalClasses(true);
		try {
		    stream.store3(trans,this, 2, false);
		    return stream.getID(trans, this);
		} finally {
			stream.showInternalClasses(false);
		}
	}
    
    /**
     * find a Db4oDatabase with the same signature as this one
     */
    public Db4oDatabase query(Transaction trans){
        // showInternalClasses(true);  has to be set for this method to be successful
        if(i_uuid > 0){
            // try fast query over uuid (creation time) first
            Db4oDatabase res = query(trans, true);
            if(res != null){
                return res;
            }
        }
        // if not found, try to find with signature
        return query(trans, false);
    }
    
    private Db4oDatabase query(Transaction trans, boolean constrainByUUID){
        ObjectContainerBase stream = trans.container();
        Query q = stream.query(trans);
        q.constrain(getClass());
        if(constrainByUUID){
            q.descend(CREATIONTIME_FIELD).constrain(new Long(i_uuid));
        }
        ObjectSet objectSet = q.execute();
        while (objectSet.hasNext()) {
            Db4oDatabase storedDatabase = (Db4oDatabase) objectSet.next();
            stream.activate(null, storedDatabase, new FixedActivationDepth(4));
            if (storedDatabase.equals(this)) {
                return storedDatabase;
            }
        }
        return null;
    }
}
    
    
