/* UtilLib - Copyright (c) 2011-2023 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.attr;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.text.CleanedString;


/**
 *
 * @author Vincent Calame
 */
public class AttributeChangeBuilder implements AttributeBuffer {

    private final SortedMap<AttributeKey, AttributeBuilder> attributeBuilderMap = new TreeMap<AttributeKey, AttributeBuilder>();
    private final Set<AttributeKey> removedKeySet = new LinkedHashSet<AttributeKey>();

    public AttributeChangeBuilder() {
    }

    public boolean containsValue(AttributeKey attributeKey) {
        AttributeBuilder builder = attributeBuilderMap.get(attributeKey);
        if (builder == null) {
            return false;
        }
        return !builder.isEmpty();
    }

    @Override
    public AttributeChangeBuilder appendValue(AttributeKey attributeKey, String value) {
        return (AttributeChangeBuilder) AttributeBuffer.super.appendValue(attributeKey, value);
    }

    @Override
    public AttributeChangeBuilder appendValue(AttributeKey attributeKey, CleanedString cleanedString) {
        if (cleanedString == null) {
            return this;
        }
        AttributeBuilder builder = attributeBuilderMap.get(attributeKey);
        if (builder == null) {
            builder = new AttributeBuilder(attributeKey);
            attributeBuilderMap.put(attributeKey, builder);
        }
        builder.addValue(cleanedString);
        return this;
    }

    public AttributeChangeBuilder appendValues(AttributeKey attributeKey, List<CleanedString> valueList) {
        if (valueList.isEmpty()) {
            return this;
        }
        AttributeBuilder builder = attributeBuilderMap.get(attributeKey);
        if (builder == null) {
            builder = new AttributeBuilder(attributeKey);
            attributeBuilderMap.put(attributeKey, builder);
        }
        for (CleanedString cs : valueList) {
            if (cs != null) {
                builder.addValue(cs);
            }
        }
        return this;
    }

    public AttributeChangeBuilder appendValues(Attribute attribute) {
        return appendValues(attribute.getAttributeKey(), attribute);
    }

    public AttributeChangeBuilder appendValues(AttributeKey attributeKey, Attribute attribute) {
        AttributeBuilder builder = attributeBuilderMap.get(attributeKey);
        if (builder == null) {
            builder = new AttributeBuilder(attributeKey);
            attributeBuilderMap.put(attributeKey, builder);
        }
        int valueLength = attribute.size();
        for (int i = 0; i < valueLength; i++) {
            builder.addValue(CleanedString.newInstance(attribute.get(i)));
        }
        return this;
    }

    public AttributeChangeBuilder clearAttribute(AttributeKey attributeKey) {
        attributeBuilderMap.remove(attributeKey);
        return this;
    }

    public AttributeChangeBuilder changeAttributes(AttributeChange attributeChange) {
        for (Attribute attribute : attributeChange.getChangedAttributes()) {
            put(attribute);
        }
        for (AttributeKey removedAttributeKey : attributeChange.getRemovedAttributeKeyList()) {
            attributeBuilderMap.remove(removedAttributeKey);
        }
        return this;
    }

    public void putRemovedAttributeKey(AttributeKey attributeKey) {
        removedKeySet.add(attributeKey);
    }

    public AttributeChangeBuilder merge(AttributeChangeBuilder otherAttributeChangeBuilder) {
        for (AttributeKey otherAttributeKey : otherAttributeChangeBuilder.removedKeySet) {
            removedKeySet.add(otherAttributeKey);
        }
        for (AttributeBuilder otherAttributeBuilder : otherAttributeChangeBuilder.attributeBuilderMap.values()) {
            appendValues(otherAttributeBuilder.toAttribute());
        }
        return this;
    }

