/*
 * 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.
 */

/* $Id$ */

package org.apache.fop.layoutmgr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.area.AreaTreeHandler;
import org.apache.fop.fo.FOElementMapping;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FONode.FONodeIterator;
import org.apache.fop.fo.FOText;
import org.apache.fop.fo.FObjMixed;
import org.apache.fop.fo.extensions.ExternalDocument;
import org.apache.fop.fo.flow.BasicLink;
import org.apache.fop.fo.flow.BidiOverride;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.flow.BlockContainer;
import org.apache.fop.fo.flow.ChangeBarBegin;
import org.apache.fop.fo.flow.ChangeBarEnd;
import org.apache.fop.fo.flow.Character;
import org.apache.fop.fo.flow.ExternalGraphic;
import org.apache.fop.fo.flow.Float;
import org.apache.fop.fo.flow.Footnote;
import org.apache.fop.fo.flow.Inline;
import org.apache.fop.fo.flow.InlineContainer;
import org.apache.fop.fo.flow.InlineLevel;
import org.apache.fop.fo.flow.InstreamForeignObject;
import org.apache.fop.fo.flow.Leader;
import org.apache.fop.fo.flow.ListBlock;
import org.apache.fop.fo.flow.ListItem;
import org.apache.fop.fo.flow.MultiCase;
import org.apache.fop.fo.flow.MultiSwitch;
import org.apache.fop.fo.flow.PageNumber;
import org.apache.fop.fo.flow.PageNumberCitation;
import org.apache.fop.fo.flow.PageNumberCitationLast;
import org.apache.fop.fo.flow.RetrieveMarker;
import org.apache.fop.fo.flow.RetrieveTableMarker;
import org.apache.fop.fo.flow.Wrapper;
import org.apache.fop.fo.flow.table.Table;
import org.apache.fop.fo.flow.table.TableBody;
import org.apache.fop.fo.flow.table.TableCell;
import org.apache.fop.fo.flow.table.TableColumn;
import org.apache.fop.fo.flow.table.TableFooter;
import org.apache.fop.fo.flow.table.TableHeader;
import org.apache.fop.fo.flow.table.TableRow;
import org.apache.fop.fo.pagination.Flow;
import org.apache.fop.fo.pagination.PageSequence;
import org.apache.fop.fo.pagination.SideRegion;
import org.apache.fop.fo.pagination.StaticContent;
import org.apache.fop.fo.pagination.Title;
import org.apache.fop.layoutmgr.inline.BasicLinkLayoutManager;
import org.apache.fop.layoutmgr.inline.BidiLayoutManager;
import org.apache.fop.layoutmgr.inline.CharacterLayoutManager;
import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
import org.apache.fop.layoutmgr.inline.ExternalGraphicLayoutManager;
import org.apache.fop.layoutmgr.inline.FloatLayoutManager;
import org.apache.fop.layoutmgr.inline.FootnoteLayoutManager;
import org.apache.fop.layoutmgr.inline.InlineContainerLayoutManager;
import org.apache.fop.layoutmgr.inline.InlineLayoutManager;
import org.apache.fop.layoutmgr.inline.InstreamForeignObjectLM;
import org.apache.fop.layoutmgr.inline.LeaderLayoutManager;
import org.apache.fop.layoutmgr.inline.PageNumberCitationLastLayoutManager;
import org.apache.fop.layoutmgr.inline.PageNumberCitationLayoutManager;
import org.apache.fop.layoutmgr.inline.PageNumberLayoutManager;
import org.apache.fop.layoutmgr.inline.TextLayoutManager;
import org.apache.fop.layoutmgr.inline.WrapperLayoutManager;
import org.apache.fop.layoutmgr.list.ListBlockLayoutManager;
import org.apache.fop.layoutmgr.list.ListItemLayoutManager;
import org.apache.fop.layoutmgr.table.TableLayoutManager;
import org.apache.fop.util.CharUtilities;

/**
 * The default LayoutManager maker class
 */
public class LayoutManagerMapping implements LayoutManagerMaker {

    /** logging instance */
    private static final Log LOG = LogFactory.getLog(LayoutManagerMapping.class);

    /** The map of LayoutManagerMakers */
    private final Map makers = new HashMap();

    private FOUserAgent userAgent;

    /** default constructor */
    public LayoutManagerMapping(FOUserAgent userAgent) {
        this.userAgent = userAgent;
        initialize();
    }

