/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/
package com.sun.star.wizards.agenda;

import java.util.Calendar;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;


import com.sun.star.awt.TextEvent;
import com.sun.star.beans.PropertyValue;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.container.XIndexAccess;
import com.sun.star.container.XNamed;
import com.sun.star.document.XDocumentProperties;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XTerminateListener;
import com.sun.star.i18n.NumberFormatIndex;
import com.sun.star.lang.Locale;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.table.XCell;
import com.sun.star.table.XTableRows;
import com.sun.star.text.*;
import com.sun.star.uno.Any;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.XNumberFormatsSupplier;
import com.sun.star.util.XNumberFormatter;
import com.sun.star.util.XSearchDescriptor;
import com.sun.star.util.XSearchable;
import com.sun.star.wizards.common.FileAccess;
import com.sun.star.wizards.common.Helper;
import com.sun.star.wizards.common.JavaTools;
import com.sun.star.wizards.common.NumberFormatter;
import com.sun.star.wizards.document.OfficeDocument;
import com.sun.star.wizards.text.TextDocument;
import com.sun.star.wizards.text.TextSectionHandler;
import com.sun.star.wizards.ui.UnoDialog2;
import com.sun.star.wizards.ui.event.DataAware;

/**
 *
 * The classes here implement the whole document-functionality of the agenda wizard:
 * the live-preview and the final "creation" of the document, when the user clicks "finish". <br/>
 * <br/>
 * <h2>Some terminology:<h2/>
 * items are names or headings. we don't make any distinction.
 *
 * <br/>
 * The Agenda Template is used as general "controller" of the whole document, whereas the
 * two child-classes ItemsTable and TopicsTable control the item tables (note plural!) and the
 * topics table (note singular).
 * <br/>   <br/>
 * Other small classes are used to abstract the handling of cells and text and we
 * try to use them as components.
 * <br/><br/>
 * We tried to keep the Agenda Template as flexible as possible, though there
 * must be many limitations, because it is generated dynamically.<br/><br/>
 * To keep the template flexible the following decisions were made:<br/>
 * 1. Item tables.<br/>
 * 1.a. there might be arbitrary number of Item tables.<br/>
 * 1.b. Item tables design (bordewr, background) is arbitrary.<br/>
 * 1.c. Items text styles are individual, and use stylelist styles with predefined names.<br/>
 * As result the following limitations:<br/>
 * Pairs of Name->value for each item.<br/>
 * Tables contain *only* those pairs.<br/>
 * 2. Topics table.<br/>
 * 2.a. arbitrary structure.<br/>
 * 2.b. design is arbitrary.<br/>
 * As result the following limitations:<br/>
 * No column merge is allowed.<br/>
 * One compolsary Heading row.<br/>
 * <br/><br/>
 * To let the template be flexible, we use a kind of "detection": we look where
 * the items are read the design of each table, reaplying it after writing the
 * table.
 * <br/><br/>
 * A note about threads:<br/>
 * Many methods here are synchronized, in order to avoid colission made by
 * events fired too often.
 * @author rpiterman
 *
 */
public class AgendaTemplate extends TextDocument implements TemplateConsts, DataAware.Listener
{

    /**
     * resources.
     */
    AgendaWizardDialogResources resources;
    /**
     * data model. This keeps the status of the agenda document, and
     * every redraw is done according to this data.
     * Exception: topic data is written programatically, event-oriented.
     */
    CGAgenda agenda;
    /**
     * the UNO Text Document serrvice
     */
    Object document;
    /**
     * Service Factory
     */
    XMultiServiceFactory docMSF;
    /**
     * The template-filename of the current template.
     * Since we often re-link section and the break the link,
     * inorder to restore them, we need a template to link to.
     * This is practically an identicall copy of the current template.
     */
    String template;
    /**
     * used for common operations on sections.
     */
    TextSectionHandler textSectionHandler;
    /**
     * a component loader.
     */
    XComponentLoader xComponentLoader;
    /**
     * an array containing all ItemTable object (which control each an Items
     * Table in the document.
     */
    ItemsTable[] itemsTables;
    /**
     * the controller of the topics table.
     */
    Topics topics;
    /**
     *  Stores reusable OOo Placeholder TextFields to insert to the document.
     */
    Map itemsCache;
    /**
     * This map is used to find which tables contains a certain Item, so
     * the keys are the different Items, the Objects are the ItemTable controllers.
     * When an Item must be redrawn (because the user checked or uncheced it),
     * the controller is retrieved from this Map, and a redraw is issued on this controller.
     */
    Map itemsMap = new Hashtable(11);
    /**
     * A temporary variable used to list all items and map them.
     */
    List _allItems = new Vector();
    /**
     * keep a reference on some static items in the document,
     * so when their content is changed (through the user), we
     * can just reference them and set their text.
     */
    TextElement teTitle, teDate, teTime, teLocation;
    XTextRange trTitle, trDate, trTime, trLocation;
    /**
     * used to format the date / time.
     */
    int dateFormat, timeFormat;
    XNumberFormatter dateFormatter, timeFormatter;
    /**
     * used to transfare time from VCL to UNO.
     */
    long docNullTime;
    Calendar calendar;
    /**
     * used to set the document title property (step 6).
     */
    private XDocumentProperties m_xDocProps;

    /**
     * loads the given template, and analyze its structure.
     * @param templateURL
     * @param topics
     * @see AgendaTemplate.initialize()
     * @see AgendaTemplate.initializeData()
     */
    public synchronized void load(String templateURL, List topics)
    {
        template = calcTemplateName(templateURL);
        document = loadAsPreview(templateURL, false);
        docMSF = ((XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, document));
        xFrame.getComponentWindow().setEnable(false);
        xTextDocument.lockControllers();
        initialize();
        initializeData(topics);
        xTextDocument.unlockControllers();
    }

    /**
     * The agenda templates are in format of aw-XXX.ott
     * the templates name is then XXX.ott.
     * This method calculates it.
     * @param url
     * @return the template name without the "aw-" at the beginning.
     */
    private String calcTemplateName(String url)
    {
        return FileAccess.connectURLs(FileAccess.getParentDir(url), FileAccess.getFilename(url).substring(3));
    }

