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 de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.model.AbstractModuleImpl;
import de.hsadmin.core.model.FieldValidation;
import de.hsadmin.core.model.SearchFilter;
import de.hsadmin.mods.pac.Pac;
import de.hsadmin.mods.user.UnixUser;

@Entity
@Table(name = "database")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="engine", discriminatorType=DiscriminatorType.STRING)
@SequenceGenerator(name = "DatabaseSeqGen", sequenceName = "database_database_id_seq")
@SearchFilter("obj.pac = :loginUserPac OR obj.pac.customer.name = :loginUserName")
public abstract class Database extends AbstractEntity implements Serializable {
	
	private static final long serialVersionUID = 6243815106074846080L;

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

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

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

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

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

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

	protected Database() {
		encoding = "UTF-8";
	}

	protected Database(String instance, Pac pac, String name, String owner,
			String encoding) {
		this.instance = instance;
		this.pac = pac;
		this.name = name;
		this.owner = owner;
		this.encoding = encoding;
	}

	@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) {
			if (name.length() < 7 || name.charAt(5) != '_') {
				throw new SecurityException("database name '" + name
						+ "' not allowed");
			}
			// 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)
				Entity entityAnnot = Pac.class.getAnnotation(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");
			}
		}
	}

	public static String createQueryFromStringKey(String humanKey) {
		return "name='" + humanKey + "'";
	}

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

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

	/**
	 * returns the encoding in the style of the specific database
	 */
	public abstract String getSystemEncoding();

	@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(pac);
	}

	public abstract Class<? extends DatabaseUser> getSqlUserClass();

	@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 getOwner() {
		return owner;
	}

	public void setOwner(String owner) {
		this.owner = owner;
	}

	public Pac getPac() {
		return pac;
	}

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

	public String getEncoding() {
		return encoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * 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";
	}
}