    /**
     * Initializes the set of maker objects associated with this LayoutManagerMapping
     */
    protected void initialize() {
        registerMaker(FOText.class, new FOTextLayoutManagerMaker());
        registerMaker(FObjMixed.class, new Maker());
        registerMaker(BidiOverride.class, new BidiOverrideLayoutManagerMaker());
        registerMaker(Inline.class, new InlineLayoutManagerMaker());
        registerMaker(Footnote.class, new FootnoteLayoutManagerMaker());
        registerMaker(InlineContainer.class,
                   new InlineContainerLayoutManagerMaker());
        registerMaker(BasicLink.class, new BasicLinkLayoutManagerMaker());
        registerMaker(Block.class, new BlockLayoutManagerMaker());
        registerMaker(Leader.class, new LeaderLayoutManagerMaker());
        registerMaker(RetrieveMarker.class, new RetrieveMarkerLayoutManagerMaker());
        registerMaker(RetrieveTableMarker.class, new RetrieveTableMarkerLayoutManagerMaker());
        registerMaker(Character.class, new CharacterLayoutManagerMaker());
        registerMaker(ExternalGraphic.class,
                   new ExternalGraphicLayoutManagerMaker());
        registerMaker(BlockContainer.class,
                   new BlockContainerLayoutManagerMaker());
        registerMaker(ListItem.class, new ListItemLayoutManagerMaker());
        registerMaker(ListBlock.class, new ListBlockLayoutManagerMaker());
        registerMaker(InstreamForeignObject.class,
                   new InstreamForeignObjectLayoutManagerMaker());
        registerMaker(PageNumber.class, new PageNumberLayoutManagerMaker());
        registerMaker(PageNumberCitation.class,
                   new PageNumberCitationLayoutManagerMaker());
        registerMaker(PageNumberCitationLast.class,
                new PageNumberCitationLastLayoutManagerMaker());
        registerMaker(Table.class, new TableLayoutManagerMaker());
        registerMaker(TableBody.class, new Maker());
        registerMaker(TableColumn.class, new Maker());
        registerMaker(TableRow.class, new Maker());
        registerMaker(TableCell.class, new Maker());
        registerMaker(TableFooter.class, new Maker());
        registerMaker(TableHeader.class, new Maker());
        registerMaker(Wrapper.class, new WrapperLayoutManagerMaker());
        registerMaker(Title.class, new InlineLayoutManagerMaker());
        registerMaker(ChangeBarBegin.class, new Maker());
        registerMaker(ChangeBarEnd.class, new Maker());
        registerMaker(MultiCase.class, new MultiCaseLayoutManagerMaker());
        registerMaker(MultiSwitch.class, new MultiSwitchLayoutManagerMaker());
        registerMaker(Float.class, new FloatLayoutManagerMaker());
    }

    /**
     * Registers a Maker class for a specific formatting object.
     * @param clazz the formatting object class
     * @param maker the maker for the layout manager
     */
    protected void registerMaker(Class clazz, Maker maker) {
        makers.put(clazz, maker);
    }

