package de.hsadmin.mods.db;

import static javax.persistence.FetchType.EAGER;
import static javax.persistence.GenerationType.SEQUENCE;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.model.AbstractModuleImpl;
import de.hsadmin.core.model.FieldValidation;
import de.hsadmin.mods.pac.Pac;
import de.hsadmin.mods.user.UnixUser;

@Entity
@Table(name = "database_user")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="engine", discriminatorType=DiscriminatorType.STRING)
@SequenceGenerator(name = "DatabaseUserSeqGen", sequenceName = "dbuser_dbuser_id_seq")
public abstract class DatabaseUser extends AbstractEntity implements Serializable {
	
	private static final long serialVersionUID = -4840133372566213014L;

	@Id
	@GeneratedValue(strategy = SEQUENCE, generator = "DatabaseUserSeqGen")
	@Column(name = "dbuser_id", columnDefinition = "integer", updatable=false, insertable=false)
	private long id;

	@FieldValidation("[a-z0-9\\_]*")
	@Column(name = "name", columnDefinition = "character varying(24)", updatable=false)
	private String name;

	@FieldValidation("[^']*")
	@Transient
	private String password;

	@FieldValidation("[a-zA-Z]*")
	@Column(name = "engine", columnDefinition = "character varying(12)", updatable=false)
	protected String instance;

	@JoinColumn(name = "packet_id", columnDefinition = "integer", updatable=false)
	@ManyToOne(fetch = EAGER)
	protected Pac pac;

	protected DatabaseUser() {
	}

	protected DatabaseUser(String instance, Pac pac, String name,
			String password) {
		this.instance = instance;
		this.pac = pac;
		this.name = name;
		this.password = password;
	}

	@Override
	public void initialize(EntityManager em, UnixUser loginUser) {
		pac = loginUser.getPac(); // a default useful for the pac admin
	}

	public void complete(EntityManager em, UnixUser loginUser) {
		if (pac == null && name != null && name.length() > 0) {
			// TODO: it's ugly having this code here, needs refactoring
			String pacName = name.substring(0, 5);
			try {
				// get the entities name (query part from FROM to WHERE)
				javax.persistence.Entity entityAnnot = Pac.class
						.getAnnotation(javax.persistence.Entity.class);
				String queryString = "SELECT obj FROM " + entityAnnot.name() + " obj WHERE "
						+ Pac.createQueryFromStringKey(pacName);

				// set parameters
				Query query = em.createQuery(queryString);
				AbstractModuleImpl.setQueryParameter(query, queryString,
						"loginUser", loginUser);
				AbstractModuleImpl.setQueryParameter(query, queryString,
						"loginUserName", loginUser.getName());
				AbstractModuleImpl.setQueryParameter(query, queryString,
						"loginUserPac", loginUser.getPac());
				pac = (Pac) query.getSingleResult();
			} catch (NoResultException exc) {
				throw new SecurityException("packet '" + pacName
						+ "' not found or access denied");
			}
		}
	}

	@Override
	public String createStringKey() {
		return getName();
	}

	@Override
	public long id() {
		return id;
	}

	public long getId() {
		return id;
	}

	protected void setId(long id) {
		this.id = id;
	}

	public String getInstance() {
		return instance;
	}

	public void setInstance(String instance) {
		this.instance = instance;
	}

	public String getName() {
		return name;
	}

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

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Pac getPac() {
		return pac;
	}

	public void setPac(Pac pac) {
		this.pac = pac;
	}

	@Override
	public boolean isNew() {
		return id == 0;
	}

	@Override
	public DatabaseUser merge(EntityManager em, UnixUser loginUser) {
		DatabaseUser dbEntity = (DatabaseUser) super.merge(em, loginUser);
		dbEntity.setPassword(this.getPassword());
		return dbEntity;
	}

	@Override
	public String getHiveName() {
		if (isNew())
			return null;
		else
			return getPac().getHiveName();
	}

	@Override
	public UnixUser owningUser(EntityManager em) {
		return getPac().owningUser(em);
	}

	/**
	 * determines whether the given user has full read access on all merged
	 * fields of this entity
	 */
	@Override
	public boolean isReadAllowedFor(UnixUser loginUser) {
		return loginUser.hasPacAdminRoleFor(getPac());
	}

	/**
	 * determines whether the given user has full write access on all merged
	 * fields of this entity
	 */
	@Override
	public boolean isWriteAllowedFor(UnixUser loginUser) {
		String pacName = pac.getName();
		if (!name.equals(pacName) && !name.startsWith(pacName + "_"))
			return false;
		return loginUser.hasPacAdminRoleFor(getPac());
	}

	/**
	 * query restriction for access control
	 */
	public static String restriction() {
		return
			// all databases of all pacs of customer
			"obj.pac.customer.name=:loginUserName OR " +
			// 	all aliases of packet admin
			"obj.pac.name=:loginUserName";
	}
}
