/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.causeway.applib.layout.grid.bootstrap;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;

import org.apache.causeway.applib.annotation.Programmatic;
import org.apache.causeway.applib.layout.component.ActionLayoutData;
import org.apache.causeway.applib.layout.component.CollectionLayoutData;
import org.apache.causeway.applib.layout.component.DomainObjectLayoutData;
import org.apache.causeway.applib.layout.component.FieldSet;
import org.apache.causeway.applib.layout.component.PropertyLayoutData;
import org.apache.causeway.applib.layout.grid.Grid;
import org.apache.causeway.applib.layout.grid.GridAbstract;
import org.apache.causeway.applib.mixins.dto.Dto;

/**
 * This is the top-level for rendering the domain object's properties, collections and actions.  It simply consists
 * of a number of rows.
 *
 * <p>
 *     The element is rendered as a &lt;div class=&quot;...&quot;&gt;
 * </p>
 *
 * @since 1.x {@index}
 */
@XmlRootElement(
        name = "grid"
        )
@XmlType(
        name = "grid"
        , propOrder = {
                "rows",
                "metadataErrors"
        }
        )
public class BSGrid extends GridAbstract implements BSElement, Dto, BSRowOwner {

    private static final long serialVersionUID = 1L;

    private String cssClass;

    @Override
    @XmlAttribute(required = false)
    public String getCssClass() {
        return cssClass;
    }

    @Override
    public void setCssClass(final String cssClass) {
        this.cssClass = cssClass;
    }

    private List<BSRow> rows = new ArrayList<>();

    // no wrapper
    @Override
    @XmlElement(name = "row", required = true)
    public List<BSRow> getRows() {
        return rows;
    }

    public void setRows(final List<BSRow> rows) {
        this.rows = rows;
    }

    private List<String> metadataErrors = new ArrayList<>();

    /**
     * For diagnostics; populated by the framework if and only if a metadata error.
     */
    @XmlElement(name = "metadataError", required = false)
    public List<String> getMetadataErrors() {
        return metadataErrors;
    }

    public void setMetadataErrors(final List<String> metadataErrors) {
        this.metadataErrors = metadataErrors;
    }

    @SuppressWarnings("unused")
    private BSRowOwner owner;

    public interface Visitor extends Grid.Visitor {
        void preVisit(final BSGrid bsGrid);
        void visit(final BSGrid bsGrid);
        void postVisit(final BSGrid bsGrid);
        void preVisit(final BSRow bsRow);
        void visit(final BSRow bsRow);
        void postVisit(final BSRow bsRow);
        void preVisit(final BSCol bsCol);
        void visit(final BSCol bsCol);
        void postVisit(final BSCol bsCol);
        void visit(final BSClearFix bsClearFix);
        void preVisit(final BSTabGroup bsTabGroup);
        void visit(final BSTabGroup bsTabGroup);
        void postVisit(final BSTabGroup bsTabGroup);
        void preVisit(final BSTab bsTab);
        void visit(final BSTab bsTab);
        void postVisit(final BSTab bsTab);
    }

    public static class VisitorAdapter extends Grid.VisitorAdapter implements Visitor {
        @Override public void preVisit(final BSGrid bsGrid) { }
        @Override public void visit(final BSGrid bsGrid) { }
        @Override public void postVisit(final BSGrid bsGrid) { }

        @Override public void preVisit(final BSRow bsRow) { }
        @Override public void visit(final BSRow bsRow) { }
        @Override public void postVisit(final BSRow bsRow) { }

        @Override public void preVisit(final BSCol bsCol) { }
        @Override public void visit(final BSCol bsCol) { }
        @Override public void postVisit(final BSCol bsCol) { }

        @Override public void visit(final BSClearFix bsClearFix) { }

        @Override public void preVisit(final BSTabGroup bsTabGroup) { }
        @Override public void visit(final BSTabGroup bsTabGroup) { }
        @Override public void postVisit(final BSTabGroup bsTabGroup) { }

        @Override public void preVisit(final BSTab bsTab) { }
        @Override public void visit(final BSTab bsTab) { }
        @Override public void postVisit(final BSTab bsTab) { }
    }

    @Override
    public void visit(final Grid.Visitor visitor) {
        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
        bsVisitor.preVisit(this);
        bsVisitor.visit(this);
        traverseRows(this, visitor);
        bsVisitor.postVisit(this);
    }

    protected void traverseRows(final BSRowOwner rowOwner, final Grid.Visitor visitor) {
        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
        final List<BSRow> rows = rowOwner.getRows();
        for (BSRow bsRow : new ArrayList<>(rows)) {
            bsRow.setOwner(this);
            bsVisitor.preVisit(bsRow);
            bsVisitor.visit(bsRow);
            traverseCols(visitor, bsRow);
            bsVisitor.postVisit(bsRow);
        }
    }

