/*
 * NOTE: This copyright does *not* cover user programs that use HQ
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 * "derived work".
 *
 * Copyright (C) [2004-2009], Hyperic, Inc.
 * This file is part of HQ.
 *
 * HQ is free software; you can redistribute it and/or modify
 * it under the terms version 2 of the GNU General Public License as
 * published by the Free Software Foundation. This program is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

package org.hyperic.hq.ui.server.session;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.security.auth.login.LoginException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.appdef.shared.AppdefEntityID;
import org.hyperic.hq.auth.shared.SessionManager;
import org.hyperic.hq.auth.shared.SessionNotFoundException;
import org.hyperic.hq.auth.shared.SessionTimeoutException;
import org.hyperic.hq.authz.server.session.AuthzApplicationEvent;
import org.hyperic.hq.authz.server.session.AuthzSubject;
import org.hyperic.hq.authz.server.session.Role;
import org.hyperic.hq.authz.server.session.RoleCreatedEvent;
import org.hyperic.hq.authz.server.session.RoleDeleteRequestedEvent;
import org.hyperic.hq.authz.server.session.RoleRemoveFromSubjectRequestedEvent;
import org.hyperic.hq.authz.server.session.SubjectDeleteRequestedEvent;
import org.hyperic.hq.authz.shared.AuthzConstants;
import org.hyperic.hq.authz.shared.AuthzSubjectManager;
import org.hyperic.hq.authz.shared.PermissionException;
import org.hyperic.hq.authz.shared.PermissionManager;
import org.hyperic.hq.authz.shared.PermissionManagerFactory;
import org.hyperic.hq.bizapp.shared.AuthzBoss;
import org.hyperic.hq.common.server.session.Crispo;
import org.hyperic.hq.common.server.session.CrispoOption;
import org.hyperic.hq.common.shared.CrispoManager;
import org.hyperic.hq.ui.Constants;
import org.hyperic.hq.ui.Dashboard;
import org.hyperic.hq.ui.WebUser;
import org.hyperic.hq.ui.shared.DashboardManager;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 */
@Service
@Transactional
public class DashboardManagerImpl implements DashboardManager, ApplicationListener<AuthzApplicationEvent> {

    private Log log = LogFactory.getLog(DashboardManagerImpl.class);

    protected SessionManager sessionManager = SessionManager.getInstance();

    private DashboardConfigDAO dashDao;
    private CrispoManager crispoManager;
    private AuthzSubjectManager authzSubjectManager;
   

    @Autowired
    public DashboardManagerImpl(DashboardConfigDAO dashDao, CrispoManager crispoManager,
                                AuthzSubjectManager authzSubjectManager) {
        this.dashDao = dashDao;
        this.crispoManager = crispoManager;
        this.authzSubjectManager = authzSubjectManager;
    }

    /**
     */
    @Transactional(readOnly = true)
    public UserDashboardConfig getUserDashboard(AuthzSubject me, AuthzSubject user) throws PermissionException {
        PermissionManager permMan = PermissionManagerFactory.getInstance();

        if (!me.equals(user) && !permMan.hasAdminPermission(me.getId())) {
            throw new PermissionException("You are unauthorized to see this " + "dashboard");
        }

        return dashDao.findDashboard(user);
    }

    /**
     */
    @Transactional(readOnly = true)
    public RoleDashboardConfig getRoleDashboard(AuthzSubject me, Role r) throws PermissionException {
        PermissionManager permMan = PermissionManagerFactory.getInstance();

        permMan.check(me.getId(), r.getResource().getResourceType(), r.getId(), AuthzConstants.roleOpModifyRole);

        return dashDao.findDashboard(r);
    }

    private ConfigResponse getDefaultConfig() {
        return new ConfigResponse();
    }

    /**
     */
    public UserDashboardConfig createUserDashboard(AuthzSubject me, AuthzSubject user, String name)
        throws PermissionException {
        PermissionManager permMan = PermissionManagerFactory.getInstance();

        if (!me.equals(user) && !permMan.hasAdminPermission(me.getId())) {
            throw new PermissionException("You are unauthorized to create " + "this dashboard");
        }

        Crispo cfg = crispoManager.create(getDefaultConfig());
        UserDashboardConfig dash = new UserDashboardConfig(user, name, cfg);
        dashDao.save(dash);
        return dash;
    }

