/*
 * This file is part of lanterna (https://github.com/mabe02/lanterna).
 *
 * lanterna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2010-2020 Martin Berglund
 */
package com.googlecode.lanterna.gui2;

import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;

import java.util.*;

/**
 * BorderLayout imitates the BorderLayout class from AWT, allowing you to add a center component with optional 
 * components around it in top, bottom, left and right locations. The edge components will be sized at their preferred
 * size and the center component will take up whatever remains.
 * @author martin
 */
public class BorderLayout implements LayoutManager {

    /**
     * This type is what you use as the layout data for components added to a panel using {@code BorderLayout} for its
     * layout manager. This values specified where inside the panel the component should be added.
     */
    public enum Location implements LayoutData {
        /**
         * The component with this value as its layout data will occupy the center space, whatever is remaining after
         * the other components (if any) have allocated their space.
         */
        CENTER,
        /**
         * The component with this value as its layout data will occupy the left side of the container, attempting to
         * allocate the preferred width of the component and at least the preferred height, but could be more depending
         * on the other components added.
         */
        LEFT,
        /**
         * The component with this value as its layout data will occupy the right side of the container, attempting to
         * allocate the preferred width of the component and at least the preferred height, but could be more depending
         * on the other components added.
         */
        RIGHT,
        /**
         * The component with this value as its layout data will occupy the top side of the container, attempting to
         * allocate the preferred height of the component and at least the preferred width, but could be more depending
         * on the other components added.
         */
        TOP,
        /**
         * The component with this value as its layout data will occupy the bottom side of the container, attempting to
         * allocate the preferred height of the component and at least the preferred width, but could be more depending
         * on the other components added.
         */
        BOTTOM,
        ;
    }

    //When components don't have a location, we'll assign an available location based on this order
    private static final List<Location> AUTO_ASSIGN_ORDER = Collections.unmodifiableList(Arrays.asList(
            Location.CENTER,
            Location.TOP,
            Location.BOTTOM,
            Location.LEFT,
            Location.RIGHT));

    @Override
    public TerminalSize getPreferredSize(List<Component> components) {
        EnumMap<Location, Component> layout = makeLookupMap(components);
        int preferredHeight = 
                (layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getRows() : 0)
                +
                Math.max(
                    layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getRows() : 0,
                    Math.max(
                        layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getRows() : 0,
                        layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getRows() : 0))
                +
                (layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getRows() : 0);

        int preferredWidth = 
                Math.max(
                    (layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getColumns() : 0) +
                        (layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getColumns() : 0) +
                        (layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getColumns() : 0),
                    Math.max(
                        layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getColumns() : 0,
                        layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getColumns() : 0));
        return new TerminalSize(preferredWidth, preferredHeight);
    }

    @Override
    public void doLayout(TerminalSize area, List<Component> components) {
        EnumMap<Location, Component> layout = makeLookupMap(components);
        int availableHorizontalSpace = area.getColumns();
        int availableVerticalSpace = area.getRows();
        
        //We'll need this later on
        int topComponentHeight = 0;
        int leftComponentWidth = 0;

        //First allocate the top
        if(layout.containsKey(Location.TOP)) {
            Component topComponent = layout.get(Location.TOP);
            topComponentHeight = Math.min(topComponent.getPreferredSize().getRows(), availableVerticalSpace);
            topComponent.setPosition(TerminalPosition.TOP_LEFT_CORNER);
            topComponent.setSize(new TerminalSize(availableHorizontalSpace, topComponentHeight));
            availableVerticalSpace -= topComponentHeight;
        }

        //Next allocate the bottom
        if(layout.containsKey(Location.BOTTOM)) {
            Component bottomComponent = layout.get(Location.BOTTOM);
            int bottomComponentHeight = Math.min(bottomComponent.getPreferredSize().getRows(), availableVerticalSpace);
            bottomComponent.setPosition(new TerminalPosition(0, area.getRows() - bottomComponentHeight));
            bottomComponent.setSize(new TerminalSize(availableHorizontalSpace, bottomComponentHeight));
            availableVerticalSpace -= bottomComponentHeight;
        }

        //Now divide the remaining space between LEFT, CENTER and RIGHT
        if(layout.containsKey(Location.LEFT)) {
            Component leftComponent = layout.get(Location.LEFT);
            leftComponentWidth = Math.min(leftComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
            leftComponent.setPosition(new TerminalPosition(0, topComponentHeight));
            leftComponent.setSize(new TerminalSize(leftComponentWidth, availableVerticalSpace));
            availableHorizontalSpace -= leftComponentWidth;
        }
        if(layout.containsKey(Location.RIGHT)) {
            Component rightComponent = layout.get(Location.RIGHT);
            int rightComponentWidth = Math.min(rightComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
            rightComponent.setPosition(new TerminalPosition(area.getColumns() - rightComponentWidth, topComponentHeight));
            rightComponent.setSize(new TerminalSize(rightComponentWidth, availableVerticalSpace));
            availableHorizontalSpace -= rightComponentWidth;
        }
        if(layout.containsKey(Location.CENTER)) {
            Component centerComponent = layout.get(Location.CENTER);
            centerComponent.setPosition(new TerminalPosition(leftComponentWidth, topComponentHeight));
            centerComponent.setSize(new TerminalSize(availableHorizontalSpace, availableVerticalSpace));
        }
        
        //Set the remaining components to 0x0
        for(Component component: components) {
            if(component.isVisible() && !layout.containsValue(component)) {
                component.setPosition(TerminalPosition.TOP_LEFT_CORNER);
                component.setSize(TerminalSize.ZERO);
            }
        }
    }
    
    private EnumMap<Location, Component> makeLookupMap(List<Component> components) {
        EnumMap<Location, Component> map = new EnumMap<>(Location.class);
        List<Component> unassignedComponents = new ArrayList<>();
        for(Component component: components) {
            if (!component.isVisible()) {
                continue;
            }
            if(component.getLayoutData() instanceof Location) {
                map.put((Location)component.getLayoutData(), component);
            }
            else {
                unassignedComponents.add(component);
            }
        }
        //Try to assign components to available locations
        for(Component component: unassignedComponents) {
            for(Location location: AUTO_ASSIGN_ORDER) {
                if(!map.containsKey(location)) {
                    map.put(location, component);
                    break;
                }
            }
        }
        return map;
    }

    @Override
    public boolean hasChanged() {
        //No internal state
        return false;
    }
}
