/*  PAT: Persistent Applications Toolkit (patsystem.sf.net)
 *  Copyright (C) 2004, 2005  Tomasz Nazar, nthx at irc dot pl
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Full version of the license is /docs/LICENSE.txt
 */
package org.nthx.pat;

import org.apache.log4j.Logger;
import org.prevayler.Command;
import org.prevayler.PrevalentSystem;
import org.prevayler.implementation.SnapshotPrevayler;

import java.io.IOException;
import java.io.Serializable;

/** PAT: some type of 'facade' to main system features. Interceptors, mixins
 *  are connected by this class.
 *  <p>Introduction of it helped me to manage all of the scattered PAT logic.
 *  <p>This class is singleton, because right now I don't like to have full
 *  set of static methods.
 *  <p><b>Note</b>: this object is for internall needs. It's not part of public API.
 *
 *  @version $Id: Pat.java 3725 2005-06-09 23:57:03Z nthx $
 *  @author nthx@users.sourceforge.net
 */
public class Pat
{
    //--- internal Fields -------
    private transient static Pat INSTANCE;
    private transient static Logger log = Logger.getLogger("pat");
    private transient static boolean NO_COMMANDS_MODE = 
        Boolean.valueOf(System.getProperty("pat.no-commands", "false")).booleanValue();


    //--- Fields ----------------
    private transient PrevalentSystem patRoot; //root of all objects...
    private transient SnapshotPrevayler snapshotPrevayler;
    private transient IdentityMapMixin temporaryIdentityMap;

    //distinction between recovered system from snapshot or just recovered one
    private transient boolean freshRoot = true;
    
    transient static boolean finishedCreatingRoot = false;


    //--- Constructors ----------
    private Pat()
    {
        temporaryIdentityMap = new IdentityMapMixin();
        if (isInNoCommandsMode())
        {
            String warning = 
                " ****************************** " +
                " * PAT is in NO-COMMANDS-MODE * " +
                " ****************************** ";
                
            System.out.println(warning);
            log.warn(warning);
        }
    }

    //--- Getters and Setters ---
    boolean isFreshRoot() { return freshRoot; }

    public IdentityMap getRootAsIdentityMap()
    {
        if (null != patRoot)
        {
            if (null != temporaryIdentityMap && finishedCreatingRoot)
                throw new IllegalStateException("Temporary IM should be null");
            return (IdentityMap)patRoot;
        }
        else
        {
            /* This situation happens when root objects is being instantiated
             * with <code>new</code> keyword. In this moment 'snapshotPrevayler'
             * doesn't exist, but new objects (inside constructor) are being 
             * created.
             * That's why we create temporary IdentityMap for them, and after 
             * creating 'snapshotPrevayler' we throw it away (after earlier 
             * snapshot) or copy values to original pat's root.
             */
            return temporaryIdentityMap;
        }
    }

    public static Pat getInstance()
    {
        if (null == INSTANCE)
            INSTANCE = new Pat();
        return INSTANCE;
    }

    public static void unload()
    {
        log.warn("Unloading PAT instance: " + INSTANCE);
        INSTANCE = null;
        finishedCreatingRoot = false;
    }
    
    Serializable executeCommand(Command command)
    throws Throwable
    {
        return snapshotPrevayler.executeCommand(command);
    }

    public void updateRoot(PrevalentSystem systemFromSnapshot,
                           PrevalentSystem newlyCreatedSystem)
    {
        if (systemFromSnapshot == newlyCreatedSystem)
            throw new IllegalStateException("Should differ!");

        if (null == systemFromSnapshot)
        {
            freshRoot = true;

            //system was just created, and commandLogs will be read for list moment...
            patRoot = newlyCreatedSystem;

            getRootAsIdentityMap().setMap(temporaryIdentityMap.getMap());
            getRootAsIdentityMap().setUniqueOID(temporaryIdentityMap.getUniqueOID());
            temporaryIdentityMap = null;
            //put root into the IM
            getRootAsIdentityMap().putObject((Identifiable)patRoot);
        }
        else
        {
            freshRoot = false;
            /*  system was recovered from snapshot..
                So: do not put there nothing, cause there are data in deserialized
                root.
                But update this local references for next Interceptors..:
            */
            patRoot = systemFromSnapshot;
            temporaryIdentityMap = null;
            ((IdentityMap)newlyCreatedSystem).setMap(null);
            ((IdentityMap)newlyCreatedSystem).setUniqueOID(-999L);
            newlyCreatedSystem = null;
        }
    }

    void setSnapshotPrevayler(SnapshotPrevayler snapshotPrevayler)
    {
        this.snapshotPrevayler = snapshotPrevayler;

        if (patRoot != snapshotPrevayler.system())
            throw new IllegalStateException("These two objects should be the same...");
    }

    void takeSnapshot()
    {
        try
        {
            snapshotPrevayler.takeSnapshot();
        } catch (IOException e)
        {
            log.error("Why?", e);
        }
    }

    void finishedCreatingRoot()
    {
        finishedCreatingRoot = true;
        printMemoryStatistics();
    }

    public boolean afterRootConstructor()
    {
        return finishedCreatingRoot;
    }
    public boolean beforeRootConstructor()
    {
        return !afterRootConstructor();
    }

    
    boolean isInNoCommandsMode()
    {
        return NO_COMMANDS_MODE;
    }
    
    private void printMemoryStatistics()
    {
        String percentage = "" + (float)getRootAsIdentityMap().getSize()*100 / Integer.MAX_VALUE;
        if (percentage.length() > 4 && percentage.charAt(2) == '.')
            percentage = percentage.substring(0, 4);
        else
            percentage = "0.00";
        
        log.debug("Objects in IdentityMap: " 
                  + getRootAsIdentityMap().getSize()
                  + " Usage of the map: " 
                  + percentage + "%");
        log.debug("------------------- * * * -----------------------");
    }

}