    /**
     * synchronize the document to the model.<br/>
     * this method rewrites all titles, item tables , and the topics table-
     * thus synchronizing the document to the data model (CGAgenda).
     * @param topicsData since the model does not contain Topics
     * information (it is only actualized on save) the given list
     * supplies this information.
     */
    private void initializeData(List topicsData)
    {
        for (int i = 0; i < itemsTables.length; i++)
        {
            try
            {
                itemsTables[i].write("");
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        redrawTitle("txtTitle");
        redrawTitle("txtDate");
        redrawTitle("txtTime");
        redrawTitle("cbLocation");

        topics.writeAll(topicsData);

        setTemplateTitle(agenda.cp_TemplateName);

    }

    /**
     * redraws/rewrites the table which contains the given item
     * This method is called when the user checks/unchecks an item.
     * The table is being found, in which the item is, and redrawn.
     * @param itemName
     */
    public synchronized void redraw(String itemName)
    {
        try
        {
            // get the table in which the item is...
            Object itemsTable =
                    itemsMap.get(itemName);
            // rewrite the table.
            ((ItemsTable) itemsTable).write(null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * update the documents title property to the given title
     * @param newTitle new title.
     */
    synchronized void setTemplateTitle(String newTitle)
    {
        m_xDocProps.setTitle(newTitle);
    }

    /**
     * constructor. The document is *not* loaded here.
     * only some formal members are set.
     * @param xmsf_ service factory.
     * @param agenda_ the data model (CGAgenda)
     * @param resources_ resources.
     */
    AgendaTemplate(XMultiServiceFactory xmsf_, CGAgenda agenda_, AgendaWizardDialogResources resources_, XTerminateListener listener)
    {
        super(xmsf_, listener, "WIZARD_LIVE_PREVIEW");

        agenda = agenda_;
        resources = resources_;

        if (itemsCache == null)
        {
            initItemsCache();
        }
        _allItems = null;

    }

    /**
     * checks the data model if the
     * item corresponding to the given string should be shown
     * @param itemName a string representing an Item (name or heading).
     * @return true if the model specifies that the item should be displayed.
     */
    boolean isShowItem(String itemName)
    {
        if (itemName.equals(FILLIN_MEETING_TYPE))
        {
            return agenda.cp_ShowMeetingType;
        }
        else if (itemName.equals(FILLIN_READ))
        {
            return agenda.cp_ShowRead;
        }
        else if (itemName.equals(FILLIN_BRING))
        {
            return agenda.cp_ShowBring;
        }
        else if (itemName.equals(FILLIN_NOTES))
        {
            return agenda.cp_ShowNotes;
        }
        else if (itemName.equals(FILLIN_FACILITATOR))
        {
            return agenda.cp_ShowFacilitator;
        }
        else if (itemName.equals(FILLIN_TIMEKEEPER))
        {
            return agenda.cp_ShowTimekeeper;
        }
        else if (itemName.equals(FILLIN_NOTETAKER))
        {
            return agenda.cp_ShowNotetaker;
        }
        else if (itemName.equals(FILLIN_PARTICIPANTS))
        {
            return agenda.cp_ShowAttendees;
        }
        else if (itemName.equals(FILLIN_CALLED_BY))
        {
            return agenda.cp_ShowCalledBy;
        }
        else if (itemName.equals(FILLIN_OBSERVERS))
        {
            return agenda.cp_ShowObservers;
        }
        else if (itemName.equals(FILLIN_RESOURCE_PERSONS))
        {
            return agenda.cp_ShowResourcePersons;
        }
        else
        {
            throw new IllegalArgumentException("No such item");
        }
    }

    /**
     * itemsCache is a Map containing all agenda item. These are object which
     * "write themselfs" to the table, given a table cursor.
     * A cache is used in order to reuse the objects, instead of recreate them.
     * This method fills the cache will all items objects (names and headings).
     */
    private void initItemsCache()
    {
        itemsCache = new Hashtable(11);

        XMultiServiceFactory xmsf = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, document);
        // Headings

        itemsCache.put(FILLIN_MEETING_TYPE,
                new AgendaItem(FILLIN_MEETING_TYPE, new TextElement(resources.itemMeetingType, STYLE_MEETING_TYPE),
                new PlaceholderElement(STYLE_MEETING_TYPE_TEXT, resources.reschkMeetingTitle_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_BRING,
                new AgendaItem(FILLIN_BRING, new TextElement(resources.itemBring, STYLE_BRING),
                new PlaceholderElement(STYLE_BRING_TEXT, resources.reschkBring_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_READ,
                new AgendaItem(FILLIN_READ, new TextElement(resources.itemRead, STYLE_READ),
                new PlaceholderElement(STYLE_READ_TEXT, resources.reschkRead_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_NOTES,
                new AgendaItem(FILLIN_NOTES, new TextElement(resources.itemNote, STYLE_NOTES),
                new PlaceholderElement(STYLE_NOTES_TEXT, resources.reschkNotes_value, resources.resPlaceHolderHint, xmsf)));

        // Names

        itemsCache.put(FILLIN_CALLED_BY,
                new AgendaItem(FILLIN_CALLED_BY, new TextElement(resources.itemCalledBy, STYLE_CALLED_BY),
                new PlaceholderElement(STYLE_CALLED_BY_TEXT, resources.reschkConvenedBy_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_FACILITATOR,
                new AgendaItem(FILLIN_FACILITATOR, new TextElement(resources.itemFacilitator, STYLE_FACILITATOR),
                new PlaceholderElement(STYLE_FACILITATOR_TEXT, resources.reschkPresiding_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_PARTICIPANTS,
                new AgendaItem(FILLIN_PARTICIPANTS, new TextElement(resources.itemAttendees, STYLE_PARTICIPANTS),
                new PlaceholderElement(STYLE_PARTICIPANTS_TEXT, resources.reschkAttendees_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_NOTETAKER,
                new AgendaItem(FILLIN_NOTETAKER, new TextElement(resources.itemNotetaker, STYLE_NOTETAKER),
                new PlaceholderElement(STYLE_NOTETAKER_TEXT, resources.reschkNoteTaker_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_TIMEKEEPER,
                new AgendaItem(FILLIN_TIMEKEEPER, new TextElement(resources.itemTimekeeper, STYLE_TIMEKEEPER),
                new PlaceholderElement(STYLE_TIMEKEEPER_TEXT, resources.reschkTimekeeper_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_OBSERVERS,
                new AgendaItem(FILLIN_OBSERVERS, new TextElement(resources.itemObservers, STYLE_OBSERVERS),
                new PlaceholderElement(STYLE_OBSERVERS_TEXT, resources.reschkObservers_value, resources.resPlaceHolderHint, xmsf)));

        itemsCache.put(FILLIN_RESOURCE_PERSONS,
                new AgendaItem(FILLIN_RESOURCE_PERSONS, new TextElement(resources.itemResource, STYLE_RESOURCE_PERSONS),
                new PlaceholderElement(STYLE_RESOURCE_PERSONS_TEXT, resources.reschkResourcePersons_value, resources.resPlaceHolderHint, xmsf)));

    }

    /**
     * Initializes a template.<br/>
     * This method does the following tasks:<br/>
     * Get a Time and Date format for the document, and retrieve the null date of the document (which is
     * document-specific).<br/>
     * Initializes the Items Cache map.
     * Analyses the document:<br/>
     * -find all "fille-ins" (apear as &gt;xxx&lt; in the document).
     * -analyze all items sections (and the tables in them).
     * -locate the titles and actualize them
     * -analyze the topics table
     */
    private void initialize()
    {
        /*
         * Get the default locale of the document, and create the date and time formatters.
         */
        XMultiServiceFactory docMSF = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, document);
        try
        {
            Object defaults = docMSF.createInstance("com.sun.star.text.Defaults");
            Locale l = (Locale) Helper.getUnoStructValue(defaults, "CharLocale");

            java.util.Locale jl = new java.util.Locale(
                    l.Language, l.Country, l.Variant);

            calendar = Calendar.getInstance(jl);

            XNumberFormatsSupplier nfs = (XNumberFormatsSupplier) UnoRuntime.queryInterface(XNumberFormatsSupplier.class, document);
            Object formatSettings = nfs.getNumberFormatSettings();
            com.sun.star.util.Date date = (com.sun.star.util.Date) Helper.getUnoPropertyValue(formatSettings, "NullDate");

            calendar.set(date.Year, date.Month - 1, date.Day);

            docNullTime = JavaTools.getTimeInMillis(calendar);

            dateFormat = NumberFormatter.getNumberFormatterKey(nfs, NumberFormatIndex.DATE_SYSTEM_LONG);
            timeFormat = NumberFormatter.getNumberFormatterKey(nfs, NumberFormatIndex.TIME_HHMM);


            dateFormatter = NumberFormatter.createNumberFormatter(xMSF, nfs);
            timeFormatter = NumberFormatter.createNumberFormatter(xMSF, nfs);
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            throw new NullPointerException("Fatal Error: could not initialize locale or date/time formats.");
        }

        /*
         * get the document properties object.
         */
        m_xDocProps = OfficeDocument.getDocumentProperties(document);

        initItemsCache();
        initializeItems();
        initializeTitles();
        initializeItemsSections();
        XMultiServiceFactory xMultiServiceFactory = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, document);
        textSectionHandler = new TextSectionHandler(xMultiServiceFactory, (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class, document));
        initializeTopics();
        _allItems.clear();
        _allItems = null;
    }

    /**
     * locates the titles (name, location, date, time) and saves a reference to thier Text ranges.
     *
     */
    private void initializeTitles()
    {
        XTextRange item = null;

        XMultiServiceFactory xmsf = (XMultiServiceFactory) UnoRuntime.queryInterface(XMultiServiceFactory.class, document);

        for (int i = 0; i < _allItems.size(); i++)
        {
            item = (XTextRange) _allItems.get(i);
            String text = item.getString().trim().toLowerCase();
            if (text.equals(FILLIN_TITLE))
            {

                teTitle = new PlaceholderTextElement(item, resources.resPlaceHolderTitle, resources.resPlaceHolderHint, xmsf);
                trTitle = item;
                _allItems.remove(i--);
            }
            else if (text.equals(FILLIN_DATE))
            {
                teDate = new PlaceholderTextElement(item, resources.resPlaceHolderDate, resources.resPlaceHolderHint, xmsf);
                trDate = item;
                _allItems.remove(i--);
            }
            else if (text.equals(FILLIN_TIME))
            {
                teTime = new PlaceholderTextElement(item, resources.resPlaceHolderTime, resources.resPlaceHolderHint, xmsf);
                trTime = item;
                _allItems.remove(i--);
            }
            else if (text.equals(FILLIN_LOCATION))
            {
                teLocation = new PlaceholderTextElement(item, resources.resPlaceHolderLocation, resources.resPlaceHolderHint, xmsf);
                trLocation = item;
                _allItems.remove(i--);
            }
        }
    }

    private void initializeTopics()
    {
        topics = new Topics();
    }

    private void initializeItems()
    {
        _allItems = searchFillInItems();
    }

    /**
     * searches the document for items in the format "&gt;*&lt;"
     * @return a vector containing the XTextRanges of the found items
     */
    private List searchFillInItems()
    {
        try
        {
            XSearchable xSearchable = (XSearchable) UnoRuntime.queryInterface(XSearchable.class, document);
            XSearchDescriptor sd = xSearchable.createSearchDescriptor();
            sd.setSearchString("<[^>]+>");
            sd.setPropertyValue("SearchRegularExpression", Boolean.TRUE);
            sd.setPropertyValue("SearchWords", Boolean.TRUE);

            XIndexAccess ia = xSearchable.findAll(sd);

            List l = new Vector(ia.getCount());
            for (int i = 0; i < ia.getCount(); i++)
            {
                try
                {
                    l.add((XTextRange) UnoRuntime.queryInterface(XTextRange.class, ia.getByIndex(i)));
                }
                catch (Exception ex)
                {
                    System.err.println("Nonfatal Error in finding fillins.");
                }
            }
            return l;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            throw new IllegalArgumentException("Fatal Error: Loading template failed: searching fillins failed");
        }
    }

    /**
     * analyze the item sections in the template. delegates the analyze of each table to the
     * ItemsTable class.
     */
    private void initializeItemsSections()
    {
        String[] sections = getSections(document, TemplateConsts.SECTION_ITEMS);

        // for each section - there is a table...
        itemsTables = new ItemsTable[sections.length];

        for (int i = 0; i < itemsTables.length; i++)
        {
            try
            {
                itemsTables[i] = new ItemsTable(getSection(sections[i]), getTable(sections[i]));
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
                throw new IllegalArgumentException("Fatal Error while initialilzing Template: items table in section " + sections[i]);
            }
        }

    }

    private String[] getSections(Object document, String s)
    {
        XTextSectionsSupplier xTextSectionsSupplier = (XTextSectionsSupplier) UnoRuntime.queryInterface(XTextSectionsSupplier.class, document);
        String[] allSections = xTextSectionsSupplier.getTextSections().getElementNames();
        return getNamesWhichStartWith(allSections, s);
    }

    Object getSection(String name) throws NoSuchElementException, WrappedTargetException
    {
        XTextSectionsSupplier xTextSectionsSupplier = (XTextSectionsSupplier) UnoRuntime.queryInterface(XTextSectionsSupplier.class, document);
        return ((Any) (xTextSectionsSupplier.getTextSections().getByName(name))).getObject();
    }

    Object getTable(String name) throws NoSuchElementException, WrappedTargetException
    {
        XTextTablesSupplier xTextTablesSupplier = (XTextTablesSupplier) UnoRuntime.queryInterface(XTextTablesSupplier.class, document);
        return ((Any) xTextTablesSupplier.getTextTables().getByName(name)).getObject();
    }

    /**
     * implementation of DataAware.Listener, is
     * called when title/date/time or location are
     * changed.
     */
    public synchronized void eventPerformed(Object param)
    {
        TextEvent te = (TextEvent) param;
        String controlName = (String) Helper.getUnoPropertyValue(
                UnoDialog2.getModel(te.Source),
                "Name");
        redrawTitle(controlName);

    }

    private synchronized void redrawTitle(String controlName)
    {
        if (controlName.equals("txtTitle"))
        {
            writeTitle(teTitle, trTitle, agenda.cp_Title);
        }
        else if (controlName.equals("txtDate"))
        {
            writeTitle(teDate, trDate, getDateString(agenda.cp_Date));
        }
        else if (controlName.equals("txtTime"))
        {
            writeTitle(teTime, trTime, getTimeString(agenda.cp_Time));
        }
        else if (controlName.equals("cbLocation"))
        {
            writeTitle(teLocation, trLocation, agenda.cp_Location);
        }
        else
        {
            throw new IllegalArgumentException("No such title control...");
        }
    }

    private void writeTitle(TextElement te, XTextRange tr, String text)
    {
        te.text = (text == null ? "" : text);
        te.write(tr);
    }
    private static long DAY_IN_MILLIS = (24 * 60 * 60 * 1000);

    private String getDateString(String d)
    {
        if (d == null || d.equals(""))
        {
            return "";
        }
        int date = new Integer(d).intValue();
        calendar.clear();
        calendar.set(date / 10000,
                (date % 10000) / 100 - 1,
                date % 100);

        long date1 = JavaTools.getTimeInMillis(calendar);
        /*
         * docNullTime and date1 are in millis, but
         * I need a day...
         */
        double daysDiff = (date1 - docNullTime) / DAY_IN_MILLIS + 1;

        return dateFormatter.convertNumberToString(dateFormat, daysDiff);
    }

    private String getTimeString(String s)
    {
        if (s == null || s.equals(""))
        {
            return "";
        }
        int time = new Integer(s).intValue();

        double t = ((double) (time / 1000000) / 24) + ((double) ((time % 1000000) / 1000) / (24 * 60));
        return timeFormatter.convertNumberToString(timeFormat, t);
    }

    /* *******************************************
     *  F I N I S H
     *********************************************/
    /** the user clicked finish **/
    public synchronized void finish(List topics)
    {
        createMinutes(topics);
        deleteHiddenSections();
        textSectionHandler.removeAllTextSections();
    }

    /**
     * hidden sections exist when an item's section is hidden because the
     * user specified not to display any items which it contains.
     * When finishing the wizard removes this sections entireley from the document.
     */
    private void deleteHiddenSections()
    {
        XTextSectionsSupplier xTextSectionsSupplier = (XTextSectionsSupplier) UnoRuntime.queryInterface(XTextSectionsSupplier.class, document);
        String[] allSections = xTextSectionsSupplier.getTextSections().getElementNames();
        try
        {
            for (int i = 0; i < allSections.length; i++)
            {
                Object section = getSection(allSections[i]);
                //Try3.showProps(section);
                boolean visible = ((Boolean) Helper.getUnoPropertyValue(section, "IsVisible")).booleanValue();
                if (!visible)
                {
                    ((XTextContent) UnoRuntime.queryInterface(XTextContent.class, section)).getAnchor().setString("");
                }
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }

    /**
     * create the minutes for the given topics or remove the minutes section from the document.
     * If no topics are supplied, or the user
     * specified not to create minuts, the minutes section will be removed,
     * @param topicsData supplies PropertyValue arrays containing the values for the topics.
     */
    public synchronized void createMinutes(List topicsData)
    {

        // if the minutes section should be removed (the
        // user did not check "create minutes")
        if (!agenda.cp_IncludeMinutes || (topicsData.size() <= 1))
        {
            try
            {
                Object minutesAllSection = getSection(SECTION_MINUTES_ALL);
                XTextSection xTextSection = (XTextSection) UnoRuntime.queryInterface(XTextSection.class, minutesAllSection);
                xTextSection.getAnchor().setString("");
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        // the user checked "create minutes"
        else
        {
            try
            {
                String itemText;
                XTextRange item;
                int topicStartTime = 0;
                try
                {
                    topicStartTime = new Integer(agenda.cp_Time).intValue();
                }
                catch (Exception ex)
                {
                }

                String time;

                // first I replace the minutes titles...
                List items = searchFillInItems();
                for (int itemIndex = 0; itemIndex < items.size(); itemIndex++)
                {
                    item = (XTextRange) items.get(itemIndex);
                    itemText = item.getString().trim().toLowerCase();

                    if (itemText.equals(FILLIN_MINUTES_TITLE))
                    {
                        fillMinutesItem(item, agenda.cp_Title, resources.resPlaceHolderTitle);
                    }
                    else if (itemText.equals(FILLIN_MINUTES_LOCATION))
                    {
                        fillMinutesItem(item, agenda.cp_Location, resources.resPlaceHolderLocation);
                    }
                    else if (itemText.equals(FILLIN_MINUTES_DATE))
                    {
                        fillMinutesItem(item, getDateString(agenda.cp_Date), resources.resPlaceHolderDate);
                    }
                    else if (itemText.equals(FILLIN_MINUTES_TIME))
                    {
                        fillMinutesItem(item, getTimeString(agenda.cp_Time), resources.resPlaceHolderTime);
                    }
                }

                items.clear();

                /*
                 * now add minutes for each topic.
                 * The template contains *one* minutes section, so
                 * we first use the one available, and then add a new one...
                 *
                 * topics data has *always* an empty topic at the end...
                 */
                for (int i = 0; i < topicsData.size() - 1; i++)
                {
                    PropertyValue[] topic = (PropertyValue[]) topicsData.get(i);

                    items = searchFillInItems();
                    for (int itemIndex = 0; itemIndex < items.size(); itemIndex++)
                    {
                        item = (XTextRange) items.get(itemIndex);
                        itemText = item.getString().trim().toLowerCase();

                        if (itemText.equals(FILLIN_MINUTE_NUM))
                        {
                            fillMinutesItem(item, topic[0].Value, "");
                        }
                        else if (itemText.equals(FILLIN_MINUTE_TOPIC))
                        {
                            fillMinutesItem(item, topic[1].Value, "");
                        }
                        else if (itemText.equals(FILLIN_MINUTE_RESPONSIBLE))
                        {
                            fillMinutesItem(item, topic[2].Value, "");
                        }
                        else if (itemText.equals(FILLIN_MINUTE_TIME))
                        {
                            int topicTime = 0;

                            try
                            {
                                topicTime = (new Integer((String) topic[3].Value)).intValue();
                            }
                            catch (Exception ex)
                            {
                            }
                            // if the topic has no time, we do not display any time here.
                            if (topicTime == 0 || topicStartTime == 0)
                            {
                                time = (String) topic[3].Value;
                            }
                            else
                            {
                                time = getTimeString(String.valueOf(topicStartTime)) + " - ";
                                topicStartTime += topicTime * 1000;
                                time += getTimeString(String.valueOf(topicStartTime));
                            }
                            fillMinutesItem(item, time, "");
                        }
                    }

                    textSectionHandler.removeTextSectionbyName(SECTION_MINUTES);

                    // after the last section we do not insert a new one.
                    if (i < topicsData.size() - 2)
                    {
                        textSectionHandler.insertTextSection(SECTION_MINUTES, template, false);
                    }
                }
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }

    /**
     * given a text range and a text, fills the given
     * text range with the given text.
     * If the given text is empty, uses a placeholder with the giveb placeholder text.
     * @param range text range to fill
     * @param text the text to fill to the text range object.
     * @param placeholder the placeholder text to use, if the text argument is empty (null or "")
     */
    private void fillMinutesItem(XTextRange range, Object text, String placeholder)
    {
        String paraStyle = (String) Helper.getUnoPropertyValue(range, "ParaStyleName");
        range.setString((String) text);
        Helper.setUnoPropertyValue(range, "ParaStyleName", paraStyle);
        if (text == null || text.equals(""))
        {
            if (placeholder != null && !placeholder.equals(""))
            {
                XTextContent placeHolder = createPlaceHolder(docMSF, placeholder, resources.resPlaceHolderHint);
                try
                {
                    range.getStart().getText().insertTextContent(range.getStart(), placeHolder, true);
                }
                catch (Exception ex)
                {
                    ex.printStackTrace();
                }
            }

        }
    }

    /**
     * creates a placeholder field with the given text and given hint.
     * @param xmsf service factory
     * @param ph place holder text
     * @param hint hint text
     * @return the place holder field.
     */
    public static XTextContent createPlaceHolder(XMultiServiceFactory xmsf, String ph, String hint)
    {
        Object placeHolder;
        try
        {
            placeHolder = xmsf.createInstance("com.sun.star.text.TextField.JumpEdit");
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return null;
        }
        Helper.setUnoPropertyValue(placeHolder, "PlaceHolder", ph);
        Helper.setUnoPropertyValue(placeHolder, "Hint", hint);
        Helper.setUnoPropertyValue(placeHolder, "PlaceHolderType", new Short(PlaceholderType.TEXT));
        return (XTextContent) UnoRuntime.queryInterface(XTextContent.class, placeHolder);

    }

    /*
     * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
     * =================================
     *  The ItemTable class
     * =================================
     * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
     */
    public class ItemsTable
    {

        Object table;
        Object section;
        /**
         * the items in the table.
         */
        List items = new Vector(6);

        public ItemsTable(Object section_, Object table_)
        {

            table = table_;
            section = section_;

            AgendaItem ai;
            XTextRange item;
            String iText;

            /* go through all <*> items in the document
             * and each one if it is in this table.
             * If they are, register them to belong here, notice their order
             * and remove them from the list of all <*> items, so the next
             * search will be faster.
             */
            for (int i = 0; i < _allItems.size(); i++)
            {
                item = (XTextRange) _allItems.get(i);
                Object t = Helper.getUnoPropertyValue(item, "TextTable");
                if ((t instanceof Any) && ((Any) t).getObject() == table)
                {
                    iText = item.getString().toLowerCase().trim();
                    ai = (AgendaItem) itemsCache.get(item.getString().toLowerCase().trim());
                    if (ai != null)
                    {
                        items.add(ai);
                        _allItems.remove(i--);
                        itemsMap.put(iText, this);
                    }
                }
            }

        }

        /**
         * link the section to the template. this will restore the original table
         * with all the items.<br/>
         * then break the link, to make the section editable.<br/>
         * then, starting at cell one, write all items that should be visible.
         * then clear the rest and remove obsolete rows.
         * If no items are visible, hide the section.
         * @param dummy we need a param to make this an Implementation of AgendaElement.
         * @throws Exception
         */
        public synchronized void write(Object dummy) throws Exception
        {
            synchronized(this)
            {
                String name = getName(section);

                // link and unlink the section to the template.
                textSectionHandler.linkSectiontoTemplate(section, template, name);
                textSectionHandler.breakLinkOfTextSection(section);

                // we need to get a new instance after linking.
                table = getTable(name);
                section = getSection(name);

                XTextTable xTextTable = (XTextTable) UnoRuntime.queryInterface(XTextTable.class, table);
                XTextTableCursor cursor = xTextTable.createCursorByCellName("A1");
                AgendaItem ai;
                // should this section be visible?
                boolean visible = false;

                // write items
                // ===========
                String cellName = "";

                /* now go through all items that belong to this
                 * table. Check each one agains the model. If it should
                 * be display, call it's write method.
                 * All items are of type AgendaItem which means they write
                 * two cells to the table: a title (text) and a placeholder.
                 * see AgendaItem class below.
                 */
                for (int i = 0; i < items.size(); i++)
                {
                    ai = (AgendaItem) items.get(i);
                    if (isShowItem(ai.name))
                    {
                        visible = true;
                        ai.table = table;
                        ai.write(cursor);
                        // I store the cell name which was last written...
                        cellName = cursor.getRangeName();

                        cursor.goRight((short) 1, false);

                    }
                }

                Helper.setUnoPropertyValue(section, "IsVisible", visible ? Boolean.TRUE : Boolean.FALSE);
                if (!visible)
                {
                    return;
                /* remove obsolete rows
                 * ====================
                 * if the cell that was last written is the current cell,
                 * it means this is the end of the table, so we end here.
                 * (because after getting the cellName above, I call the goRight method.
                 * If it did not go right, it means its the last cell.
                 */
                }
                if (cellName.equals(cursor.getRangeName()))
                {
                    return;
                /*
                 * if not, we continue and clear all cells until we are at the end of the row.
                 */
                }
                Object cell;
                while ((!cellName.equals(cursor.getRangeName()) && (!cursor.getRangeName().startsWith("A"))))
                {
                    cell = xTextTable.getCellByName(cursor.getRangeName());
                    ((XTextRange) UnoRuntime.queryInterface(XTextRange.class, cell)).setString("");
                    cellName = cursor.getRangeName();
                    cursor.goRight((short) 1, false);
                }

                /*
                 * again: if we are at the end of the table, end here.
                 */
                if (cellName.equals(cursor.getRangeName()))
                {
                    return;
                }
                int rowIndex = getRowIndex(cursor);
                int rowsCount = getRowCount((XTextTable) UnoRuntime.queryInterface(XTextTable.class, table));

                /* now before deleteing i move the cursor up so it
                 * does not disappear, because it will crash office.
                 */
                cursor.gotoStart(false);

                if (rowsCount >= rowIndex)
                {
                    removeTableRows(table, rowIndex - 1, (rowsCount - rowIndex) + 1);
                }
            }
        }
    }

    /*
     * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
     * =================================
     *  The Topics class
     * =================================
     * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
     */
    /**
     * This class handles the preview of the topics table.
     * You can call it the controller of the topics table.
     * It differs from ItemsTable in that it has no data model -
     * the update is done programttically.<br/>
     * <br/>
     * The decision to make this class a class by its own
     * was done out of logic reasons and not design/functionality reasons,
     * since there is anyway only one instance of this class at runtime
     * it could have also be implemented in the AgendaTemplate class
     * but for clarity and separation I decided to make a sub class for it.
     *
     * @author rp143992
     */
    public class Topics
    {

        /**
         * the topics table
         */
        XTextTable table;
        /**
         * A List of Cell Formatters for the first row.
         */
        List firstRowFormat = new Vector();
        /**
         * A List of Cell Formatters for the last row.
         * (will contain them in reverse order)
         */
        List lastRowFormat = new Vector();
        /**
         * the format of the cell of each topic cell.
         */
        List topicCellFormats = new Vector();
        /**
         * for each topic cell there is
         * a member in this vector
         */
        List topicCells = new Vector();
        int rowsPerTopic;
        /**
         * fields which hold the number of the
         * fillins in the cells vectors.
         */
        int numCell = -1;
        int topicCell = -1;
        int responsibleCell = -1;
        int timeCell = -1;
        /**
         * this is a list which traces which topics were written to the document
         * and which not. When a cell needs to be actualized, it is checked that the
         * whole topic is already present in the document, using this vector.
         * The vector contains nulls for topics which were not written, and
         * empty strings for topics which were written (though any other
         * object would also do - i check only if it is a null or not...);
         */
        List writtenTopics = new Vector();

        /**
         * Analyze the structure of the Topics table.
         * The structure Must be as follows:<br>
         * -One Header Row. <br>
         * -arbitrary number of rows per topic <br>
         * -arbitrary content in the topics row <br>
         * -only soft formatting will be restored. <br>
         * -the topic rows must repeat three times. <br>
         * -in the topics rows, placeholders for number, topic, responsible, and duration
         * must be placed.<br>
         * <br>
         * A word about table format: to reconstruct the format of the
         * table we hold to the following formats: first row (header), topic, and last row.
         * We hold the format of the last row, because one might wish to give it
         * a special format, other than the one on the bottom of each topic.
         * The left and right borders of the whole table are, on the other side,
         * part of the topics rows format, and need not be preserved seperateley.
         */
        public Topics()
        {
            Object t;

            Map topicItems = new Hashtable(4);

            // This is the topics table. say hallo :-)
            try
            {
                t = getTable(SECTION_TOPICS);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
                throw new IllegalArgumentException("Fatal error while loading template: table " + SECTION_TOPICS + " could not load.");
            }

            // and this is the XTable.
            table = ((XTextTable) UnoRuntime.queryInterface(XTextTable.class, t));

            /* first I store all <*> ranges
             * which are in the topics table.
             * I store each <*> range in this - the key
             * is the cell it is in. Later when analyzing the topic,
             * cell by cell, I check in this map to know
             * if a cell contains a <*> or not.
             */
            Hashtable items = new Hashtable();

            XTextRange item;
            Object cell;
            for (int i = 0; i < _allItems.size(); i++)
            {
                item = (XTextRange) _allItems.get(i);
                t = Helper.getUnoPropertyValue(item, "TextTable");
                if ((t instanceof Any) && ((Any) t).getObject() == table)
                {
                    cell = Helper.getUnoPropertyValue(item, "Cell");
                    items.put(((Any) cell).getObject(), item);
                }
            }

            /*
             * in the topics table, there are always one
             * title row and three topics defined.
             * So no mutter how many rows a topic takes - we
             * can restore its structure and format.
             */
            int rows = getRowCount(table);

            rowsPerTopic = (rows - 1) / 3;

            String firstCell = "A" + (1 + rowsPerTopic + 1);
            String afterLastCell = "A" + (1 + (rowsPerTopic * 2) + 1);

            // go to the first row of the 2. topic
            XTextTableCursor cursor = table.createCursorByCellName(firstCell);
            XTextRange range;

            // analyze the structure of the topic rows.
            while (!cursor.getRangeName().equals(afterLastCell))
            {
                cell = table.getCellByName(cursor.getRangeName());
                XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, cell);
                // first I store the content and para style of the cell
                AgendaElement ae = new TextElement(xTextRange);
                // if the cell contains a relevant <...>
                // i add the text element to the hash,
                // so it's text can be updated later.
                range = (XTextRange) items.get(cell);
                if (range != null)
                {
                    topicItems.put(xTextRange.getString().toLowerCase().trim(), ae);
                }

                topicCells.add(ae);

                // and store the format of the cell.
                topicCellFormats.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName())));

                // goto next cell.
                cursor.goRight((short) 1, false);
            }

            /*
             * now - in which cell is every fillin?
             */
            numCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_NUMBER));
            topicCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_TOPIC));
            responsibleCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_RESPONSIBLE));
            timeCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_TIME));



            /* now that we know how the topics look like,
             * we get the format of the first and last rows.
             */

            // format of first row
            cursor.gotoStart(false);
            do
            {
                firstRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName())));
                cursor.goRight((short) 1, false);
            }
            while (!cursor.getRangeName().startsWith("A"));

            // format of the last row
            cursor.gotoEnd(false);
            while (!cursor.getRangeName().startsWith("A"))
            {
                lastRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName())));
                cursor.goLeft((short) 1, false);
            }
            // we missed the A cell - so we have to add it also..
            lastRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName())));

            removeTableRows(table, 1 + rowsPerTopic, rows - rowsPerTopic - 1);

        }

        /**
         * @param topic the topic number to write
         * @param data the data of the topic.
         * @return the number of rows that have been added
         * to the table. 0 or a negative number: no rows added.
         */
        private int write2(int topic, PropertyValue[] data) throws Exception
        {
            while (topic >= writtenTopics.size())
            {
                writtenTopics.add(null);
            }
            writtenTopics.set(topic, "");

            // make sure threr are enough rows for me...
            int rows = getRowCount(table);
            int reqRows = 1 + (topic + 1) * rowsPerTopic;
            int firstRow = reqRows - rowsPerTopic + 1;
            int diff = reqRows - rows;
            if (diff > 0)
            {
                insertTableRows(table, rows, diff);            // set the item's text...
            }
            setItemText(numCell, data[0].Value);
            setItemText(topicCell, data[1].Value);
            setItemText(responsibleCell, data[2].Value);
            setItemText(timeCell, data[3].Value);

            // now write !
            XTextTableCursor cursor = table.createCursorByCellName("A" + firstRow);

            for (int i = 0; i < topicCells.size(); i++)
            {
                ((AgendaElement) topicCells.get(i)).write(table.getCellByName(cursor.getRangeName()));
                cursor.goRight((short) 1, false);
            }

            // now format !
            cursor.gotoCellByName("A" + firstRow, false);

            formatTable(cursor, topicCellFormats, false);

            return diff;

        }

        /**
         * check if the topic with the given index is written to the table.
         * @param topic the topic number (0 base)
         * @return true if the topic is already written to the table. False if not.
         * (false would mean new rows must be added to the table in order to
         * be able to write this topic).
         */
        private boolean isWritten(int topic)
        {
            return (writtenTopics.size() > topic && writtenTopics.get(topic) != null);
        }

        /**
         * rewrites a single cell containing.
         * This is used in order to refresh the topic/responsible/duration data in the
         * preview document, in response to a change in the gui (by the user).
         * Since the structure of the topics table is flexible, we don't reference a cell
         * number. Rather, we use "what" argument to specify which cell should be redrawn.
         * The Topics object, which analyzed the structure of the topics table appon
         * initialization, refreshes the approperiate cell.
         * @param topic index of the topic (0 based).
         * @param what 0 for num, 1 for topic, 2 for responsible, 3 for duration
         * @param data the row's data.
         * @throws Exception if something goes wrong (thow nothing should)
         */
        public void writeCell(int topic, int what, PropertyValue[] data) throws Exception
        {
            // if the whole row should be written...
            if (!isWritten(topic))
            {
                write(topic, data);
            // write only the "what" cell.
            }
            else
            {
                // calculate the table row.
                int firstRow = 1 + (topic * rowsPerTopic) + 1;
                // go to the first cell of this topic.
                XTextTableCursor cursor = table.createCursorByCellName("A" + firstRow);

                TextElement te = null;
                int cursorMoves = 0;

                switch (what)
                {
                    case 0:
                        te = setItemText(numCell, data[0].Value);
                        cursorMoves = numCell;
                        break;
                    case 1:
                        te = setItemText(topicCell, data[1].Value);
                        cursorMoves = topicCell;
                        break;
                    case 2:
                        te = setItemText(responsibleCell, data[2].Value);
                        cursorMoves = responsibleCell;
                        break;
                    case 3:
                        te = setItemText(timeCell, data[3].Value);
                        cursorMoves = timeCell;
                        break;
                }
                // move the cursor to the needed cell...
                cursor.goRight((short) cursorMoves, false);
                XCell xc = table.getCellByName(cursor.getRangeName());
                // and write it !
                te.write(xc);
                ((TableCellFormatter) topicCellFormats.get(cursorMoves)).format(xc);

            }
        }

        /**
         * writes the given topic.
         * if the first topic was involved, reformat the
         * first row.
         * If any rows were added to the table, reformat
         * the last row.
         * @param topic the index of the topic to write.
         * @param data the topic's data. (see TopicsControl
         * for explanation about the topics data model)
         * @throws Exception if something goes wrong (though nothing should).
         */
        public void write(int topic, PropertyValue[] data) throws Exception
        {
            int diff = write2(topic, data);
            /* if the first topic has been written,
             * one needs to reformat the first row.
             */
            if (topic == 0)
            {
                formatFirstRow();
            }
            /*
             * if any rows were added, one needs to format
             * the whole table again.
             */
            if (diff > 0)
            {
                formatLastRow();
            }
        }

        /**
         * Writes all the topics to thetopics table.
         * @param topicsData a List containing all Topic's Data.
         */
        public void writeAll(List topicsData)
        {
            try
            {
                for (int i = 0; i < topicsData.size() - 1; i++)
                {
                    write2(i, (PropertyValue[]) topicsData.get(i));
                }
                formatLastRow();
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }

        /**
         * removes obsolete rows, reducing the
         * topics table to the given number of topics.
         * Note this method does only reducing - if
         * the number of topics given is greater than the
         * number of actuall topics it does *not* add
         * new rows !
         * Note also that the first topic will never be removed.
         * If the table contains no topics, the whole section will
         * be removed uppon finishing.
         * The reason for that is a "table-design" one: the first topic is
         * maintained in order to be able to add rows with a design of this topic,
         * and not of the header row.
         * @param topics the number of topics the table should contain.
         * @throws Exception
         */
        public void reduceDocumentTo(int topics) throws Exception
        {
            // we never remove the first topic...
            if (topics <= 0)
            {
                topics = 1;
            }
            XTableRows tableRows = table.getRows();
            int targetNumOfRows = topics * rowsPerTopic + 1;
            if (tableRows.getCount() > targetNumOfRows)
            {
                tableRows.removeByIndex(targetNumOfRows, tableRows.getCount() - targetNumOfRows);
            }
            formatLastRow();
            while (writtenTopics.size() > topics)
            {
                writtenTopics.remove(topics);
            }
        }

        /**
         * reapply the format of the first (header) row.
         */
        private void formatFirstRow()
        {
            XTextTableCursor cursor = table.createCursorByCellName("A1");
            formatTable(cursor, firstRowFormat, false);
        }

        /**
         * reaply the format of the last row.
         */
        private void formatLastRow()
        {
            XTextTableCursor cursor = table.createCursorByCellName("A1");
            cursor.gotoEnd(false);
            formatTable(cursor, lastRowFormat, true);
        }

        /**
         * returns a text element for the given cell,
         * which will write the given text.
         * @param cell the topics cell number.
         * @param value the value to write.
         * @return a TextElement object which will write the given value
         * to the given cell.
         */
        private TextElement setItemText(int cell, Object value)
        {
            if (cell >= 0)
            {
                TextElement te = ((TextElement) topicCells.get(cell));
                if (te != null)
                {
                    te.text = value.toString();
                }
                return te;
            }
            return null;
        }

        /**
         * formats a series of cells from the given one,
         * using the given List of TableCellFormatter objects,
         * in the given order.
         * This method is used to format the first (header) and the last
         * rows of the table.
         * @param cursor a table cursor, pointing to the start cell to format
         * @param formats a List containing TableCellFormatter objects. Each will format one cell in the direction specified.
         * @param reverse if true the cursor will move left, formatting in reverse order (used for the last row).
         */
        private void formatTable(XTextTableCursor cursor, List formats, boolean reverse)
        {
            for (int i = 0; i < formats.size(); i++)
            {
                ((TableCellFormatter) formats.get(i)).format(table.getCellByName(cursor.getRangeName()));
                if (reverse)
                {
                    cursor.goLeft((short) 1, false);
                }
                else
                {
                    cursor.goRight((short) 1, false);
                }
            }
        }
    }


    /*
     * =================================
     * Here are some static help methods
     * =================================
     */
    public static String[] getNamesWhichStartWith(String[] allNames, String prefix)
    {
        Vector v = new Vector();
        for (int i = 0; i < allNames.length; i++)
        {
            if (allNames[i].startsWith(prefix))
            {
                v.add(allNames[i]);
            }
        }
        String[] s = new String[v.size()];
        System.arraycopy(v.toArray(), 0, s, 0, s.length);
        return s;
    }

    /**
     * Convenience method, costs the given object to an XNamed, and returnes its name.
     * @param obj an XNamed object.
     * @return the name of the given object.
     */
    public static String getName(Object obj)
    {
        return ((XNamed) UnoRuntime.queryInterface(XNamed.class, obj)).getName();
    }

    /**
     * convenience method, for removing a number of cells from a table.
     * @param table
     * @param start
     * @param count
     */
    public static void removeTableRows(Object table, int start, int count)
    {
        XTableRows rows = ((XTextTable) UnoRuntime.queryInterface(XTextTable.class, table)).getRows();
        rows.removeByIndex(start, count);
    }

    /**
     * Convenience method for inserting some cells into a table.
     * @param table
     * @param start
     * @param count
     */
    public static void insertTableRows(Object table, int start, int count)
    {
        XTableRows rows = ((XTextTable) UnoRuntime.queryInterface(XTextTable.class, table)).getRows();
        rows.insertByIndex(start, count);
    }

    /**
     * returns the row index for this cursor, assuming
     * the cursor points to a single cell.
     * @param cursor
     * @return the row index in which the cursor is.
     */
    public static int getRowIndex(XTextTableCursor cursor)
    {
        return getRowIndex(cursor.getRangeName());
    }

    /**
     * returns the row index for this cell name.
     * @param cellName
     * @return the row index for this cell name.
     */
    public static int getRowIndex(String cellName)
    {
        return Integer.parseInt(cellName.substring(1));
    }

    /**
     * returns the rows count of this table, assuming
     * there is no vertical merged cells.
     * @param table
     * @return the rows count of the given table.
     */
    public static int getRowCount(XTextTable table)
    {
        String[] cells = table.getCellNames();
        return getRowIndex(cells[cells.length - 1]);
    }
}

/*
 * ===========================================================================================
 *
 *                  End of AgendaTempalte class
 *
 * ===========================================================================================
 *
 */
/*
 * =================================
 *  The AgendaElement interface
 * =================================
 */
/**
 * Interface that is used for writing content to a Uno Text / TextRange
 * @author rp143992
 *
 */
interface AgendaElement
{

    void write(Object any) throws Exception;
}


/*
 * =================================
 *  The ParaStyled class
 * =================================
 */
/**
 * Basic implementation of the AgendaElement interface -
 * writes nothing, but applies a ParaStyle to the given XText/XTextRange
 * @author rp143992
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
class ParaStyled implements AgendaElement
{

    String paraStyle;

    ParaStyled(String paraStyle_)
    {
        paraStyle = paraStyle_;
    }

    void format(Object textRange)
    {
        XText o;
        o = ((XText) UnoRuntime.queryInterface(XText.class, textRange));
        if (o == null)
        {
            o = ((XTextRange) UnoRuntime.queryInterface(XTextRange.class, textRange)).getText();
        }
        XTextRange xtr = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, textRange);
        XTextCursor cursor = o.createTextCursorByRange(xtr);

        Helper.setUnoPropertyValue(cursor, "ParaStyleName", paraStyle);
    }

    public void write(Object textRange)
    {
        format(textRange);
    }
}

/*
 * =================================
 *  The TextElement class
 * =================================
 */
/**
 * A basic implementation of AgendaElement:
 * writes a String to the given XText/XTextRange, and applies
 * a ParaStyle to it (using the parent class).
 * @author rp143992
 */
class TextElement extends ParaStyled
{

    String text;

    TextElement(XTextRange range)
    {
        this(range.getString(), (String) Helper.getUnoPropertyValue(range.getStart(), "ParaStyleName"));
    }

    TextElement(String text_, String paraStyle_)
    {
        super(paraStyle_);
        text = text_;
    }

    public void write(Object textRange)
    {
        ((XTextRange) UnoRuntime.queryInterface(XTextRange.class, textRange)).setString(text);
        if (!text.equals(""))
        {
            super.write(textRange);
        }
    }
}

/**
 * A Text element which, if the text to write is empty (null or "")
 * inserts a placeholder instead.
 * @author rp143992
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
class PlaceholderTextElement extends TextElement
{

    String hint;
    String placeHolderText;
    XMultiServiceFactory xmsf;

    PlaceholderTextElement(XTextRange textRange, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_)
    {
        super(textRange);
        placeHolderText = placeHolderText_;
        hint = hint_;
        xmsf = xmsf_;
    }

    PlaceholderTextElement(String text, String paraStyle, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_)
    {
        super(text, paraStyle);
        placeHolderText = placeHolderText_;
        hint = hint_;
        xmsf = xmsf_;
    }

    public void write(Object textRange)
    {
        super.write(textRange);
        if (text == null || text.equals(""))
        {
            XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, textRange);
            try
            {
                XTextContent xTextContent = AgendaTemplate.createPlaceHolder(xmsf, placeHolderText, hint);
                xTextRange.getText().insertTextContent(xTextRange.getStart(), xTextContent, true);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
}

/*
 * =================================
 *  The PlaceHolder class
 * =================================
 */
/**
 * An Agenda element which writes no text, but inserts a placeholder, and formats
 * it using a ParaStyleName.
 * @author rp143992
 *
 */
class PlaceholderElement extends ParaStyled
{

    String hint;
    String placeHolderText;
    XMultiServiceFactory xmsf;

    PlaceholderElement(String paraStyle, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_)
    {
        super(paraStyle);
        placeHolderText = placeHolderText_;
        hint = hint_;
        xmsf = xmsf_;
    }

    public void write(Object textRange)
    {
        XTextRange xTextRange = (XTextRange) UnoRuntime.queryInterface(XTextRange.class, textRange);
        try
        {
            XTextContent xTextContent = AgendaTemplate.createPlaceHolder(xmsf, placeHolderText, hint);
            xTextRange.getText().insertTextContent(xTextRange.getStart(), xTextContent, true);
            super.write(textRange);
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}


/*
 * =================================
 *  The AgendaItem class
 * =================================
 */
/**
 * An implementation of AgendaElement which
 * gets as a parameter a table cursor, and writes
 * a text to the cell marked by this table cursor, and
 * a place holder to the next cell.
 * @author rp143992
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
class AgendaItem implements AgendaElement
{

    TextElement textElement;
    AgendaElement field;
    public Object table;
    String name;

    AgendaItem(String name_, TextElement te, AgendaElement f)
    {
        name = name_;
        field = f;
        textElement = te;
    }

    public void write(Object tableCursor) throws Exception
    {
        XTextTableCursor xTextTableCursor = (XTextTableCursor) UnoRuntime.queryInterface(XTextTableCursor.class, tableCursor);
        XTextTable xTextTable = (XTextTable) UnoRuntime.queryInterface(XTextTable.class, table);

        String cellname = xTextTableCursor.getRangeName();
        Object cell = xTextTable.getCellByName(cellname);

        textElement.write(cell);

        xTextTableCursor.goRight((short) 1, false);

        //second field is actually always null...
        // this is a preparation for adding placeholders.
        if (field != null)
        {
            field.write(xTextTable.getCellByName(xTextTableCursor.getRangeName()));
        }
    }
}

/*
 * =================================
 *  The TableCellFormatter class
 * =================================
 */
/**
 * reads/write a table cell format from/to a table cell or a group of cells.
 *
 */
class TableCellFormatter
{

    static String[] properties = new String[]
    {
        "BackColor",
        "BackTransparent",
        "BorderDistance",
        "BottomBorder",
        "BottomBorderDistance",
        "LeftBorder",
        "LeftBorderDistance",
        "RightBorder",
        "RightBorderDistance",
        "TopBorder",
        "TopBorderDistance"
    };
    private Object[] values = new Object[properties.length];

    public TableCellFormatter(Object tableCell)
    {
        for (int i = 0; i < properties.length; i++)
        {
            values[i] = Helper.getUnoPropertyValue(tableCell, properties[i]);
        }
    }

    public void format(Object tableCell)
    {
        Helper.setUnoPropertyValues(tableCell, properties, values);
    }
}




