/*
 *
 *  Copyright (C) 2021 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>.
 *
 *
 */

package uk.co.gardennotebook.mysql;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.spi.GNDBException;
import uk.co.gardennotebook.spi.IComment;
//import uk.co.gardennotebook.spi.IWeatherConditionBuilder;
import uk.co.gardennotebook.spi.NotebookEntryType;

import java.time.LocalDate;
import java.util.*;

/**
 *
 *  Handle Comment addition/change/delete
 *
 *	@author	Andy Gegg
 *	@version	3.0.0
 *	@since	3.0.0
 */
final class DBCommentHandler
{
    private static final Logger LOGGER = LogManager.getLogger();

    private List<String> newComments;
    private int[] deletedComments;
    private int delComNext = 0;
    private boolean changedComments = false;

    private final Map<Integer, CommentChangeList> editComments = new HashMap<>();

    final NotebookEntryType nbType;
    Integer parentId = null;    //  id of the owning item

    private final boolean newInstance;

    /**
     * constructor when the owning item is being created
     *
     * @param nb    the type of the owning item
     */
    DBCommentHandler(NotebookEntryType nb)
    {
        this(nb, null);
    }

    /**
     * Constructor to use when changing comments on an existing item
     *
     * @param nb    the type of the owning item
     * @param parentId  the DB key of the owning item
     */
    DBCommentHandler(NotebookEntryType nb, Integer parentId)
    {
        nbType = nb;
        this.parentId = parentId;
        newInstance = (parentId == null);

    }

    /**
     * For a new item, the parentId must be set before the comment changes can be saved
     *
     * @param parentId  the DB key of the new owning item
     */
    void setParentId(Integer parentId)
    {
        this.parentId = parentId;
    }

    boolean isChangedComments()
    {
        return changedComments;
    }

    void addComment(final String... newVals)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("addComment[array](): {}", newVals);

        if (newVals == null) return;
        this.addComment(Arrays.asList(newVals));
        LOGGER.traceExit();
        return;
    }

    void addComment(final List<String> newVals)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("addComment<list>(): {}", newVals);

        if (newVals == null) return;
        if (newVals.isEmpty()) return;
        if (this.newComments == null) this.newComments = new ArrayList<>();
        ArrayList<String> temp = new ArrayList<>(newVals);
        temp.removeIf(s -> (s == null || s.isEmpty()));
        this.newComments.addAll(temp);
        changedComments = true;
        LOGGER.traceExit("addComment");
    }

    /**
     *	remove a comment from this item
     *
     *	@param	newVals	the comment to remove.  If the comment does not exist, this is a null-op
     */
    void deleteComment(int... newVals)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("deleteComment()");

        if (deletedComments == null)
        {
            deletedComments = new int[10];
        }
        if (delComNext + newVals.length >= deletedComments.length)
        {
            deletedComments = Arrays.copyOf(deletedComments, deletedComments.length + newVals.length + 10);
        }
        for (int newVal : newVals)
        {
            if (newVal > 0)
            {
                deletedComments[delComNext++] = newVal;
            }
        }
        changedComments = true;
        LOGGER.traceExit(log4jEntryMsg);
        return;
    }

    void deleteComment(final IComment... newVals)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("deleteComment()");

        if (newVals == null) return;
        if (newInstance) return;
        int[] keys = new int[newVals.length];
        int keyCount = 0;
        for (IComment cmnt : newVals) {
            if (cmnt == null) continue;
            if (cmnt.getOwnerType() != nbType) continue;
            if (!cmnt.getOwnerKey().equals(parentId)) continue;

            Integer ky = cmnt.getKey();
            if (ky == null) continue;
            if (ky > 0) keys[keyCount++] = ky;
        }
        if (keyCount == 0) return;
        keys = Arrays.copyOf(keys, keyCount);	// trim array to actual size - should be a null-op
        this.deleteComment(keys);
        LOGGER.traceExit(log4jEntryMsg);
    }

    void deleteComment(final List<IComment> vals)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("deleteComment()");

        if (vals == null) return;
        this.deleteComment(vals.toArray(new IComment[0]));
        LOGGER.traceExit(log4jEntryMsg);
    }

    void changeComment(final IComment base, final String comment)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("changeComment(): base: {}, comment: {}", base, comment);

        if (base == null) return;
        if (newInstance) return;
        if (base.getOwnerType() != nbType) return;
        if (!base.getOwnerKey().equals(parentId)) return;

        if (comment == null || comment.isEmpty()) return;

        if (comment.equals(base.getComment())) return;

        changeComment(base, null, comment);
        LOGGER.traceExit(log4jEntryMsg);
    }

    void changeComment(final IComment base, final LocalDate date)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("changeComment(): base: {}, date: {}", base, date);

        if (base == null) return;
        if (newInstance) return;
        if (base.getOwnerType() != nbType) return;
        if (!base.getOwnerKey().equals(parentId)) return;

        if (date == null || date.isAfter(LocalDate.now())) return;
        if (date.equals(base.getDate())) return;

        changeComment(base, date, null);
        LOGGER.traceExit(log4jEntryMsg);
    }

    void changeComment(final IComment base, final LocalDate date, final String comment)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("changeComment(): base: {}, date: {}, comment: {}", base, date, comment);

        if (base == null) return;
        if (newInstance) return;
        if (base.getOwnerType() != nbType) return;
        if (!base.getOwnerKey().equals(parentId)) return;

        if (date == null && (comment == null || comment.isEmpty())) return;
        if (date != null && date.isAfter(LocalDate.now())) return;

        Integer key = base.getKey();
        if (key == null) return;
        if (key < 0) return;

        //	If we're here at least date or comment has been given
        //	Check something's changed
        if (	comment != null && comment.equals(base.getComment()) &&
                date != null && date.equals(base.getDate())	)return;
        editComments.putIfAbsent(key, new CommentChangeList(key, null, null));
        if (date != null) editComments.get(key).date = date;
        if (comment != null && !comment.isEmpty()) editComments.get(key).comment = comment;
        changedComments = true;
        LOGGER.traceExit(log4jEntryMsg);
        return;
    }

    void save() throws GNDBException
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("save()");

        if (!changedComments) return;
        if (parentId == null || parentId <= 0)
        {
            throw new IllegalArgumentException("parentId invalid");
        }

        CommentBuilder commB = new CommentBuilder(nbType, parentId);
        if (newComments != null && !newComments.isEmpty())
        {	//save new comments to database
            commB.addComment(null, newComments.toArray(new String[0]));
        }

        if (!editComments.isEmpty())
        {	//modify old comments from database
            commB.changeComment(editComments.values());
        }
        if (deletedComments != null && delComNext != 0)
        {	//delete old comments from database
            deletedComments = Arrays.copyOf(deletedComments, delComNext);
            commB.removeComment(deletedComments);
        }

        newComments = null;
        deletedComments = null;
        delComNext = 0;
        changedComments = false;

        editComments.clear();

    }

}
