/*
 * RHQ Management Platform
 * Copyright (C) 2005-2009 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also 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 and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.core.domain.dashboard;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

import org.hibernate.annotations.Cascade;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.group.ResourceGroup;

/**
 * @author Greg Hinkle
 * @author Jay Shaughnessy
 */
@Entity
@SequenceGenerator(name = "RHQ_DASHBOARD_ID_SEQ", sequenceName = "RHQ_DASHBOARD_ID_SEQ")
@Table(name = "RHQ_DASHBOARD")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class Dashboard implements Serializable {

    private static final long serialVersionUID = 1L;

    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "RHQ_DASHBOARD_ID_SEQ")
    @Id
    private int id = 0;

    @Column(name = "NAME", nullable = false)
    private String name;

    @Column(name = "CATEGORY", nullable = false)
    @Enumerated(EnumType.STRING)
    private DashboardCategory category;

    // currently unused
    @Column(name = "SHARED", nullable = false)
    private boolean shared = false;

    @JoinColumn(name = "CONFIGURATION_ID", referencedColumnName = "ID", nullable = true)
    @OneToOne(cascade = { CascadeType.ALL }, optional = true)
    private Configuration configuration = new Configuration();

    @JoinColumn(name = "SUBJECT_ID", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Subject owner;

    @JoinColumn(name = "RESOURCE_ID", nullable = true)
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private Resource resource;

    @JoinColumn(name = "GROUP_ID", nullable = true)
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    private ResourceGroup group;

    @OneToMany(mappedBy = "dashboard", fetch = FetchType.EAGER)
    @Cascade( { org.hibernate.annotations.CascadeType.PERSIST, org.hibernate.annotations.CascadeType.MERGE,
        org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
    private Set<DashboardPortlet> portlets = new HashSet<DashboardPortlet>();

    public static final String CFG_COLUMNS = "columns";
    public static final String CFG_WIDTHS = "widths";
    public static final String CFG_BACKGROUND = "background";
    public static final String CFG_REFRESH_RATE = "refresh";

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public boolean isShared() {
        return shared;
    }

    public void setShared(boolean shared) {
        this.shared = shared;
    }

    public Subject getOwner() {
        return owner;
    }

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

    public DashboardCategory getCategory() {
        return category;
    }

    public void setCategory(DashboardCategory category) {
        this.category = category;
    }

    public Resource getResource() {
        return resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
    }

    public ResourceGroup getGroup() {
        return group;
    }

    public void setGroup(ResourceGroup group) {
        this.group = group;
    }

    public int getColumns() {
        return Integer.valueOf(configuration.getSimpleValue(CFG_COLUMNS, "1"));
    }

    public void setColumns(int columns) {
        configuration.put(new PropertySimple(CFG_COLUMNS, columns));
    }

    public String[] getColumnWidths() {
        return configuration.getSimpleValue(CFG_WIDTHS, "").split(",");
    }

    public void setColumnWidths(String... columnWidths) {
        if (null == columnWidths || columnWidths.length == 0) {
            return;
        }

        // note - this impl is a little verbose but it avoids a smartgwt javascript problem with
        // varargs handling. Not sure how bad the problem is but it definitely occured with SmartGwt 2.4
        // when a single String was pssed as the varArg list.  Be wary of directly using varargs array
        // element 0...
        StringBuilder sb = new StringBuilder();
        sb.append(columnWidths[0]);
        for (int i = 1; i < columnWidths.length; ++i) {
            sb.append(",");
            sb.append(columnWidths[i]);
        }
        configuration.put(new PropertySimple(CFG_WIDTHS, sb));
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    public Set<DashboardPortlet> getPortlets() {
        return portlets;
    }

    public List<DashboardPortlet> getPortlets(int column) {

        ArrayList<DashboardPortlet> columnPortlets = new ArrayList<DashboardPortlet>();
        for (DashboardPortlet p : this.portlets) {
            if (p.getColumn() == column) {
                columnPortlets.add(p);
            }
        }

        Collections.sort(columnPortlets, new Comparator<DashboardPortlet>() {
            public int compare(DashboardPortlet o1, DashboardPortlet o2) {
                return (o1.getIndex() < o2.getIndex() ? -1 : (o1.getIndex() == o2.getIndex() ? 0 : 1));
            }
        });

        return columnPortlets;
    }

    public boolean removePortlet(DashboardPortlet storedPortlet) {
        if (!portlets.contains(storedPortlet)) {
            return false;
        }

        // lower the index by 1 for  portlets in the same column, below the one being removed
        int col = storedPortlet.getColumn();
        int index = storedPortlet.getIndex();

        portlets.remove(storedPortlet);

        for (Iterator<DashboardPortlet> i = portlets.iterator(); i.hasNext();) {
            DashboardPortlet next = i.next();
            if (col == next.getColumn() && index < next.getIndex()) {
                next.setIndex(next.getIndex() - 1);
            }
        }

        return true;
    }

    /** 
     * This can be used to safely add a portlet without knowing the current portlet positioning on the
     * Dashboard. It adds the portlet to the bottom of column with the least portlets.
     * 
     * @param storedPortlet, MODIFIED with assigned column, index
     */
    public void addPortlet(DashboardPortlet storedPortlet) {
        int[] columnCounts = new int[getColumns()];
        Arrays.fill(columnCounts, 0);
        // set column counts
        for (DashboardPortlet dashboardPortlet : portlets) {
            ++(columnCounts[dashboardPortlet.getColumn()]);
        }
        // get best column
        int bestColumn = -1;
        int minPortlets = Integer.MAX_VALUE;
        for (int column = 0; (column < columnCounts.length); ++column) {
            if (columnCounts[column] < minPortlets) {
                bestColumn = column;
                minPortlets = columnCounts[column];
            }
        }
        // addPortlet to best Column
        addPortlet(storedPortlet, bestColumn, minPortlets);
    }

    /**
     * Call this only if you are sure the column and index are valid, not already used and not leaving gaps.
     * 
     * @param storedPortlet, MODIFIED with assigned column, index
     * @param column
     * @param index
     */
    public void addPortlet(DashboardPortlet storedPortlet, int column, int index) {
        storedPortlet.setColumn(column);
        storedPortlet.setIndex(index);
        storedPortlet.setDashboard(this);
        portlets.add(storedPortlet);
    }

    public Dashboard deepCopy(boolean keepIds) {

        Dashboard newDashboard = new Dashboard();
        if (keepIds) {
            newDashboard.id = this.id;
        }
        newDashboard.name = this.name;
        newDashboard.owner = this.owner;
        newDashboard.shared = this.shared;
        newDashboard.configuration = this.configuration.deepCopy(keepIds);

        for (DashboardPortlet portlet : portlets) {
            DashboardPortlet newPortlet = portlet.deepCopy(keepIds);
            newPortlet.setDashboard(this);
            newDashboard.addPortlet(newPortlet, newPortlet.getColumn(), newPortlet.getIndex());
        }
        return newDashboard;
    }

}
