/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.bc.report;

import org.kuali.kfs.module.bc.BCConstants.Report.BuildMode;
import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPullup;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * Holds information on the current build configuration of the report control list table, and a requested configuration.
 */
public class ReportControlListBuildHelper implements Serializable {
    private BuildState currentState;
    private BuildState requestedState;
    private boolean forceRebuild;

    /**
     * Constructs a ReportControlListBuildHelper.java.
     */
    public ReportControlListBuildHelper() {
        super();
        forceRebuild = false;
    }

    /**
     * Determines whether a build is needed based on the current and requested states
     *
     * @return true if build is needed, false otherwise
     */
    public boolean isBuildNeeded() {
        boolean buildNeeded = false;

        if (this.isForceRebuild()) {
            buildNeeded = true;
        } else if (this.getCurrentState() == null && this.getRequestedState() != null) {
            buildNeeded = true;
        } else if (this.getRequestedState() != null && !this.getCurrentState().equals(this.getRequestedState())) {
            buildNeeded = true;
        }

        return buildNeeded;
    }

    /**
     * Helper method to add a requestState
     *
     * @param pointOfView           - request point of view
     * @param selectedOrganizations - organizations selected for the report
     * @param buildMode             - mode in which the control list should be build
     * @see org.kuali.kfs.module.bc.BCConstants.Report.BuildMode
     */
    public void addBuildRequest(String pointOfView, Collection<BudgetConstructionPullup> selectedOrganizations, BuildMode buildMode) {
        this.setRequestedState(new BuildState(pointOfView, selectedOrganizations, buildMode));
    }

    /**
     * Called when the control list has been built that satisfies the requested state. Will set current state to the requested
     * state then empty the requested state.
     */
    public void requestBuildComplete() {
        if (this.getRequestedState() != null) {
            if (this.getCurrentState() == null) {
                this.setCurrentState(new BuildState());
            }

            this.getCurrentState().setPointOfView(this.getRequestedState().getPointOfView());
            this.getCurrentState().setSelectedOrganizations(this.getRequestedState().getSelectedOrganizations());
            this.getCurrentState().setBuildMode(this.getRequestedState().getBuildMode());

            this.setRequestedState(null);
            this.setForceRebuild(false);
        } else {
            throw new RuntimeException("Requested state does not exist. Control list build state has been lost.");
        }
    }

    /**
     * Represents a build configuration for the control list.
     */
    public class BuildState implements Serializable {
        private String pointOfView;
        private Collection<BudgetConstructionPullup> selectedOrganizations;
        private BuildMode buildMode;

        /**
         * Constructs a ReportControlListBuildHelper.java.
         */
        public BuildState() {
        }

        /**
         * Constructs a ReportControlListBuildHelper.java.
         *
         * @param pointOfView           - chart/org point of view string
         * @param selectedOrganizations - organizations selected for reporting
         * @param buildMode             - mode for restricting report data
         */
        public BuildState(String pointOfView, Collection<BudgetConstructionPullup> selectedOrganizations, BuildMode buildMode) {
            this.pointOfView = pointOfView;
            this.selectedOrganizations = selectedOrganizations;
            this.buildMode = buildMode;
        }

        /**
         * Gets the buildMode attribute.
         *
         * @return Returns the buildMode.
         */
        public BuildMode getBuildMode() {
            return buildMode;
        }

        /**
         * Sets the buildMode attribute value.
         *
         * @param buildMode The buildMode to set.
         */
        public void setBuildMode(BuildMode buildMode) {
            this.buildMode = buildMode;
        }

        /**
         * Gets the pointOfView attribute.
         *
         * @return Returns the pointOfView.
         */
        public String getPointOfView() {
            return pointOfView;
        }

        /**
         * Sets the pointOfView attribute value.
         *
         * @param pointOfView The pointOfView to set.
         */
        public void setPointOfView(String pointOfView) {
            this.pointOfView = pointOfView;
        }

        /**
         * Gets the selectedOrganizations attribute.
         *
         * @return Returns the selectedOrganizations.
         */
        public Collection<BudgetConstructionPullup> getSelectedOrganizations() {
            return selectedOrganizations;
        }

        /**
         * Sets the selectedOrganizations attribute value.
         *
         * @param selectedOrganizations The selectedOrganizations to set.
         */
        public void setSelectedOrganizations(Collection<BudgetConstructionPullup> selectedOrganizations) {
            this.selectedOrganizations = selectedOrganizations;
        }

        /**
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof BuildState) {
                boolean isEqual = true;

                if (obj == null) {
                    isEqual = false;
                } else {
                    BuildState compareState = (BuildState) obj;

                    if (!this.getPointOfView().equals(compareState.getPointOfView())) {
                        isEqual = false;
                    }

                    if (!this.compareOrganizations(this.getSelectedOrganizations(), compareState.getSelectedOrganizations())) {
                        isEqual = false;
                    }

                    if (!this.getBuildMode().equals(compareState.getBuildMode())) {
                        isEqual = false;
                    }
                }

                return isEqual;
            } else {
                return super.equals(obj);
            }
        }

        /**
         * Compares two collections of BudgetConstructionPullup objects for equality. BudgetConstructionPullup objects are compared
         * by primary key.
         *
         * @param currentOrgs   - current org build
         * @param requestedOrgs - requested org build
         * @return boolean indicating true if the collections are equal, false otherwise
         */
        private boolean compareOrganizations(Collection<BudgetConstructionPullup> currentOrgs, Collection<BudgetConstructionPullup> requestedOrgs) {
            Set<String> currentOrgSet = new HashSet<String>();
            for (BudgetConstructionPullup pullup : currentOrgs) {
                currentOrgSet.add(pullup.getChartOfAccountsCode() + pullup.getOrganizationCode());
            }

            for (BudgetConstructionPullup pullup : requestedOrgs) {
                if (!currentOrgSet.contains(pullup.getChartOfAccountsCode() + pullup.getOrganizationCode())) {
                    return false;
                }
                currentOrgSet.remove(pullup.getChartOfAccountsCode() + pullup.getOrganizationCode());
            }

            if (!currentOrgSet.isEmpty()) {
                return false;
            }

            return true;
        }
    }

    /**
     * Gets the currentState attribute.
     *
     * @return Returns the currentState.
     */
    public BuildState getCurrentState() {
        return currentState;
    }

    /**
     * Sets the currentState attribute value.
     *
     * @param currentState The currentState to set.
     */
    public void setCurrentState(BuildState currentState) {
        this.currentState = currentState;
    }

    /**
     * Gets the requestedState attribute.
     *
     * @return Returns the requestedState.
     */
    public BuildState getRequestedState() {
        return requestedState;
    }

    /**
     * Sets the requestedState attribute value.
     *
     * @param requestedState The requestedState to set.
     */
    public void setRequestedState(BuildState requestedState) {
        this.requestedState = requestedState;
    }

    /**
     * Gets the forceRebuild attribute.
     *
     * @return Returns the forceRebuild.
     */
    public boolean isForceRebuild() {
        return forceRebuild;
    }

    /**
     * Sets the forceRebuild attribute value.
     *
     * @param forceRebuild The forceRebuild to set.
     */
    public void setForceRebuild(boolean forceRebuild) {
        this.forceRebuild = forceRebuild;
    }

}