    /**
     */
    public RoleDashboardConfig createRoleDashboard(AuthzSubject me, Role r, String name) throws PermissionException {
        PermissionManager permMan = PermissionManagerFactory.getInstance();

        permMan.check(me.getId(), r.getResource().getResourceType(), r.getId(), AuthzConstants.roleOpModifyRole);

        Crispo cfg = crispoManager.create(getDefaultConfig());
        RoleDashboardConfig dash = new RoleDashboardConfig(r, name, cfg);
        dashDao.save(dash);
        return dash;
    }

    /**
     * Reconfigure a user's dashboard
     */
    public void configureDashboard(AuthzSubject me, DashboardConfig cfg, ConfigResponse newCfg)
        throws PermissionException {
        if (!isEditable(me, cfg)) {
            throw new PermissionException("You are unauthorized to modify " + "this dashboard");
        }
        crispoManager.update(cfg.getCrispo(), newCfg);
    }

    /**
     */
    public void renameDashboard(AuthzSubject me, DashboardConfig cfg, String name) throws PermissionException {
        if (!isEditable(me, cfg)) {
            throw new PermissionException("You are unauthorized to modify " + "this dashboard");
        }
        cfg.setName(name);
    }

    /**
     * Determine if a dashboard is editable by the passed user
     */
    @Transactional(readOnly = true)
    public boolean isEditable(AuthzSubject me, DashboardConfig dash) {
        PermissionManager permMan = PermissionManagerFactory.getInstance();

        if (permMan.hasAdminPermission(me.getId()))
            return true;

        return dash.isEditable(me);
    }

    /**
     */
    @Transactional(readOnly = true)
    public Collection<DashboardConfig> getDashboards(AuthzSubject me) throws PermissionException {
        Collection<DashboardConfig> res = new ArrayList<DashboardConfig>();

        PermissionManager permMan = PermissionManagerFactory.getInstance();
        if (permMan.hasGuestRole() && permMan.hasAdminPermission(me.getId())) {
            res.addAll(dashDao.findAllRoleDashboards());
            res.add(getUserDashboard(me, me));
            return res;
        }

        UserDashboardConfig cfg = getUserDashboard(me, me);
        if (cfg != null)
            res.add(cfg);

        if (permMan.hasGuestRole())
            res.addAll(dashDao.findRolesFor(me));

        return res;
    }

    /**
     * Update dashboard and user configs to account for resource deletion
     * 
     * @param ids An array of ID's of removed resources
     */
    public void handleResourceDelete(AppdefEntityID[] ids) {
        for (int i = 0; i < ids.length; i++) {
            String appdefKey = ids[i].getAppdefKey();
            List<CrispoOption> copts = crispoManager.findOptionByValue(appdefKey);

            for (CrispoOption o : copts) {
                String val = o.getValue();
                String newVal = removeResource(val, appdefKey);

                if (!val.equals(newVal)) {
                    crispoManager.updateOption(o, newVal);
                    log.debug("Update option key=" + o.getKey() + " old =" + val + " new =" + newVal);
                }
            }
        }
    }

    /**
     */
    @Transactional(readOnly = true)
    public ConfigResponse getRssUserPreferences(String user, String token) throws LoginException {
        ConfigResponse preferences;
        try {
            AuthzSubject me = authzSubjectManager.findSubjectByName(user);
            preferences = getUserDashboard(me, me).getConfig();
        } catch (Exception e) {
            throw new LoginException("Username has no preferences");
        }

        // Let's make sure that the rss auth token matches
        String prefToken = preferences.getValue(Constants.RSS_TOKEN);
        if (token == null || !token.equals(prefToken))
            throw new LoginException("Username and Auth token do not match");

        return preferences;
    }

    private String removeResource(String val, String resource) {
        val = StringUtil.remove(val, resource);
        val = StringUtil.replace(val, Constants.EMPTY_DELIMITER, Constants.DASHBOARD_DELIMITER);
        return val;
    }
    