    private void traverseCols(final Grid.Visitor visitor, final BSRow bsRow) {
        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
        final List<BSRowContent> cols = bsRow.getCols();
        for (BSRowContent rowContent : new ArrayList<>(cols)) {
            rowContent.setOwner(bsRow);
            if(rowContent instanceof BSCol) {
                final BSCol bsCol = (BSCol) rowContent;
                bsVisitor.preVisit(bsCol);
                bsVisitor.visit(bsCol);
                traverseDomainObject(bsCol, visitor);
                traverseTabGroups(bsCol, visitor);
                traverseActions(bsCol, visitor);
                traverseFieldSets(bsCol, visitor);
                traverseCollections(bsCol, visitor);
                traverseRows(bsCol, visitor);
                bsVisitor.postVisit(bsCol);
            } else if (rowContent instanceof BSClearFix) {
                final BSClearFix bsClearFix = (BSClearFix) rowContent;
                bsVisitor.visit(bsClearFix);
            } else {
                throw new IllegalStateException(
                        "Unrecognized implementation of BSRowContent, " + rowContent);
            }
        }
    }

    private void traverseDomainObject(final BSCol bsCol, final Grid.Visitor visitor) {
        final DomainObjectLayoutData domainObject = bsCol.getDomainObject();
        if(domainObject == null) {
            return;
        }
        domainObject.setOwner(bsCol);
        visitor.visit(domainObject);
    }

    private void traverseTabGroups(
            final BSTabGroupOwner bsTabGroupOwner,
            final Grid.Visitor visitor) {
        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
        final List<BSTabGroup> tabGroups = bsTabGroupOwner.getTabGroups();
        for (BSTabGroup bsTabGroup : new ArrayList<>(tabGroups)) {
            bsTabGroup.setOwner(bsTabGroupOwner);
            bsVisitor.preVisit(bsTabGroup);
            bsVisitor.visit(bsTabGroup);
            traverseTabs(bsTabGroup, visitor);
            bsVisitor.postVisit(bsTabGroup);
        }
    }

    private void traverseTabs(
            final BSTabOwner bsTabOwner,
            final Grid.Visitor visitor) {
        final BSGrid.Visitor bsVisitor = asBsVisitor(visitor);
        final List<BSTab> tabs = bsTabOwner.getTabs();
        for (BSTab tab : new ArrayList<>(tabs)) {
            tab.setOwner(bsTabOwner);
            bsVisitor.preVisit(tab);
            bsVisitor.visit(tab);
            traverseRows(tab, visitor);
            bsVisitor.postVisit(tab);
        }
    }

    private static Visitor asBsVisitor(final Grid.Visitor visitor) {
        return visitor instanceof Visitor? (Visitor) visitor : new BSGrid.VisitorAdapter() {
            @Override public void visit(final DomainObjectLayoutData domainObjectLayoutData) {
                visitor.visit(domainObjectLayoutData);
            }

            @Override public void visit(final ActionLayoutData actionLayoutData) {
                visitor.visit(actionLayoutData);
            }

            @Override public void visit(final PropertyLayoutData propertyLayoutData) {
                visitor.visit(propertyLayoutData);
            }

            @Override public void visit(final CollectionLayoutData collectionLayoutData) {
                visitor.visit(collectionLayoutData);
            }

            @Override public void visit(final FieldSet fieldSet) {
                visitor.visit(fieldSet);
            }
        };
    }

    @Programmatic
    @XmlTransient
    public LinkedHashMap<String, BSTab> getAllTabsByName() {
        final LinkedHashMap<String, BSTab> tabsByName = new LinkedHashMap<>();

        visit(new BSGrid.VisitorAdapter() {
            @Override
            public void visit(final BSTab bSTab) {
                tabsByName.put(bSTab.getName(), bSTab);
            }
        });
        return tabsByName;
    }

    @Programmatic
    @XmlTransient
    public LinkedHashMap<String, HasCssId> getAllCssId() {
        final LinkedHashMap<String, HasCssId> divsByCssId = new LinkedHashMap<>();

        visit(new BSGrid.VisitorAdapter() {
            @Override
            public void visit(final BSRow bsRow) {
                final String id = bsRow.getId();
                divsByCssId.put(id, bsRow);
            }
        });
        return divsByCssId;
    }

    @Override
    @Programmatic
    @XmlTransient
    public BSGrid getGrid() {
        return this;
    }

}
