//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// 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.1 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

package com.threerings.stats.data;

import java.util.zip.CRC32;

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

import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;

import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;

import com.threerings.presents.dobj.DSet;

import static com.threerings.stats.Log.log;

/**
 * A base class for persistent statistics tracked on a per-player basis (some for a single game,
 * others for all time).
 */
public abstract class Stat
    implements DSet.Entry, Cloneable
{
    /**
     * Defines the various per-player tracked statistics.
     */
    public interface Type extends Serializable
    {
        /** Returns a new blank stat instance of the specified type. */
        public Stat newStat ();

        /** Returns the enum name of stat. */
        public String name ();

        /** Returns the unique code for this stat which is a function of its name. */
        public int code ();

        /** Returns true if this stat is persisted between sessions. */
        public boolean isPersistent ();
    }

    /** Provides auxilliary information to statistics during the persisting process. */
    public static interface AuxDataSource
    {
        /** Maps the specified string to a unique integer value. */
        public int getStringCode (Type type, String value);

        /** Maps the specified unique code back to its string value. */
        public String getCodeString (Type type, int code);
    }

    /**
     * Maps a {@link Type}'s code code back to a {@link Type} instance.
     */
    public static Type getType (int code)
    {
        return _codeToType.get(code);
    }

    /**
     * Used by the {@link Type} implementation to map itself to an integer code.
     *
     * @return the code to which the supplied type was mapped.
     */
    public static int initType (Type type, Stat prototype)
    {
        // compute a code for this stat using the CRC32 hash of its name
        _crc.reset();
        _crc.update(type.name().getBytes());
        int code = (int)_crc.getValue();

        // make sure the code does not collide
        if (_codeToType.containsKey(code)) {
            log.warning("Stat type collision! " + type.name() + " and " +
                        _codeToType.get(code).name() + " both map to '" + code + "'.");
            return -1;
        }

        // initialize the prototype
        prototype._type = type;

        // map the stat by its code
        _codeToType.put(code, type);
        return code;
    }

    /**
     * Returns the type of this statistic.
     */
    public Type getType ()
    {
        return _type;
    }

    /**
     * Returns the integer code to which this statistic's name maps.
     */
    public int getCode ()
    {
        return _type.code();
    }

    /**
     * Returns true if the supplied statistic has been modified since it
     * was loaded from the repository.
     */
    public boolean isModified ()
    {
        return _modified;
    }

    /**
     * Forces this stat to consider itself modified. Generally this is not
     * called but rather the derived class will update its modified state when
     * it is actually modified.
     */
    public void setModified (boolean modified)
    {
        _modified = modified;
    }

    /**
     * Returns the modification count of this Stat when it was loaded from the repository.
     */
    public byte getModCount ()
    {
        return _modCount;
    }

    /**
     * Sets the modification count for this Stat. StatRepository calls this when converting
     * a StatRecord into a Stat; it shouldn't be called otherwise.
     */
    public void setModCount (byte modCount)
    {
        _modCount = modCount;
    }

    /**
     * Serializes this instance for storage in the item database. Derived classes must override
     * this method to implement persistence.
     */
    public abstract void persistTo (ObjectOutputStream out, AuxDataSource aux)
        throws IOException;

    /**
     * Unserializes this item from data obtained from the item database.  Derived classes must
     * override this method to implement persistence.
     */
    public abstract void unpersistFrom (ObjectInputStream in, AuxDataSource aux)
        throws IOException, ClassNotFoundException;

    @Override
    public String toString ()
    {
        StringBuilder buf = new StringBuilder(StringUtil.toUSLowerCase(_type.name()));
        buf.append("=").append(valueToString());
        buf.append("(").append(_modCount).append(")");
        return buf.toString();
    }

    /**
     * Derived statistics must override this method and render their value to a string. Used by
     * {@link #toString} and to display the value in game.
     */
    public abstract String valueToString ();

    // documentation inherited from DSet.Entry
    public String getKey ()
    {
        return _type.name();
    }

    @Override
    public Stat clone ()
    {
        try {
            return (Stat) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    /** The type of the statistic in question. */
    protected Type _type;

    /** Indicates whether or not this statistic has been modified since it was loaded from the
     * database. */
    protected boolean _modified;

    /** The last known modification count for this stat, if it was read from the database. */
    protected byte _modCount;

    /** Used for computing our stat codes. */
    protected static CRC32 _crc = new CRC32();

    /** The table mapping stat codes to enumerated types. */
    protected static HashIntMap<Type> _codeToType = new HashIntMap<Type>();
}

@@ -2930,105 +2930,36 @@
     
-_crc.reset();%0A        _crc.update(type.name().getBytes());%0A        int code = (int)_crc.getValue(
+int code = crc32(type.name()
 );%0A%0A
@@ -3408,32 +3408,272 @@
 rn code;%0A    %7D%0A%0A
+    /**%0A     * Computes and returns the CRC32 hash of the specified string.%0A     */%0A    public static int crc32 (String string)%0A    %7B%0A        _crc.reset();%0A        _crc.update(string.getBytes());%0A        return (int)_crc.getValue();%0A    %7D%0A%0A
     /**%0A     * R

Move the CRC computation to its own method so that we can use it elsewhere.