    public void onApplicationEvent(AuthzApplicationEvent event) {
        if(event instanceof SubjectDeleteRequestedEvent) {
            dashDao.handleSubjectRemoval(((SubjectDeleteRequestedEvent)event).getSubject());
        }else if(event instanceof RoleDeleteRequestedEvent) {
            roleRemoved(((RoleDeleteRequestedEvent)event).getRole());
        }else if(event instanceof RoleCreatedEvent) {
            roleCreated(((RoleCreatedEvent)event).getRole());
        }else if(event instanceof RoleRemoveFromSubjectRequestedEvent) {
            roleRemovedFromSubject(((RoleRemoveFromSubjectRequestedEvent)event).getRole(), ((RoleRemoveFromSubjectRequestedEvent)event).getSubject());
        }
    }

    private void roleRemoved(Role role) {
        RoleDashboardConfig cfg = dashDao.findDashboard(role);
        if (cfg == null) {
            return;
        }
        List<CrispoOption> opts = crispoManager.findOptionByKey(Constants.DEFAULT_DASHBOARD_ID);
        for (CrispoOption opt : opts) {
            if (Integer.valueOf(opt.getValue()).equals(cfg.getId())) {
                crispoManager.updateOption(opt, null);
            }
        }
        dashDao.handleRoleRemoval(role);
    }
    
    private void roleCreated(Role role) {
        Crispo cfg = crispoManager.create(getDefaultConfig());
        RoleDashboardConfig dash = new RoleDashboardConfig(role, role.getName() + " Role Dashboard", cfg);
        dashDao.save(dash);
    }
    
    private void roleRemovedFromSubject(Role r, AuthzSubject from) {
        RoleDashboardConfig cfg = dashDao.findDashboard(r);
        Crispo c = from.getPrefs();
        if (c != null) {
            for (CrispoOption opt : c.getOptions()) {
                if (opt.getKey().equals(Constants.DEFAULT_DASHBOARD_ID) &&
                    Integer.valueOf(opt.getValue()).equals(cfg.getId())) {
                    crispoManager.updateOption(opt, null);
                    break;
                }
            }
        }
    }

    @Transactional(readOnly = true)
    public List<DashboardConfig> findEditableDashboardConfigs(WebUser user, AuthzBoss boss)
        throws SessionNotFoundException, SessionTimeoutException, PermissionException, RemoteException {
        AuthzSubject me = boss.findSubjectById(user.getSessionId(), user.getSubject().getId());

        Collection<DashboardConfig> dashboardCollection = getDashboards(me);
        List<DashboardConfig> editableDashboardConfigs = new ArrayList<DashboardConfig>();

        for (DashboardConfig config : dashboardCollection) {
            if (isEditable(me, config)) {
                editableDashboardConfigs.add(config);
            }
        }

        return editableDashboardConfigs;
    }

    @Transactional(readOnly = true)
    public List<Dashboard> findEditableDashboards(WebUser user, AuthzBoss boss) throws SessionNotFoundException,
        SessionTimeoutException, PermissionException, RemoteException {
        List<DashboardConfig> dashboardConfigs = findEditableDashboardConfigs(user, boss);
        List<Dashboard> editableDashboards = new ArrayList<Dashboard>();

        for (DashboardConfig config : dashboardConfigs) {
            Dashboard dashboard = new Dashboard();

            dashboard.set_name(config.getName());
            dashboard.setId(config.getId());

            editableDashboards.add(dashboard);
        }

        return editableDashboards;
    }

    /**
     * Find a given dashboard by its id
     * @param id the id of the dashboard
     * @param user current user
     * @param boss the authzboss
     * @return the DashboardConfig of the corresponding DashboardId or null if
     *         none
     */
    @Transactional(readOnly = true)
    public DashboardConfig findDashboard(Integer id, WebUser user, AuthzBoss boss) {
        Collection<DashboardConfig> dashboardCollection;

        try {
            AuthzSubject me = boss.findSubjectById(user.getSessionId(), user.getSubject().getId());
            dashboardCollection = getDashboards(me);
        } catch (Exception e) {
            return null;
        }

        for (DashboardConfig config : dashboardCollection) {
            if (config.getId().equals(id)) {
                return config;
            }
        }

        return null;
    }
}
