package de.hsadmin.mods.user;

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

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import de.hsadmin.core.model.AbstractEntity;
import de.hsadmin.core.model.EntityInfo;
import de.hsadmin.core.model.FieldValidation;
import de.hsadmin.core.util.Config;
import de.hsadmin.mods.pac.Pac;

@Entity(name = "UnixUsers")
@Table(name = "unixuser")
@SequenceGenerator(name = "UnixUsersSeqGen", sequenceName = "unixuser_unixuser_id_seq")
@EntityInfo(name = "UNIX-Zugang/Mailbox")
public class UnixUser extends AbstractEntity implements Serializable {
	
	private static final long serialVersionUID = 7823071611805642906L;

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

	// attribute userid - really NOT unique!
	@Column(name = "userid", columnDefinition = "integer", nullable = false, updatable=false)
	private long userId;

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

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

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

	@FieldValidation("[a-zA-Z0-9\\_\\-\\.\\,\\ ]*")
	@Column(name = "comment", columnDefinition = "character varying(128)")
	private String comment;

	@FieldValidation("[a-z\\/]*")
	@Column(name = "shell", columnDefinition = "character varying(32)")
	private String shell;

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

	@Column(name = "locked", columnDefinition = "boolean")
	private boolean locked;

	@Column(name = "quota_softlimit", columnDefinition = "integer")
	private Integer quotaSoftlimit;

	@Column(name = "quota_hardlimit", columnDefinition = "integer")
	private Integer quotaHardlimit;

	public UnixUser() {
	}

	public UnixUser(Pac pac, String name, int userId, String comment,
			String homeDir, String shell) {
		this.pac = pac;
		this.name = name;
		this.userId = userId;
		this.comment = comment;
		this.shell = shell;
		init();
	}

	/**
	 *  fixes some fields if null
	 */
	public void init() {
		if (comment == null) {
			comment = name;
		}
		if (homedir == null) {
			this.homedir = getDefaultHomedir();
		}
		if (shell == null) {
			shell = "/bin/false";
		}
		if (quotaSoftlimit == null) {
			quotaSoftlimit = new Integer(0);
		}
		if (quotaHardlimit == null) {
			quotaHardlimit = new Integer(0);
		}
	}

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

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

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

	public long getId() {
		return id;
	}

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

	public long getUserId() {
		return userId;
	}

	public void setUserId(long userId) {
		this.userId = userId;
	}

	public String getName() {
		return name;
	}

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

	public String getPassword() {
		// TODO: should NOT be an external property
		return password;
	} 
	public void setPassword(String password) {
		this.password = password;
	}

	public Pac getPac() {
		return pac;
	}

	public void setPac(Pac pac) {
		this.pac = pac;
		if (pac.getUnixUser() != null) {
			pac.getUnixUser().add(this);
		}
	}

	public String getComment() {
		return comment;
	}

	public void setComment(String comment) {
		this.comment = comment;
	}

	public String getShell() {
		return shell;
	}

	public void setShell(String shell) {
		this.shell = shell;
	}

	public String getHomedir() {
		return homedir;
	}

	public void setHomedir(String homedir) {
		this.homedir = homedir;
	}

	/**
	 * returns true if the user has the default homedir path
	 */
	public boolean isDefaultHomedir() {
		return getHomedir().equals(getDefaultHomedir());
	}

	/**
	 * returns the default homedir path
	 */
	private String getDefaultHomedir() {
		String pacName = pac.getName();
		if (name.equals(pacName))
			return "/home/pacs/" + pacName;
		else
			return "/home/pacs/" + pacName + "/users/"
					+ name.substring(pacName.length() + 1);
	}

	public boolean isLocked() {
		return locked;
	}

	public void setLocked(boolean locked) {
		this.locked = locked;
	}

	public Integer getQuotaSoftlimit() {
		return quotaSoftlimit;
	}

	public void setQuotaSoftlimit(Integer quota) {
		this.quotaSoftlimit = quota;
	}

	public void setQuotaHardlimit(Integer quotaLimit) {
		this.quotaHardlimit = quotaLimit;
	}

	public Integer getQuotaHardlimit() {
		return quotaHardlimit;
	}

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

	@Override
	public void initialize(EntityManager em, UnixUser loginUser) {
		pac = loginUser.getPac(); // a default useful for the pac admin
		// TODO should not be hardcoded, but how?
		homedir = "/home/pacs/" + pac.getName() + "/users/..."; 
	}

	@Override
	public UnixUser merge(EntityManager em, UnixUser loginUser) {
		if (homedir == null)
			homedir = "/home/pacs/" + pac.getName() + "/users/"
					+ getName().substring(6); // TODO: Hack

		UnixUser dbEntity = (UnixUser) super.merge(em, loginUser);
		dbEntity.setPassword(this.getPassword()); // because this is not in database
		return dbEntity;
	}

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

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

	/**
	 * determines whether this user account is a hostmaster account
	 */
	public boolean hasHostmasterRole() {
		// TODO: hardcoded Hostsharing conventions
		String login = getName();
		return login.length() == 2 || ((login.startsWith(Config.getInstance().getProperty("accountprefix.hostmaster", "hsh01") + "-") && login.length() == 8));
	}

	/**
	 * determines whether this user account has rights on the given customer
	 */
	public boolean hasCustomerRoleFor(de.hsadmin.mods.cust.Customer cust) {
		// TODO: hardcoded Hostsharing conventions
		return getName().equals(cust.getName()) || hasHostmasterRole();
	}

	/**
	 * determines whether this user account has admin rights on the given pac
	 */
	public boolean hasPacAdminRoleFor(de.hsadmin.mods.pac.Pac pac) {
		// TODO: hardcoded Hostsharing conventions
		return pac.getName().equals(getName())
				|| hasCustomerRoleFor(pac.getCustomer());
	}

	/**
	 * determines whether this user account has admin rights on the given dom
	 */
//	public boolean hasDomAdminRoleFor(de.hsadmin.mods.dom.Domain dom) {
//		// TODO: hardcoded Hostsharing conventions
//		return this.getId() == dom.getUser().getId()
//				|| hasPacAdminRoleFor(dom.getUser().getPac());
//	}

	@Override
	public boolean isWriteAllowedFor(UnixUser loginUser) {
		String pacName = pac.getName();
		if (!name.equals(pacName) && !name.startsWith(pacName + "-"))
			return false;
		if (super.isWriteAllowedFor(loginUser))
			return true;
		return this.getId() == loginUser.getId() || loginUser.hasPacAdminRoleFor(getPac());
	}

	@Override
	public boolean isReadAllowedFor(UnixUser loginUser) {
		if (super.isReadAllowedFor(loginUser))
			return true;
		return this.getId() == loginUser.getId() || loginUser.hasPacAdminRoleFor(getPac());
	}

	/**
	 * JPA-Query restriction for these entities
	 */
	public static String restriction() {
		return
			// customers can access all users in their pacs
			"obj.pac.customer.name = :loginUserName OR " 
			// packet admins can access all users in their pacs
			 + "obj.pac.name = :loginUserName OR "
			// all users can access their own account
			 + "obj.name = :loginUserName";
	}
}
