//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2014.01.21 at 01:52:19 PM IST
//
package io.github.jeddict.jpa.spec;

import io.github.jeddict.db.accessor.EmbeddedIdSpecAccessor;
import io.github.jeddict.db.accessor.IdSpecAccessor;
import io.github.jeddict.db.accessor.VersionSpecAccessor;
import io.github.jeddict.jpa.spec.extend.Attribute;
import io.github.jeddict.jpa.spec.extend.IPrimaryKeyAttributes;
import io.github.jeddict.jpa.spec.extend.JavaClass;
import io.github.jeddict.jpa.spec.extend.PersistenceAttributes;
import io.github.jeddict.jpa.spec.workspace.WorkSpace;
import io.github.jeddict.source.ClassExplorer;
import io.github.jeddict.source.MemberExplorer;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
import static io.github.jeddict.util.StringUtils.isNotBlank;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.XMLAttributes;

/**
 *
 *
 * This element contains the entity field or property mappings. It may be
 * sparsely populated to include only a subset of the fields or properties. If
 * metadata-complete for the entity is true then the remainder of the attributes
 * will be defaulted according to the default rules.
 *
 *
 *
 * <p>
 * Java class for attributes complex type.
 *
 * <p>
 * The following schema fragment specifies the expected content contained within
 * this class.
 *
 * <pre>
 * &lt;complexType name="attributes">
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="description" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
 *         &lt;choice>
 *           &lt;element name="id" type="{http://java.sun.com/xml/ns/persistence/orm}id" maxOccurs="unbounded" minOccurs="0"/>
 *           &lt;element name="embedded-id" type="{http://java.sun.com/xml/ns/persistence/orm}embedded-id" minOccurs="0"/>
 *         &lt;/choice>
 *         &lt;element name="basic" type="{http://java.sun.com/xml/ns/persistence/orm}basic" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="version" type="{http://java.sun.com/xml/ns/persistence/orm}version" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="many-to-one" type="{http://java.sun.com/xml/ns/persistence/orm}many-to-one" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="one-to-many" type="{http://java.sun.com/xml/ns/persistence/orm}one-to-many" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="one-to-one" type="{http://java.sun.com/xml/ns/persistence/orm}one-to-one" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="many-to-many" type="{http://java.sun.com/xml/ns/persistence/orm}many-to-many" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="element-collection" type="{http://java.sun.com/xml/ns/persistence/orm}element-collection" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="embedded" type="{http://java.sun.com/xml/ns/persistence/orm}embedded" maxOccurs="unbounded" minOccurs="0"/>
 *         &lt;element name="transient" type="{http://java.sun.com/xml/ns/persistence/orm}transient" maxOccurs="unbounded" minOccurs="0"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 *
 *
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "primaryKeyAttributes", propOrder = {
    "description",
    "id",
    "embeddedId",
    "version"
})
public class PrimaryKeyAttributes extends PersistenceAttributes<IdentifiableClass> implements IPrimaryKeyAttributes {

    protected String description;
    protected List<Id> id;
    @XmlElement(name = "embedded-id")
    protected EmbeddedId embeddedId;
    protected List<Version> version;

    @Override
    public void load(ClassExplorer clazz) {
        Collection<MemberExplorer> members = clazz.getMembers();
        Set<String> mapsId = new HashSet<>();
        MemberExplorer embeddedIdMember = null;

        for (MemberExplorer member : members) {
            if (member.isAnnotationPresent(jakarta.persistence.Id.class)
                    && !(member.isAnnotationPresent(jakarta.persistence.OneToOne.class)
                    || member.isAnnotationPresent(jakarta.persistence.ManyToOne.class))) {
                this.findId(member.getFieldName())
                        .orElseGet(() -> {
                            Id id = new Id();
                            this.addId(id);
                            return id;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.Version.class)) {
                this.findVersion(member.getFieldName())
                        .orElseGet(() -> {
                            Version version = new Version();
                            this.addVersion(version);
                            return version;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.EmbeddedId.class)) {
                this.setEmbeddedId(EmbeddedId.load(member));
                embeddedIdMember = member;
            } else if (member.isAnnotationPresent(jakarta.persistence.Basic.class)) {
                this.findBasic(member.getFieldName())
                        .orElseGet(() -> {
                            Basic basic = new Basic();
                            this.addBasic(basic);
                            return basic;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.Transient.class)) {
                this.findTransient(member.getFieldName())
                        .orElseGet(() -> {
                            Transient _transient = new Transient();
                            this.addTransient(_transient);
                            return _transient;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.ElementCollection.class)) {
                Optional<ElementCollection> elementCollectionOpt = this.findElementCollection(member.getFieldName());
                if (elementCollectionOpt.isPresent()) {
                    ElementCollection.load(elementCollectionOpt.get(), member);
                } else {
                    ElementCollection elementCollection = ElementCollection.load(new ElementCollection(), member);
                    if (nonNull(elementCollection)) {
                        this.addElementCollection(elementCollection);
                    }
                }
            } else if (member.isAnnotationPresent(jakarta.persistence.OneToOne.class)) {
                OneToOne oneToOne = this.findOneToOne(member.getFieldName())
                        .orElseGet(() -> {
                            OneToOne oneToOneObj = new OneToOne();
                            this.addOneToOne(oneToOneObj);
                            return oneToOneObj;
                        });
                oneToOne.load(member);
                if (isNotBlank(oneToOne.getMapsId())) {
                    mapsId.add(oneToOne.getMapsId());
                } else {
                    mapsId.add(oneToOne.getName());
                }
            } else if (member.isAnnotationPresent(jakarta.persistence.ManyToOne.class)) {
                ManyToOne manyToOne = this.findManyToOne(member.getFieldName())
                        .orElseGet(() -> {
                            ManyToOne manyToOneObj = new ManyToOne();
                            this.addManyToOne(manyToOneObj);
                            return manyToOneObj;
                        });
                manyToOne.load(member);
                if (isNotBlank(manyToOne.getMapsId())) {
                    mapsId.add(manyToOne.getMapsId());
                } else {
                    mapsId.add(manyToOne.getName());
                }
            } else if (member.isAnnotationPresent(jakarta.persistence.OneToMany.class)) {
                this.findOneToMany(member.getFieldName())
                        .orElseGet(() -> {
                    OneToMany oneToMany = new OneToMany();
                    this.addOneToMany(oneToMany);
                    return oneToMany;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.ManyToMany.class)) {
                this.findManyToMany(member.getFieldName())
                        .orElseGet(() -> {
                    ManyToMany manyToMany = new ManyToMany();
                    this.addManyToMany(manyToMany);
                    return manyToMany;
                        }).load(member);
            } else if (member.isAnnotationPresent(jakarta.persistence.Embedded.class)) {
                Optional<Embedded> embeddedOpt = this.findEmbedded(member.getFieldName());
                if (embeddedOpt.isPresent()) {
                    Embedded.load(embeddedOpt.get(), member);
                } else {
                    Embedded embedded = Embedded.load(new Embedded(), member);
                    if (nonNull(embedded)) {
                        this.addEmbedded(embedded);
                    }
                }
            } else {
                this.findBasic(member.getFieldName()) //Default Annotation
                        .orElseGet(() -> {
                            Basic basic = new Basic();
                            this.addBasic(basic);
                            return basic;
                        }).load(member);
            }
        }

        if (this.getEmbeddedId() != null) {
            String embeddableClassFQN = null;
            try {
                embeddableClassFQN = embeddedIdMember.getType();

                Optional<ClassExplorer> embeddableOpt = clazz.getSource().createClass(embeddableClassFQN);
                if (embeddableOpt.isPresent()) {
                    for (MemberExplorer member : embeddableOpt.get().getMembers()) {
                        if (!mapsId.contains(member.getFieldName())) {
                            this.findId(member.getFieldName())
                                    .orElseGet(() -> {
                                        Id id = new Id();
                                        this.addId(id);
                                        return id;
                                    }).load(member);
                        }
                    }
                }
            } catch (FileNotFoundException ex) {
                throw new IllegalStateException("Unable to load class " + embeddableClassFQN);
            }
        }
    }

    /**
     * Gets the value of the description property.
     *
     * @return possible object is {@link String }
     *
     */
    @Override
    public String getDescription() {
        return description;
    }

    /**
     * Sets the value of the description property.
     *
     * @param value allowed object is {@link String }
     *
     */
    @Override
    public void setDescription(String value) {
        this.description = value;
    }

    /**
     * Gets the value of the id property.
     *
     * <p>
     * This accessor method returns a reference to the live list, not a
     * snapshot. Therefore any modification you make to the returned list will
     * be present inside the JAXB object. This is why there is not a
     * <CODE>set</CODE> method for the id property.
     *
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getId().add(newItem);
     * </pre>
     *
     *
     * <p>
     * Objects of the following type(s) are allowed in the list {@link Id }
     *
     *
     */
    @Override
    public List<Id> getId() {
        if (id == null) {
            id = new ArrayList<>();
        }
        return this.id;
    }

    @Override
    public void addId(Id id) {
        this.getId().add(id);
        notifyListeners(id, "addAttribute", null, null);
        id.setAttributes(this);
    }

    @Override
    public void removeId(Id id) {
        this.getId().remove(id);
        notifyListeners(id, "removeAttribute", null, null);
    }

    public Optional<Id> getId(String _id) {
        return findById(id, _id);
    }

    public Optional<Id> findId(String name) {
        return findByName(id, name);
    }

    /**
     * Gets the value of the embeddedId property.
     *
     * @return possible object is {@link EmbeddedId }
     *
     */
    @Override
    public EmbeddedId getEmbeddedId() {
        return embeddedId;
    }

    /**
     * Sets the value of the embeddedId property.
     *
     * @param value allowed object is {@link EmbeddedId }
     *
     */
    @Override
    public void setEmbeddedId(EmbeddedId value) {
        this.embeddedId = value;
        if (value == null) {
            notifyListeners(null, "removeAttribute", null, null);
        } else {
            notifyListeners(embeddedId, "addAttribute", null, null);
            value.setAttributes(this);
        }

    }

    /**
     * Gets the value of the version property.
     *
     * <p>
     * This accessor method returns a reference to the live list, not a
     * snapshot. Therefore any modification you make to the returned list will
     * be present inside the JAXB object. This is why there is not a
     * <CODE>set</CODE> method for the version property.
     *
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getVersion().add(newItem);
     * </pre>
     *
     *
     * <p>
     * Objects of the following type(s) are allowed in the list {@link Version }
     *
     *
     * @return
     */
    @Override
    public List<Version> getVersion() {
        if (version == null) {
            version = new ArrayList<>();
        }
        return this.version;
    }

    @Override
    public List<Version> getSuperVersion() {
        List<Version> superVersion = new ArrayList();
        JavaClass currentManagedClass = getJavaClass();
        do {
            if (currentManagedClass instanceof IdentifiableClass) {
                IdentifiableClass identifiableClass = (IdentifiableClass) currentManagedClass;
                superVersion.addAll(identifiableClass.getAttributes().getVersion());
            }
            currentManagedClass = currentManagedClass.getSuperclass();
        } while (currentManagedClass != null);
        return superVersion;
    }

    @Override
    public void addVersion(Version version) {
        this.getVersion().add(version);
        notifyListeners(version, "addAttribute", null, null);
        version.setAttributes(this);
    }

    @Override
    public void removeVersion(Version version) {
        this.getVersion().remove(version);
        notifyListeners(version, "removeAttribute", null, null);
    }

    public Optional<Version> getVersion(String _id) {
        return findById(version, _id);
    }

    public Optional<Version> findVersion(String name) {
        return findByName(version, name);
    }

    @Override
    public List<Attribute> getAllAttribute(boolean includeParentClassAttibute) {
        List<Attribute> attributes = new ArrayList<>();
        if (this.getEmbeddedId() != null) {
            attributes.add(this.getEmbeddedId());
        }
        attributes.addAll(this.getId());
        attributes.addAll(super.getAllAttribute(includeParentClassAttibute));
        attributes.addAll(this.getVersion());
        return attributes;
    }

    @Override
    public XMLAttributes getAccessor(WorkSpace workSpace) {
        return getAccessor(workSpace, false);
    }

    //Remove inherit functionality , once eclipse support dynamic mapped superclass
    @Override
    public XMLAttributes getAccessor(WorkSpace workSpace, boolean inherit) {
        XMLAttributes attr = super.getAccessor(workSpace);
        attr.setIds(new ArrayList<>());
        attr.setVersions(new ArrayList<>());
        return updateAccessor(workSpace, attr, inherit);
    }

    public XMLAttributes updateAccessor(WorkSpace workSpace, XMLAttributes attr) {
        return updateAccessor(workSpace, attr, false);
    }

    @Override
    public XMLAttributes updateAccessor(WorkSpace workSpace, XMLAttributes attr, boolean inherit) {
        super.updateAccessor(workSpace, attr, inherit);
        return processAccessor(attr, inherit);
    }

    private XMLAttributes processAccessor(XMLAttributes attr, boolean inherit) {
        if (getEmbeddedId() != null) {
            attr.setEmbeddedId(EmbeddedIdSpecAccessor.getInstance(getEmbeddedId(), inherit));
        } else {
            attr.getIds().addAll(getId()
                    .stream()
                    .map(id -> IdSpecAccessor.getInstance(id, inherit))
                    .collect(toList()));
        }
        attr.getVersions().addAll(getVersion()
                .stream()
                .map(version -> VersionSpecAccessor.getInstance(version, inherit))
                .collect(toList()));

        return attr;
    }

    @Override
    public List<Attribute> getNonRelationAttributes() {
        List<Attribute> attributes = new ArrayList<>(this.getId());
        attributes.addAll(this.getBasic());
        attributes.addAll(this.getElementCollection().stream().filter(ec -> ec.getConnectedClass() == null).collect(toList()));
        attributes.addAll(this.getVersion());
        return attributes;
    }

    public boolean hasCompositePrimaryKey() {
        return this.getId().size() + (this.getEmbeddedId() != null ? 1 : 0)
                + this.getOneToOne().stream().filter(attr -> attr.isPrimaryKey()).count()
                + this.getManyToOne().stream().filter(attr -> attr.isPrimaryKey()).count() > 1;
    }

    @Override
    public Attribute getIdField() {
        List<Id> superIds = this.getSuperId();
        IdClass idClass;
        EmbeddedId superEmbeddedId;
        if (superIds.size() == 1) {
            return superIds.get(0);
        } else if ((superEmbeddedId = this.getSuperEmbeddedId()) != null) {
            return superEmbeddedId;
        } else if ((idClass = this.getSuperIdClass()) != null) {
            DefaultAttribute pkFindEntity = new DefaultAttribute();
            pkFindEntity.setName(idClass.getClazz());
            pkFindEntity.setAttributeType(idClass.getClazz());
            return pkFindEntity;
        } else { //no pk
            return null;
        }
    }

    @Override
    public List<Id> getSuperId() {
        List<Id> superIds = new ArrayList();
        JavaClass currentManagedClass = getJavaClass();
        do {
            if (currentManagedClass instanceof IdentifiableClass) {
                IdentifiableClass identifiableClass = (IdentifiableClass) currentManagedClass;
                superIds.addAll(identifiableClass.getAttributes().getId());
            }
            currentManagedClass = currentManagedClass.getSuperclass();
        } while (currentManagedClass != null);
        return superIds;
    }

    @Override
    public List<Attribute> getPrimaryKeyAttributes() {
        List<Attribute> superPrimaryKeys = new ArrayList();
        JavaClass currentManagedClass = getJavaClass();
        do {
            if (currentManagedClass instanceof IdentifiableClass) {
                IdentifiableClass identifiableClass = (IdentifiableClass) currentManagedClass;
                superPrimaryKeys.addAll(identifiableClass.getAttributes().getId());
                superPrimaryKeys.addAll(identifiableClass.getAttributes().getDerivedRelationAttributes());
            }
            currentManagedClass = currentManagedClass.getSuperclass();
        } while (currentManagedClass != null);
        return superPrimaryKeys;
    }

    public EmbeddedId getSuperEmbeddedId() {
        JavaClass currentManagedClass = getJavaClass();
        do {
            if (currentManagedClass instanceof IdentifiableClass) {
                IdentifiableClass identifiableClass = (IdentifiableClass) currentManagedClass;
                if (identifiableClass.getAttributes().getEmbeddedId() != null) {
                    return identifiableClass.getAttributes().getEmbeddedId();
                }
            }
            currentManagedClass = currentManagedClass.getSuperclass();
        } while (currentManagedClass != null);
        return null;
    }

    public IdClass getSuperIdClass() {
        JavaClass currentManagedClass = getJavaClass();
        do {
            if (currentManagedClass instanceof IdentifiableClass) {
                IdentifiableClass identifiableClass = (IdentifiableClass) currentManagedClass;
                if (identifiableClass.getIdClass() != null) {
                    return identifiableClass.getIdClass();
                }
            }
            currentManagedClass = currentManagedClass.getSuperclass();
        } while (currentManagedClass != null);
        return null;
    }

    @Override
    public Set<String> getConnectedClass() {
        Set<String> javaClasses = new HashSet<>(super.getConnectedClass());
        getCompositeKeyConnectedClass().ifPresent(jc -> javaClasses.add(jc));
        return javaClasses;
    }

    public Optional<String> getCompositeKeyConnectedClass() {
        List<Id> superIds = this.getSuperId();
        if (superIds.size() > 1) {
            EmbeddedId superEmbeddedId = this.getSuperEmbeddedId();
            if (superEmbeddedId != null) {
                return Optional.of(superEmbeddedId.getConnectedClass().getFQN());
            } else {
                IdClass idClass = this.getSuperIdClass();
                return Optional.of(getJavaClass().getRootPackage() + '.' + idClass.getClazz());
            }
        }
        return Optional.empty();
    }

}
