package me.chyxion.tigon.shiro;

import org.slf4j.Logger;
import java.util.HashSet;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.realm.AuthorizingRealm;
import me.chyxion.tigon.shiro.model.AuthInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authc.AuthenticationInfo;
import me.chyxion.tigon.shiro.model.Credential;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import me.chyxion.tigon.shiro.model.SerializableByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**
 * @version 0.0.1
 * @since 0.0.1
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Apr 7, 2015 3:19:12 PM
 */
public class AuthorizingRealmSupport extends AuthorizingRealm {
    private static final Logger log = 
        LoggerFactory.getLogger(AuthorizingRealmSupport.class);

    @Autowired(required = false)
    private AuthRealm authRealm;

    public AuthorizingRealmSupport() {
        setCredentialsMatcher(new SimpleCredentialsMatcher() {
            @Override
            public boolean doCredentialsMatch(
                AuthenticationToken authcToken, AuthenticationInfo info) {
                AuthInfo authInfo = (AuthInfo) info;
                return authRealm.credentialMatch(
                    authcToken.getCredentials(), 
                    new Credential(authInfo.getCredentials(), 
                            authInfo.getCredentialsSalt(),
                            authInfo.getExtra()));
            }
        });
    }

    /**
     * Login
     * {@inheritDoc}
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authToken) 
            throws AuthenticationException {
        Object principal = authToken.getPrincipal();
        Assert.state(principal != null, "Null Username Is Not Allowed");
        log.info("Login [{}].", principal);

        Credential credential = authRealm.credential(principal, authToken);
        if (credential == null) {
            throw new UnknownAccountException(
                "No Account [" + principal + "] Found");
        }
        return new AuthInfo(principal, credential.getCredential(), 
                    new SerializableByteSource(
                        ByteSource.Util.bytes(credential.getSalt())),
                    getName()).setExtra(credential.getExtra());
    }

    /**
     * Get Permissions
     * {@inheritDoc}
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Assert.state(principals != null, "Principals Could Not Be Null");
        Object principal = getAvailablePrincipal(principals);
        SimpleAuthorizationInfo authInfo = null;
        if (principal != null) {
             authInfo = new SimpleAuthorizationInfo(
                    new HashSet<String>(authRealm.roles(principal)));
             authInfo.addStringPermissions(authRealm.permissions(principal));
        }
        return authInfo;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        return getAvailablePrincipal(principals);
    }
}