    /** {@inheritDoc} */
    public void makeLayoutManagers(FONode node, List lms) {
        Maker maker = (Maker) makers.get(node.getClass());
        if (maker == null) {
            if (FOElementMapping.URI.equals(node.getNamespaceURI())) {
                LOG.error("No LayoutManager maker for class " + node.getClass());
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Skipping the creation of a layout manager for " + node.getClass());
                }
            }
        } else {
            maker.make(node, lms, userAgent);
        }
    }

    /** {@inheritDoc} */
    public LayoutManager makeLayoutManager(FONode node) {
        List<LayoutManager> lms = new ArrayList<>();
        makeLayoutManagers(node, lms);
        if (lms.size() == 0) {
            throw new IllegalStateException("LayoutManager for class "
                                   + node.getClass()
                                   + " is missing.");
        } else if (lms.size() > 1) {
            throw new IllegalStateException("Duplicate LayoutManagers for class "
                                   + node.getClass()
                                   + " found, only one may be declared.");
        }
        return lms.get(0);
    }

    /** {@inheritDoc} */
    public PageSequenceLayoutManager makePageSequenceLayoutManager(
        AreaTreeHandler ath, PageSequence ps) {
        return new PageSequenceLayoutManager(ath, ps);
    }

    /** {@inheritDoc} */
    public ExternalDocumentLayoutManager makeExternalDocumentLayoutManager(
        AreaTreeHandler ath, ExternalDocument ed) {
        return new ExternalDocumentLayoutManager(ath, ed);
    }

    /** {@inheritDoc} */
    public FlowLayoutManager makeFlowLayoutManager(
            PageSequenceLayoutManager pslm, Flow flow) {
        return new FlowLayoutManager(pslm, flow);
    }

    /** {@inheritDoc} */
    public ContentLayoutManager makeContentLayoutManager(PageSequenceLayoutManager pslm,
                                                         Title title) {
        return new ContentLayoutManager(pslm, title);
    }

    /** {@inheritDoc} */
    public StaticContentLayoutManager makeStaticContentLayoutManager(
            PageSequenceLayoutManager pslm, StaticContent sc, SideRegion reg) {
        return new StaticContentLayoutManager(pslm, sc, reg);
    }

    /** {@inheritDoc} */
    public StaticContentLayoutManager makeStaticContentLayoutManager(
        PageSequenceLayoutManager pslm, StaticContent sc, org.apache.fop.area.Block block) {
        return new StaticContentLayoutManager(pslm, sc, block);
    }

    /** a layout manager maker base class */
    public static class Maker {
        /**
         * Create a layout manager.
         * @param node the associated FO node
         * @param lms a list of layout managers to which new manager is to be added
         */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            // no layout manager
        }
    }

    /** a layout manager maker */
    public static class FOTextLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            FOText foText = (FOText) node;
            if (foText.length() > 0) {
                lms.add(new TextLayoutManager(foText, userAgent));
            }
        }
    }

    /** a layout manager maker */
    public static class BidiOverrideLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            if (node instanceof BidiOverride) {
                lms.add(new BidiLayoutManager((BidiOverride) node));
            }
        }
    }

    /** a layout manager maker */
    public static class InlineLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new InlineLayoutManager((InlineLevel) node));
        }
    }

    /** a layout manager maker */
    public static class FootnoteLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new FootnoteLayoutManager((Footnote) node));
        }
    }

    /** a layout manager maker */
    public static class InlineContainerLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new InlineContainerLayoutManager((InlineContainer) node));
        }
    }

    /** a layout manager maker */
    public static class BasicLinkLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new BasicLinkLayoutManager((BasicLink) node));
        }
    }

    /** a layout manager maker */
    public static class BlockLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
         public void make(FONode node, List lms, FOUserAgent userAgent) {
             lms.add(new BlockLayoutManager((Block) node));
         }
    }

    /** a layout manager maker */
    public static class LeaderLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new LeaderLayoutManager((Leader) node));
        }
    }

    /** a layout manager maker */
    public static class CharacterLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            Character foCharacter = (Character) node;
            if (foCharacter.getCharacter() != CharUtilities.CODE_EOT) {
                lms.add(new CharacterLayoutManager(foCharacter));
            }
        }
    }

    /** a layout manager maker */
    public static class ExternalGraphicLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            ExternalGraphic eg = (ExternalGraphic) node;
            if (!eg.getSrc().equals("")) {
                lms.add(new ExternalGraphicLayoutManager(eg));
            }
        }
    }

    /** a layout manager maker */
    public static class BlockContainerLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new BlockContainerLayoutManager((BlockContainer) node));
         }
    }

    /** a layout manager maker */
    public static class ListItemLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
         public void make(FONode node, List lms, FOUserAgent userAgent) {
             lms.add(new ListItemLayoutManager((ListItem) node));
         }
    }

    /** a layout manager maker */
    public static class ListBlockLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new ListBlockLayoutManager((ListBlock) node));
        }
    }

    /** a layout manager maker */
    public static class InstreamForeignObjectLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new InstreamForeignObjectLM((InstreamForeignObject) node));
        }
    }

    /** a layout manager maker */
    public static class PageNumberLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
         public void make(FONode node, List lms, FOUserAgent userAgent) {
             lms.add(new PageNumberLayoutManager((PageNumber) node));
         }
    }

    /** a layout manager maker */
    public static class PageNumberCitationLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
         public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new PageNumberCitationLayoutManager((PageNumberCitation) node));
         }
    }

    /** a layout manager maker */
    public static class PageNumberCitationLastLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
           lms.add(new PageNumberCitationLastLayoutManager((PageNumberCitationLast) node));
        }
    }

    /** a layout manager maker */
    public static class TableLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            Table table = (Table) node;
            TableLayoutManager tlm = new TableLayoutManager(table);
            lms.add(tlm);
        }
    }

    /** a layout manager maker */
    public class RetrieveMarkerLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            Iterator baseIter;
            baseIter = node.getChildNodes();
            if (baseIter == null) {
                return;
            }
            while (baseIter.hasNext()) {
                FONode child = (FONode) baseIter.next();
                makeLayoutManagers(child, lms);
            }
        }
    }

    public class RetrieveTableMarkerLayoutManagerMaker extends Maker {
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            FONodeIterator baseIter = node.getChildNodes();
            if (baseIter == null) {
                // this happens when the retrieve-table-marker cannot be resolved yet
                RetrieveTableMarker rtm = (RetrieveTableMarker) node;
                RetrieveTableMarkerLayoutManager rtmlm = new RetrieveTableMarkerLayoutManager(rtm);
                lms.add(rtmlm);
                return;
            }
            while (baseIter.hasNext()) {
                // this happens when the retrieve-table-marker has been resolved
                FONode child = baseIter.next();
                makeLayoutManagers(child, lms);
            }
        }
    }

    /** a layout manager maker */
    public class WrapperLayoutManagerMaker extends Maker {
        /** {@inheritDoc} */
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            //We insert the wrapper LM before it's children so an ID
            //on the node can be registered on a page.
            lms.add(new WrapperLayoutManager((Wrapper)node));
            Iterator baseIter;
            baseIter = node.getChildNodes();
            if (baseIter == null) {
                return;
            }
            while (baseIter.hasNext()) {
                FONode child = (FONode) baseIter.next();
                makeLayoutManagers(child, lms);
            }
        }
    }

    public class MultiSwitchLayoutManagerMaker extends Maker {

        @Override
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new MultiSwitchLayoutManager((MultiSwitch) node));
        }
    }

    public class MultiCaseLayoutManagerMaker extends Maker {

        @Override
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new MultiCaseLayoutManager((MultiCase) node));
        }
    }

    public static class FloatLayoutManagerMaker extends Maker {
        public void make(FONode node, List lms, FOUserAgent userAgent) {
            lms.add(new FloatLayoutManager((Float) node));
        }
    }

}
