/**************************************************************
 * 
 * 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.openoffice.xmerge.merger.merge;

import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;

import org.openoffice.xmerge.ConverterCapabilities;
import org.openoffice.xmerge.merger.Iterator;
import org.openoffice.xmerge.merger.NodeMergeAlgorithm;
import org.openoffice.xmerge.merger.diff.CellNodeIterator;
import org.openoffice.xmerge.converter.xml.OfficeConstants;
import org.openoffice.xmerge.util.XmlUtil;


/**
 *  This is an implementation of the <code>NodeMergeAlgorithm</code>
 *  interface.  It is used to merge two rows using a positional
 *  comparison base method.
 */
public final class PositionBaseRowMerge implements NodeMergeAlgorithm {

    /**  The capabilities of this converter. */
    private ConverterCapabilities cc_;


    /**
     *  Constructor.
     * 
     *  @param  cc  The <code>ConverterCapabilities</code>.
     */
    public PositionBaseRowMerge(ConverterCapabilities cc) {
        cc_ = cc;
    }


    public void merge(Node orgRow, Node modRow) {

        Iterator orgCells = new CellNodeIterator(cc_, orgRow);
        Iterator modCells = new CellNodeIterator(cc_, modRow);

        mergeCellSequences(orgCells, modCells);
    }


    // used to compare the cell 1 by 1
    private void mergeCellSequences(Iterator orgSeq, Iterator modSeq) {

        boolean needMerge = true;
        Element orgCell, modCell;

        Object orgSeqObject = orgSeq.start();
        Object modSeqObject = modSeq.start();

        while (orgSeqObject != null) {


            needMerge = true;

            if (modSeqObject ==  null)  {
                // no corresponding cell in the target, empty out the cell
                SheetUtil.emptyCell(cc_, (Node)orgSeqObject);
                orgSeqObject = orgSeq.next();

            } else {

                // compare the cell directly
                if (!orgSeq.equivalent(orgSeqObject, modSeqObject)) {

                    orgCell = (Element)orgSeqObject;
                    modCell = (Element)modSeqObject;

                    // check whether the original cell with multiple column
                    // if so, need to split one out for merge
                    String orgColRepeated = orgCell.getAttribute(
                        OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
                    String modColRepeated = modCell.getAttribute(
                        OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);

                    int orgColNum = 1;
                    int modColNum = 1;

                    if (orgColRepeated.length() > 0) {
                        orgColNum =
                            Integer.valueOf(orgColRepeated).intValue();
                    }
                    if (modColRepeated.length() > 0) {
                        modColNum =
                            Integer.valueOf(modColRepeated).intValue();
                    }

                    // try to find out the common number of repeated cols
                    if (orgColNum == modColNum) {
                        orgSeqObject = orgSeq.next();
                        modSeqObject = modSeq.next();

                    // cut the original cell into 2 half, first half
                    // have the repeated attribute = modify cell attr
                    } else if (orgColNum > modColNum) {
                        Element orgSplitCell = splitColRepeatedCell(
                                    orgCell, modColNum,
                                    orgColNum - modColNum);
                        // it may equal after the split!
                        if (orgSeq.equivalent(orgSplitCell, modCell)) {
                            needMerge = false;
                        }
                        orgCell = orgSplitCell;
                        modSeqObject = modSeq.next();

                    // cut the modified cell into 2 half, first half
                    // have the repeated attribute = original cell attr
                    } else {
                        Element modSplitCell = splitColRepeatedCell(
                                    modCell, orgColNum,
                                    modColNum - orgColNum);
                        // it may equal after the split!
                        if (modSeq.equivalent(orgCell, modSplitCell)) {
                            needMerge = false;
                        }
                        modCell = modSplitCell;
                        orgSeqObject = orgSeq.next();
                    }

                    if (needMerge) {
                        mergeCells(orgCell, modCell);
                    }

                } else {
                    // cells are equivalent, move on to next one.
                    orgSeqObject = orgSeq.next();
                    modSeqObject = modSeq.next();
                } // end if-else
            } // end if-else
        } // end while loop

        // get the one of the original cell, so that the cloned node
        // can base it to find the document node
        orgCell = (Element)orgSeq.start();

        // add any extra cells to the original cell sequence.
        for (; modSeqObject != null; modSeqObject = modSeq.next()) {
            Node clonedNode = XmlUtil.deepClone(orgCell, (Node)modSeqObject);
            Node parent = orgCell.getParentNode();
            parent.appendChild(clonedNode);
        }
    }


    private Element splitColRepeatedCell(Element orgCell,
                                         int splitNum, int orgNum) {
        // NOTE: should we really want to do deep clone?
        // in most the case, it is an empty cell, but the
        // specification didn't forbid any node to use multiple
        // column attributes. i.e. the node can contain text
        // nodes or other things under it.
        Element splitCell = (Element)(orgCell.cloneNode(true));

        if (splitNum > 1) {
            splitCell.setAttribute(
              OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED,
              String.valueOf(splitNum));
        } else if (splitNum == 1) {
            splitCell.removeAttribute(
              OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
        }
        if (orgNum > 1) {
            orgCell.setAttribute(
              OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED,
              String.valueOf(orgNum));
        } else if (orgNum == 1) {
            orgCell.removeAttribute(
              OfficeConstants.ATTRIBUTE_TABLE_NUM_COLUMNS_REPEATED);
        }

        Node parentNode = orgCell.getParentNode();
        parentNode.insertBefore(splitCell, orgCell);

        return splitCell;
    }


    private void mergeCells(Element orgCell, Element modCell) {

        // remove all the supported attributes and possible text child for
        // string cells
        SheetUtil.emptyCell(cc_, orgCell);

        // copy all the supported attributes and possible text child from
        // the modified cell
        NamedNodeMap attrNodes = modCell.getAttributes();

        if (attrNodes != null) {

            // copy the first text:p node. As it's not necessary only string
            // type cell can have a text:p section. 
            NodeList paraNodes =
                modCell.getElementsByTagName(OfficeConstants.TAG_PARAGRAPH);

            Node firstParaNode = paraNodes.item(0);

            // try to clone the node
            if (firstParaNode != null) {

                Node clonedNode = XmlUtil.deepClone(orgCell, firstParaNode);

                // insert as the first child of the original cell
                Node firstChild = orgCell.getFirstChild();
                if (firstChild != null) {
                    orgCell.insertBefore(clonedNode, firstChild);
                } else {
                    orgCell.appendChild(clonedNode);
                }
            }

            // check all the attributes and copy those we supported in
            // converter
            // NOTE: for attribute list, refer to section 4.7.2 in specification
            int len = attrNodes.getLength();

            for (int i = 0; i < len; i++) {
                Node attr = attrNodes.item(i);

                // copy the supported attrs
                if (cc_.canConvertAttribute(OfficeConstants.TAG_TABLE_CELL,
                                           attr.getNodeName())) {
                    orgCell.setAttribute(attr.getNodeName(),
                                         attr.getNodeValue());
                }
            }
        }
    }
}

