package com.elderbyte.vidada.domain.tags;

import com.elderbyte.common.ArgumentNullException;
import com.elderbyte.vidada.domain.documents.metadata.DocumentMetaData;

import javax.persistence.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static java.util.stream.Collectors.toList;


/**
 * Represents a simple tag, where the the tag name is the id.
 * The name id of a tag must follow strict rules:
 *
 * - A tag is case insensitive (it is always converted to lower-case)
 * - A tag must match the regex (\w{2,}\.?)+
 *
 * Examples:
 *
 * hello
 * hello.world
 * hello.world.again
 *
 * @author IsNull
 *
 */
@Entity
public class Tag  implements Comparable<Tag> {

    /***************************************************************************
     *                                                                         *
     * Static builder                                                          *
     *                                                                         *
     **************************************************************************/

    private static Predicate<String> tagValidator = Pattern.compile("(\\w{2,}\\.?)+").asPredicate();

    public static String toString(Collection<Tag> tags){
        return "[" + String.join(",", tags.stream().map(t -> t.getName()).collect(toList())) + "]";
    }

    /**
     * Creates a new Tag from the given string.
     * @param tagName
     * @return
     * @throws ArgumentNullException if tagName is null
     */
    public static Optional<Tag> buildTag(String tagName){

        if(tagName == null) throw new ArgumentNullException("tagName");

        tagName = toTagIdName(tagName);
        if(isTagValid(tagName)){
            return Optional.ofNullable(new Tag(tagName));
        }else{
            return Optional.empty();
        }
    }

    /**
     * Creates a new Tag from the given string.
     * @param tags
     * @return
     */
    public static Set<Tag> buildTags(String... tags){
        Set<Tag> createdTags = new HashSet<>();
        for(String tagStr : tags){
            Optional<Tag> t = Tag.buildTag(tagStr);
            if(t.isPresent()){
                createdTags.add(t.get());
            }
        }
        return createdTags;
    }

    public static String toTagIdName(String text){
        String tagIdName = text.trim().toLowerCase();
        tagIdName = tagIdName.replaceAll("\\W", ".");  // Replace all non-word chars by .
        return tagIdName;
    }

    private static boolean isTagValid(String name){
        if(name != null && !name.isEmpty()){
            return tagValidator.test(name);
        }
        return false;
    }


	/***************************************************************************
	 *                                                                         *
	 * Private fields                                                          *
	 *                                                                         *
	 **************************************************************************/

	transient int hashcode_cache = -1;

	@Id
	@Column(name = "name", nullable=false)
	private String name;

	/***************************************************************************
	 *                                                                         *
	 * Constructors                                                            *
	 *                                                                         *
	 **************************************************************************/

    /**
     * Empty ORM Constructor
     */
	protected Tag() { }

    /**
     * Creates a new Tag with the given name
     * @param name
     */
	Tag(String name){
	    if(name == null) throw new ArgumentNullException("name");
        if(!isTagValid(name)) throw new IllegalArgumentException("tag name must be a valid tag id! '" +name+"'");
        this.name = name;
	}

	/***************************************************************************
	 *                                                                         *
	 * Properties                                                              *
	 *                                                                         *
	 **************************************************************************/

    /**
     * Gets the tag name
     * @return
     */
	public String getName() {
		return name;
	}

    /**
     * Is this tag valid
     */
    public boolean isValid() {
        return isTagValid(this.name);
    }

	/***************************************************************************
	 *                                                                         *
	 * Overridden Public API                                                   *
	 *                                                                         *
	 **************************************************************************/

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

	@Override
	public int hashCode() {
		if(hashcode_cache == -1){
			final int prime = 31;
			hashcode_cache = prime * 1 + ((name == null) ? 0 : name.hashCode());
		}
		return hashcode_cache;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Tag other = (Tag) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	@Override
	public int compareTo(Tag o) {
		if(o == null) return -1;
		return this.getName().compareTo(o.getName());
	}


}
