package play.modules.cream;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;

import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.core.TransientRepository;
import org.jcrom.Jcrom;
import org.jcrom.annotations.JcrNode;

import play.Invoker.InvocationContext;
import play.Logger;
import play.Play;
import play.PlayPlugin;
import play.classloading.ApplicationClasses.ApplicationClass;
import play.exceptions.UnexpectedException;
import play.modules.cream.annotations.JcrNoSession;
import play.modules.cream.annotations.JcrSession;
import play.modules.cream.helpers.JcrRepositoryCreationHelper;
import play.modules.cream.helpers.JcrRepositoryHelper;
import play.modules.cream.ocm.JcrMapper;

/**
 * Cream: JCR Plugin for Play! Framework
 * 
 */
public class JcrPlugin extends PlayPlugin {
    @Override
    public void afterInvocation() {
        JCR.closeSession();
    }

    @Override
    public void beforeInvocation() {
        InvocationContext current = InvocationContext.current();
        if (!current.isAnnotationPresent(JcrNoSession.class)) {
            Session currentSession = createCurrentSession(current);
            JCR.addSession(currentSession);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object bind(String name, Class clazz, java.lang.reflect.Type type, Annotation[] annotations,
            Map<String, String[]> params) {

        if (Model.class.isAssignableFrom(clazz)) {
            String keyName = Model.Manager.factoryFor(clazz).keyName();
            String idKey = name + "." + keyName;
            if (params.containsKey(idKey) && params.get(idKey).length > 0 && params.get(idKey)[0] != null
                    && params.get(idKey)[0].trim().length() > 0) {
                String id = params.get(idKey)[0];
                try {
                    Model o = (Model) JcrMapper.loadByUUID(clazz, id);
                    if (o != null) {
                        return Model.edit(o, name, params, annotations);
                    }
                } catch (Exception e) {
                    throw new UnexpectedException(e);
                }
            }
            return Model.create(clazz, name, params, annotations);
        }
        return super.bind(name, clazz, type, annotations, params);
    }

    @Override
    public Object bind(String name, Object o, Map<String, String[]> params) {
        if (o instanceof Model) {
            return Model.edit(o, name, params, null);
        }
        return null;
    }

    @Override
    public void enhance(ApplicationClass applicationClass) throws Exception {
        new JcrEnhancer().enhanceThisClass(applicationClass);
    }

    @Override
    public String getStatus() {
        StringWriter sw = new StringWriter();
        PrintWriter out = new PrintWriter(sw);

        out.println("Jcr Repository:");
        out.println("~~~~~~~~~~~~~~");

        if (JcrRepositoryHelper.repository == null) {
            out.println("(not yet initialized)");
            return sw.toString();
        }

        out.println("name: " + JcrRepositoryHelper.repository.getDescriptor(Repository.REP_NAME_DESC));
        out.println("version: " + JcrRepositoryHelper.repository.getDescriptor(Repository.REP_VERSION_DESC));
        out.println("default workspace: " + JcrRepositoryHelper.defaultWorkspace);

        out.println("\nJcr Mapped Classes:");
        out.println("~~~~~~~~~~~~~~~~~~");
        for (Class clazz : JcrMapper.jcrom.getMappedClasses()) {
            out.println(clazz.getName());
        }

        return sw.toString();
    }

    @Override
    public void invocationFinally() {
        JCR.closeSession();
    }

    @Override
    public Model.Factory modelFactory(Class<? extends play.db.Model> modelClass) {
        return (modelClass.isAnnotationPresent(JcrNode.class)) ? new JcrModelLoader(modelClass) : null;
    }

    @Override
    public void onApplicationStart() {
        mapJcrEntities();
        setUpJcrRepository();
    }

    @Override
    public void onApplicationStop() {
        JcrRepositoryHelper.shutdown();
    }

    @Override
    public void onInvocationException(Throwable e) {
        JCR.closeSession();
    }

    private Session createCurrentSession(InvocationContext current) {
        Session currentSession;
        JcrSession jcrConfig = current.getAnnotation(JcrSession.class);
        if (jcrConfig != null && StringUtils.isNotBlank(jcrConfig.workspace())) {
            currentSession = JcrRepositoryHelper.openSession(jcrConfig.workspace());
        } else {
            currentSession = JcrRepositoryHelper.openSession();
        }

        postEvent(JcrPlugin.class.getSimpleName() + ".JcrSessionCreated", currentSession);

        return currentSession;
    }

    private void mapJcrEntities() {
        Jcrom jcrom = new Jcrom();

        List<Class> jcrClasses = Play.classloader.getAnnotatedClasses(JcrNode.class);
        for (Class jcrClass : jcrClasses) {
            Logger.trace("Jcrom mapping of %s", jcrClass.getName());
            jcrom.map(jcrClass);
        }

        JcrMetadata jcrMetadata = JcrMetadata.getInstance();
        jcrMetadata.intialize(jcrom);

        JcrMapper.jcrom = jcrom;
    }

    private void setUpJcrRepository() {
        Repository repository;
        Properties p = Play.configuration;
        String username = p.getProperty("cream.jcr.username", "anonymous");
        String password = p.getProperty("cream.jcr.password", "");
        String workspace = p.getProperty("cream.jcr.workspace", "default");
        String repoConfiguration = p.getProperty("cream.jcr.configuration", "/play/modules/cream/repository.xml");

        if ("transient".equals(p.getProperty("cream.jcr.mode"))) {
            repository = new TransientRepository();
        } else {
            String url = p.getProperty("cream.jcr.url", "");
            repository = JcrRepositoryCreationHelper.createRepository(url, repoConfiguration);
        }

        JcrRepositoryHelper.repository = repository;
        JcrRepositoryHelper.defaultCredentials = new SimpleCredentials(username, password.toCharArray());
        JcrRepositoryHelper.defaultWorkspace = workspace;
    }
}
