/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package relations;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;


/**
 * An entity that contains relations corresponding to family tree relations.
 * This entity demonstrates the following JPA features:
 *
 * 1. Enum fields (gender)
 * 2. @OneToOne relations
 * 3. @OneToMany relations
 * 4. Named queries
 */
@Entity
@NamedQueries({

    // a sibling shares a mother and a father
    @NamedQuery(name="siblings", query="select distinct sibling1 "
        + "from Deity sibling1, Deity sibling2 where "
        + "sibling1.father = sibling2.father "
        + "and sibling1.mother = sibling2.mother "
        + "and sibling2 = ?1 and sibling1 <> ?1"),

    // a half-siling shares a mother or a father, but not both
    @NamedQuery(name="half-siblings", query="select distinct sibling1 "
        + "from Deity sibling1, Deity sibling2 where "
        + "((sibling1.father = sibling2.father "
        + "and sibling1.mother <> sibling2.mother) "
        + "or (sibling1.father <> sibling2.father "
        + "and sibling1.mother = sibling2.mother)) "
        + "and sibling2 = ?1 and sibling1 <> ?1"),

    // a cousin shares a grandparent, but is not a sibling
    @NamedQuery(name="cousins", query="select distinct cousin1 "
        + "from Deity cousin1, Deity cousin2 where "
        + "("
            + "cousin1.father.father = cousin2.father.father "
            + "or cousin1.father.mother = cousin2.father.mother "
            + "or cousin1.mother.father = cousin2.mother.father "
            + "or cousin1.mother.mother = cousin2.mother.mother) "
        + "and (cousin1.father <> cousin2.father) "
        + "and (cousin1.mother <> cousin2.mother) "
        + "and cousin2 = ?1 and cousin1 <> ?1")
    })
public class Deity implements Serializable {
    
    private static final long serialVersionUID = 1L;

    // the Id is the name, which is generally a bad idea, but we are
    // confident that diety names will be unique
    @Id
    private String name;

    @Basic @Enumerated(EnumType.STRING)
    private Gender gender;

    @OneToOne(cascade=CascadeType.ALL)
    private Deity mother;

    @OneToOne(cascade=CascadeType.ALL)
    private Deity father;

    @OneToMany(cascade=CascadeType.ALL)
    private Set<Deity> children;

    public static enum Gender { MALE, FEMALE }


    public Deity(String name, Gender gender) {
        this.name = name;
        this.gender = gender;
    }


    //////////////////////////
    // Business methods follow
    //////////////////////////

    /**
     * She's having a baby...
     *
     * @param  childName  the baby name
     * @return the new child
     *
     * @throws IllegalArgumentException if the person is not a woman, or
     *                                  if the person is unmarried (illegitimate
     *                                  children are not yet supported)
     */
    public Deity giveBirth(String childName, Deity childFather, Gender gender) {
        if (this.gender != Gender.FEMALE)
            throw new IllegalArgumentException("Only women can have children!");

        if (childName == null)
            throw new IllegalArgumentException("No child name!");

        // create the child
        Deity child = new Deity(childName, gender);

        // set the parents in the children...
        child.mother = this;

        // add the child to this member's children
        if (children == null)
            children = new HashSet<>();
        children.add(child);

        if (childFather != null) {
            child.father = childFather;
            if (childFather.children == null)
                childFather.children = new HashSet<>();
            childFather.children.add(child);
        }

        return child;
    }


    ////////////////////////////////////
    // Property accessor methods follow
    ////////////////////////////////////


    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }


    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Gender getGender() {
        return this.gender;
    }


    public void setMother(Deity mother) {
        this.mother = mother;
    }

    public Deity getMother() {
        return this.mother;
    }


    public void setFather(Deity father) {
        this.father = father;
    }

    public Deity getFather() {
        return this.father;
    }


    public void setChildren(Set<Deity> children) {
        this.children = children;
    }

    public Set<Deity> getChildren() {
        return this.children;
    }

}

