/*
 *
 *  Copyright (C) 2021, 2022 Andrew Gegg
 *
 * 	This file is part of the Gardeners Notebook application
 *
 *  The Gardeners Notebook application is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 *
 */

/*
	Change log
    2.9.6	First version
    3.0.4   needSave handling
 */

package uk.co.gardennotebook.fxbean;

import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.spi.*;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * This class provides common handling for CommentEditing in the various *Bean classes.
 *
 * @author Andy Gegg
 *	@version	3.0.4
 *	@since	2.9.6
 */

final class BeanCommentHandler<T extends INotebookEntry>
{
    private static final Logger LOGGER = LogManager.getLogger();

    private final INotebookBean parent;
    private final T baseItem;

    private ObservableList<CommentBean> childrenComments = null;
    private final ReadOnlyStringWrapper commentTextProperty = new ReadOnlyStringWrapper(this, "commentText", "");	//	2.9.6
    private List<CommentCopy> originalComments; // = new ArrayList<>();	//	2.9.6

    private boolean needSave = false;

    BeanCommentHandler(INotebookBean parent, T baseItem)
    {
        this.parent = parent;
        this.baseItem = baseItem;

        initialiseChildrenComments();
        copyComments();
    }

    /**
     * Returns the current list of comments
     *
     * @return  the current list of comments
     */
    public ObservableList<CommentBean> getComments()
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("getComments()");

//		LOGGER.debug("comments: {}", childrenComments);
        return LOGGER.traceExit(log4jEntryMsg, FXCollections.unmodifiableObservableList(childrenComments));
    }

    /**
     *  A displayable Comment list.
     *
     * @return  a String containing the Comments in a form suitable for display,
     *          i.e. each comment on a new line, with the date at the start separated from the text by a tab.
     */
    ReadOnlyStringProperty commentTextProperty()
    {
        setCommentTextProperty();
        return commentTextProperty.getReadOnlyProperty();
    }


    /**
     * Check if any changes have been made.
     *
      * @return true if changes have been made and need to be saved.
     */
    boolean needSave()
    {
        return needSave;
    }

    /**
     *  Add a new comment.
     *
     *  WARNING - using this method to create a Comment will result in a duplicate comment
     *              To create a new comment use:
     *              CommentBean cb = new CommentBean(parent);
     *              cb.setComment("string>");
     *
     * @param text  -   the text of a comment to be added
     *
     * @return  a new CommentBean containing text as its comment text
     */
    CommentBean addComment(final String text)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("addComment(String): {}", text);
        if (text == null || text.isBlank()) return null;	//	2.9.6

//        LOGGER.debug("before create CommentBean");
        CommentBean cb = new CommentBean(parent);
//        LOGGER.debug("after create CommentBean");
        cb.setComment(text);
//        LOGGER.debug("after set text");
        childrenComments.add(cb);
//        LOGGER.debug("after add to childrenComments");
        setCommentTextProperty();
