/*
 ** 2011 April 5
 **
 ** The author disclaims copyright to this source code.  In place of
 ** a legal notice, here is a blessing:
 **    May you do good and not evil.
 **    May you find forgiveness for yourself and forgive others.
 **    May you share freely, never taking more than you give.
 */

package info.ata4.bsplib.entity;

import info.ata4.bsplib.vector.Vector3f;
import info.ata4.log.LogUtils;
import java.io.PrintStream;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;

/**
 * Abstract entity representation that works roughly like in Hammer. Has two
 * KeyValue lists, one for normal, unique key-values and one for I/O that may
 * contain duplicates.
 *
 * @author Nico Bergemann <barracuda415 at yahoo.de>
 */
public class Entity {

    private static final Logger L = LogUtils.getLogger();

    private Map<String, String> keyValue = new LinkedHashMap<>();
    private List<KeyValue> keyValueIO = new ArrayList<>();
    private String className;

    /**
     * Creates a new empty entity with the given class name.
     *
     * @param className entity class name, must not be null or empty
     */
    public Entity(String className) {
        if (className == null) {
            throw new NullPointerException();
        }

        if (className.length() == 0) {
            throw new IllegalArgumentException("Empty class name");
        }

        this.className = className;
    }

    /**
     * Creates a new entity from a list of raw key-values.
     *
     * @param kvList raw key-value list
     */
    public Entity(List<KeyValue> kvList) {
        for (KeyValue kv : kvList) {
            String key = kv.getKey();
            String value = kv.getValue();

            // special KV, don't add it
            if (key.equals("classname")) {
                if (className == null) {
                    className = value;
                } else {
                    L.log(Level.WARNING, "Found duplicate classname key, ignoring {0}", kv);
                }
                continue;
            }

            if (EntityIO.isEntityIO(kv)) {
                keyValueIO.add(kv);
            } else {
                keyValue.put(key, value);
            }
        }

        // check and add missing class name
        if (className == null || className.isEmpty()) {
            L.log(Level.WARNING, "Missing or empty class name, using \"unknown_entity\"");
            className = "unknown_entity";
        }
    }

    public List<KeyValue> getIO() {
        return keyValueIO;
    }

    public Set<String> getKeys() {
        return keyValue.keySet();
    }

    public Collection<String> getValues() {
        return keyValue.values();
    }

    public Set<Entry<String, String>> getEntrySet() {
        return keyValue.entrySet();
    }

    public boolean hasKey(String key) {
        return keyValue.containsKey(key);
    }

    public String getValue(String key) {
        return keyValue.get(key);
    }

    public void setValue(String key, Object value) {
        keyValue.put(key, String.valueOf(value));
    }

    public void setValue(KeyValue kv) {
        setValue(kv.getKey(), kv.getValue());
    }

    public void removeValue(String key) {
        keyValue.remove(key);
    }

    public void clear() {
        keyValue.clear();
        keyValueIO.clear();
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String value) {
        className = value;
    }

    public String getTargetName() {
        return getValue("targetname");
    }

    public void setTargetName(String value) {
        setValue("targetname", value);
    }

    public Vector3f getVector3f(String key) {
        String str = getValue(key);

        if (str == null) {
            return null;
        }

        // parse origin values
        try {
            // split string by whitespaces
            String[] costr = StringUtils.split(str, ' ');

            float x = costr.length > 0 ? Float.parseFloat(costr[0]) : 0;
            float y = costr.length > 1 ? Float.parseFloat(costr[1]) : 0;
            float z = costr.length > 2 ? Float.parseFloat(costr[2]) : 0;

            return new Vector3f(x, y, z);
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    public void setVector3f(String key, Vector3f value) {
        setValue(key, value.x + " " + value.y + " " + value.z);
    }

    public Vector3f getOrigin() {
        return getVector3f("origin");
    }

    public void setOrigin(Vector3f origin) {
        setVector3f("origin", origin);
    }

    public Vector3f getAngles() {
        Vector3f a = getVector3f("angles");

        if (a == null) {
            return null;
        }

        // swap pitch yaw roll (Y Z X) axes
        return new Vector3f(a.z, -a.x, a.y);
    }

    public void setAngles(Vector3f a) {
        setVector3f("angles", new Vector3f(a.z, -a.x, a.y));
    }

    /**
     * Returns the model number for this entity.
     *
     * @return The model number of this entity. -1 if no valid model number was
     * assigned or -2 if this entity is a prop.
     */
    public int getModelNum() {
        String model = getValue("model");

        if (model == null) {
            // no model
            return -1;
        } else if (model.startsWith("*")) {
            try {
                // get model id
                return Integer.parseInt(model.substring(1));
            } catch (NumberFormatException ex) {
                return -2;
            }
        } else if (model.length() == 0) {
            // worldspawn
            return 0;
        } else {
            // studio model or invalid model format
            return -2;
        }
    }

    public void setModelNum(int modelnum) {
        setValue("model", "*" + modelnum);
    }

    /**
     * Prints all key-values to a PrintStream.
     *
     * @param ps PrintStream to write to
     */
    public void dump(PrintStream ps) {
        ps.println(getClassName() + ":");

        for (String key : keyValue.keySet()) {
            String value = keyValue.get(key);
            if (key.equals("classname")) {
                continue;
            }
            ps.println("  " + key + " = " + value);
        }

        for (KeyValue kv : keyValueIO) {
            ps.println("  " + kv.getKey() + ": " + kv.getValue());
        }

        ps.println();
    }

    /**
     * Prints all key-values to standard output
     */
    public void dump() {
        dump(System.out);
    }

    @Override
    public String toString() {
        return getClassName() + (getTargetName() == null ? "" : " (" + getTargetName() + ")");
    }
}