    public AttributeChangeBuilder filterRealChanges(Attributes attributes, AttributeChange attributeChange, Predicate<AttributeKey> removeableAttributePredicate) {
        for (Attribute changedAttribute : attributeChange.getChangedAttributes()) {
            Attribute currentAttribute = attributes.getAttribute(changedAttribute.getAttributeKey());
            if ((currentAttribute == null) || (!AttributeUtils.isEqual(currentAttribute, changedAttribute))) {
                put(changedAttribute);
            }
        }
        for (AttributeKey attributeKey : attributeChange.getRemovedAttributeKeyList()) {
            if (attributes.getAttribute(attributeKey) != null) {
                putRemovedAttributeKey(attributeKey);
            }
        }
        for (Attribute attribute : attributes) {
            AttributeKey attributeKey = attribute.getAttributeKey();
            if (removeableAttributePredicate.test(attributeKey)) {
                if (attributeChange.getChangedAttributes().getAttribute(attributeKey) == null) {
                    putRemovedAttributeKey(attributeKey);
                }
            }
        }
        return this;
    }

    public Attributes toAttributes(@Nullable Attributes previousAttributes) {
        if (previousAttributes == null) {
            previousAttributes = AttributeUtils.EMPTY_ATTRIBUTES;
        }
        if ((attributeBuilderMap.isEmpty()) && (removedKeySet.isEmpty())) {
            return previousAttributes;
        }
        List<Attribute> attributeList = new ArrayList<Attribute>();
        for (Attribute attribute : previousAttributes) {
            AttributeKey attributeKey = attribute.getAttributeKey();
            if ((!attributeBuilderMap.containsKey(attributeKey)) && (!removedKeySet.contains(attributeKey))) {
                attributeList.add(attribute);
            }
        }
        for (AttributeBuilder builder : attributeBuilderMap.values()) {
            Attribute attribute = builder.toAttribute();
            if (attribute != null) {
                attributeList.add(attribute);
            }
        }
        int size = attributeList.size();
        if (size == 0) {
            return AttributeUtils.EMPTY_ATTRIBUTES;
        }
        if (size == 1) {
            return AttributeUtils.toSingletonAttributes(attributeList.get(0));
        }
        Attribute[] array = attributeList.toArray(new Attribute[size]);
        return new InternalAttributes(array);
    }

    public AttributeChange toAttributeChange() {
        Attributes changedAttributes = toAttributes(null);
        for (AttributeKey attributeKey : attributeBuilderMap.keySet()) {
            removedKeySet.remove(attributeKey);
        }
        List<AttributeKey> removedAttributeKeyList;
        if (removedKeySet.isEmpty()) {
            removedAttributeKeyList = AttributeUtils.EMPTY_ATTRIBUTEKEYLIST;
        } else {
            removedAttributeKeyList = AttributeUtils.wrap(removedKeySet.toArray(new AttributeKey[removedKeySet.size()]));
        }
        return new InternalChangeAttribute(changedAttributes, removedAttributeKeyList);
    }

    private void put(Attribute attribute) {
        AttributeKey attributeKey = attribute.getAttributeKey();
        AttributeBuilder builder = new AttributeBuilder(attributeKey);
        for (String value : attribute) {
            builder.addValue(CleanedString.newInstance(value));
        }
        attributeBuilderMap.put(attributeKey, builder);
    }

    public static AttributeChangeBuilder init() {
        return new AttributeChangeBuilder();
    }


    private static class InternalAttributes extends AbstractList<Attribute> implements Attributes {

        private final Attribute[] attributeArray;

        private InternalAttributes(Attribute[] attributeArray) {
            this.attributeArray = attributeArray;
        }

        @Override
        public Attribute getAttribute(AttributeKey key) {
            for (Attribute attribute : attributeArray) {
                if (attribute.getAttributeKey().equals(key)) {
                    return attribute;
                }
            }
            return null;
        }

        @Override
        public int size() {
            return attributeArray.length;
        }

        @Override
        public Attribute get(int i) {
            return attributeArray[i];
        }

    }


    private static class InternalChangeAttribute implements AttributeChange {

        private final Attributes changedAttributes;
        private final List<AttributeKey> removedAttributeKeyList;

        private InternalChangeAttribute(Attributes changedAttributes, List<AttributeKey> removedAttributeKeyList) {
            this.changedAttributes = changedAttributes;
            this.removedAttributeKeyList = removedAttributeKeyList;
        }

        @Override
        public Attributes getChangedAttributes() {
            return changedAttributes;
        }

        @Override
        public List<AttributeKey> getRemovedAttributeKeyList() {
            return removedAttributeKeyList;
        }

    }


}