//        LOGGER.debug("after setCommentText");

        needSave = true;

        return LOGGER.traceExit(log4jEntryMsg, cb);
    }	//	addComment()

    /**
     * Add a CommentBean passed as a new Comment.
     *
     * @param comment   -   the comment to be added.
     */
    void addComment(CommentBean comment)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("addComment(comment bean): comment: {}, text : {}", comment, comment==null ? "null" : comment.getComment());
        if (comment == null) return;
        if (comment.getComment() == null || comment.getComment().isBlank()) return;

        if (childrenComments.contains(comment)) return;

        childrenComments.add(comment);
        setCommentTextProperty();

        needSave = true;

        LOGGER.debug("addComment(comment bean): commentTextProperty: {}", commentTextProperty::get);

        LOGGER.traceExit(log4jEntryMsg);
    }

    /**
     * Change the comment text of the given comment.
     *
     * @param comment   -   the comment to be changed
     * @param text  -   the new comment text
     */
    void changeCommentText(final CommentBean comment, final String text)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("changeCommentText(): comment={}, text={}", comment, text);
        if (text == null || text.isBlank()) return;

        if (comment == null)
        {
            addComment(text);
            return;
        }

        comment.setComment(text);

        if (!childrenComments.contains(comment))
            childrenComments.add(comment);

        setCommentTextProperty();

        needSave = true;

        LOGGER.traceExit(log4jEntryMsg);
    }	//	changeCommentText()

    /**
     * Change the date of the given comment.
     *
     * @param comment   -   the comment to be changed.
     * @param date  -   the new date
     */
    void changeCommentDate(CommentBean comment, final LocalDate date)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("changeCommentDate(): comment={}, date={}", comment, date);
        if (date == null) return;

        if (comment == null)
        {
            return;
        }

        comment.setDate(date);
        if (!childrenComments.contains(comment))
            childrenComments.add(comment);

        setCommentTextProperty();

        needSave = true;

        LOGGER.traceExit(log4jEntryMsg);
    }	//	changeCommentDate()

    /**
     * Delete the given comment.
     *
     * @param comment   -   the comment to be deleted.
     */
    void deleteComment(CommentBean comment)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("deleteComment(): comment={}", comment);
        if (comment == null) return;

        //	2.9.6
        childrenComments.remove(comment);
        setCommentTextProperty();

        needSave = true;

        if (comment.isNew()) return;

        LOGGER.traceExit(log4jEntryMsg);
    }	//	deleteComment()

    private void setCommentTextProperty()
    {
        String sb = "";
        for (CommentBean cb : childrenComments)
        {
            sb += cb.getDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));
            sb += "\t";
            sb += cb.getComment();
            sb += "\n";
        }
        if (!sb.isEmpty())
        {
            sb = sb.substring(0, sb.length() - 1);
        }
        commentTextProperty.set(sb);
    }

    void saveComments(Consumer<CommentBean> adder,
                      Consumer<CommentBean> textChanger,
                      Consumer<CommentBean> dateChanger,
                      Consumer<CommentBean> deleter)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("saveComments()");

        for (var cb : childrenComments)
        {
            LOGGER.debug("childrenComment: {}", childrenComments);
            LOGGER.debug("originalComments: {}", originalComments);
            LOGGER.debug("this comment: {}", cb.getComment());
            if (cb.isNew())
            {
                LOGGER.debug("adding comment");
                adder.accept(cb);
            }
            else
            {
                boolean gotIt = false;
//                LOGGER.debug("originalComments: {}", originalComments);
                for (var copy : originalComments)
                {
                    if  (copy.id() == cb.getKey())
                    {
                        LOGGER.debug("match found: cb.text: {}, copy.text: {}", cb.getComment(), copy.comment());
                        LOGGER.debug("match found: cb.date: {}, copy.date: {}", cb.getDate(), copy.date());
                        gotIt = true;
                        if (!copy.comment().equals(cb.getComment()))
                        {
                            textChanger.accept(cb);
                        }
                        if (!copy.date().equals(cb.getDate()))
                        {
                            LOGGER.debug("date change");
                            dateChanger.accept(cb);
                        }
                        break;
                    }
                }
                if (!gotIt)
                    deleter.accept(cb);
            }
        }

        needSave = false;

    }

    private void initialiseChildrenComments()
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("initialiseChildrenComments()");
        childrenComments = FXCollections.observableArrayList();

        if (parent == null) // can happen if the underlying item is deleted, the *Bean sets default values
        {
            return;
        }
        if (parent.isNew()) return;

//        LOGGER.debug("initialiseChildrenComments(): before adding comments: baseItem.getComments(): {}", baseItem.getComments());
        for (IComment ix : baseItem.getComments())
        {
            CommentBean cb = new CommentBean(parent, ix);
            childrenComments.add(cb);
        }
    }

    private void copyComments()
    {
        originalComments = new ArrayList<>();
        for ( var cb : childrenComments)
        {
            originalComments.add(new CommentCopy(cb.getKey(), cb.getDate(), cb.getComment()));
        }
    }

    private record CommentCopy(int id, LocalDate date, String comment) {}

}
