/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.asset;

import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.SimpleAssetCache;
import com.jme3.export.*;
import java.io.IOException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <code>AssetKey</code> is a key that is used to
 * look up a resource from a cache. 
 * This class should be immutable.
 */
public class AssetKey<T> implements Savable, Cloneable {

    protected String name;
    protected transient String folder;
    protected transient String extension;
    
    public AssetKey(String name){
        this.name = reducePath(name);
        this.extension = getExtension(this.name);
    }

    public AssetKey(){
    }

    @Override
    public AssetKey<T> clone() {
        try {
            return (AssetKey<T>) super.clone();
        } catch (CloneNotSupportedException ex) {
            throw new AssertionError();
        }
    }
    
    protected static String getExtension(String name) {
        int idx = name.lastIndexOf('.');
        //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
        if (name.toLowerCase().endsWith(".xml")) {
            idx = name.substring(0, idx).lastIndexOf('.');
            if (idx == -1) {
                idx = name.lastIndexOf('.');
            }
        }
        if (idx <= 0 || idx == name.length() - 1) {
            return "";
        } else {
            return name.substring(idx + 1).toLowerCase();
        }
    }

    protected static String getFolder(String name) {
        int idx = name.lastIndexOf('/');
        if (idx <= 0 || idx == name.length() - 1) {
            return "";
        } else {
            return name.substring(0, idx + 1);
        }
    }

    /**
     * @return The asset path
     */
    public String getName() {
        return name;
    }

    /**
     * @return The extension of the <code>AssetKey</code>'s name. For example,
     * the name "Interface/Logo/Monkey.png" has an extension of "png".
     */
    public String getExtension() {
        return extension;
    }

    /**
     * @return The folder in which the asset is located in.
     * E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o" 
     * then "Models/MyModel/" is returned.
     */
    public String getFolder(){
        if (folder == null)
            folder = getFolder(name);
        
        return folder;
    }

    /**
     * @return The preferred cache class for this asset type. Specify "null"
     * if caching is to be disabled. By default the 
     * {@link SimpleAssetCache} is returned.
     */
    public Class<? extends AssetCache> getCacheType(){
        return SimpleAssetCache.class;
    }
    
    /**
     * @return The preferred processor type for this asset type. Specify "null"
     * if no processing is required.
     */
    public Class<? extends AssetProcessor> getProcessorType(){
        return null;
    }
    
    /**
     * Removes all relative elements of a path (A/B/../C.png and A/./C.png).
     * @param path The path containing relative elements
     * @return A path without relative elements
     */
    public static String reducePath(String path) {
        if (path == null || path.indexOf("./") == -1) {
            return path;
        }
        String[] parts = path.split("/");
        LinkedList<String> list = new LinkedList<String>();
        for (int i = 0; i < parts.length; i++) {
            String string = parts[i];
            if (string.length() == 0 || string.equals(".")) {
                //do nothing
            } else if (string.equals("..")) {
                if (list.size() > 0 && !list.getLast().equals("..")) {
                    list.removeLast();
                } else {
                    list.add("..");
                    Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path \"{0}\" is outside assetmanager root", path);
                }
            } else {
                list.add(string);
            }
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            String string = list.get(i);
            if (i != 0) {
                builder.append("/");
            }
            builder.append(string);
        }
        return builder.toString();
    }
    
    @Override
    public boolean equals(Object other){
        if (!(other instanceof AssetKey)){
            return false;
        }
        return name.equals(((AssetKey)other).name);
    }

    @Override
    public int hashCode(){
        return name.hashCode();
    }

    @Override
    public String toString(){
        return name;
    }

    public void write(JmeExporter ex) throws IOException {
        OutputCapsule oc = ex.getCapsule(this);
        oc.write(name, "name", null);
    }

    public void read(JmeImporter im) throws IOException {
        InputCapsule ic = im.getCapsule(this);
        name = reducePath(ic.readString("name", null));
        extension = getExtension(name);
    }

}
