/**
 * Copyright (c) 2011-2021, JFXtras
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    Neither the name of the organization nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JFXTRAS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jfxtras.internal.scene.control.skin.agenda.base24hour;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.FormatStyle;
import java.util.Locale;

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.NodeOrientation;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Callback;
import jfxtras.internal.scene.control.skin.agenda.AgendaSkin;
import jfxtras.internal.scene.control.skin.agenda.base24hour.AppointmentAbstractPane.AppointmentForDrag;
import jfxtras.scene.control.agenda.Agenda;
import jfxtras.scene.control.agenda.Agenda.Appointment;

/**
 * This class is not a class but a data holder, a record, all fields are accessed directly.
 * Its methods are utility methods, which normally would be statics in a util class. 
 */
class LayoutHelp {
	public LayoutHelp(Agenda skinnable, AgendaSkin skin) {
		this.skinnable = skinnable;
		this.skin = skin;
		dragPane = new DragPane(this);
		
		// header
		titleDateTimeHeightProperty.bind( textHeightProperty.multiply(1.5) ); 
		appointmentHeaderPaneHeightProperty.bind( textHeightProperty.add(5) ); // not sure why the 5 is needed
		headerHeightProperty.bind( highestNumberOfWholedayAppointmentsProperty.multiply( appointmentHeaderPaneHeightProperty ).add( titleDateTimeHeightProperty ) );

		// day columns
		dayFirstColumnXProperty.bind( timeWidthProperty );
		dayContentWidthProperty.bind( dayWidthProperty.subtract(10) ); // the 10 is a margin at the right so that there is always room to start a new appointment
		
		// hour height
		dayHeightProperty.bind(hourHeighProperty.multiply(24));
		durationInMSPerPixelProperty.bind( msPerDayProperty.divide(dayHeightProperty) );
		
		// generic
		Text textHeight = new Text("X");
		textHeight.getStyleClass().add("Agenda");		
		textHeightProperty.set( textHeight.getBoundsInParent().getHeight() );
		
		// time column
		Text textWidth = new Text("88:88");
		textWidth.getStyleClass().add("Agenda");		
		timeWidthProperty.bind( timeColumnWhitespaceProperty.add( textWidth.getBoundsInParent().getWidth() )  );
	}
	final Agenda skinnable;
	final AgendaSkin skin;
	final DragPane dragPane;
	
	final DoubleProperty msPerDayProperty = new SimpleDoubleProperty(24 * 60 * 60 * 1000);
	
	final IntegerProperty highestNumberOfWholedayAppointmentsProperty = new SimpleIntegerProperty(0);
	
	final DoubleProperty paddingProperty = new SimpleDoubleProperty(3);
	final DoubleProperty timeColumnWhitespaceProperty = new SimpleDoubleProperty(10);
	final DoubleProperty wholedayAppointmentFlagpoleWidthProperty = new SimpleDoubleProperty(5);
	final DoubleProperty textHeightProperty = new SimpleDoubleProperty(0);
	final DoubleProperty titleDateTimeHeightProperty = new SimpleDoubleProperty(0);
	final DoubleProperty headerHeightProperty = new SimpleDoubleProperty(0);
	final DoubleProperty appointmentHeaderPaneHeightProperty = new SimpleDoubleProperty(0);
	final DoubleProperty timeWidthProperty = new SimpleDoubleProperty(0); 
	final DoubleProperty dayFirstColumnXProperty = new SimpleDoubleProperty(0); 
	final DoubleProperty dayWidthProperty = new SimpleDoubleProperty(0); 
	final DoubleProperty dayContentWidthProperty = new SimpleDoubleProperty(0); 
	final DoubleProperty dayHeightProperty = new SimpleDoubleProperty(0);  
	final DoubleProperty durationInMSPerPixelProperty = new SimpleDoubleProperty(0);
	final DoubleProperty hourHeighProperty = new SimpleDoubleProperty(0);
	
	SimpleDateFormat dayOfWeekDateFormat = new SimpleDateFormat("E", Locale.getDefault());
	DateTimeFormatter dayOfWeekDateTimeFormatter = new DateTimeFormatterBuilder().appendPattern("E").toFormatter(Locale.getDefault());
	SimpleDateFormat dateFormat = (SimpleDateFormat)SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT, Locale.getDefault());
	DateTimeFormatter dateDateTimeFormatter = new DateTimeFormatterBuilder().appendLocalized(FormatStyle.SHORT, null).toFormatter(Locale.getDefault());
	final static SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
	DateTimeFormatter timeDateTimeFormatter = new DateTimeFormatterBuilder().appendPattern("HH:mm").toFormatter(Locale.getDefault());

	/**
	 * I have no clue why the wholeday appointment header needs an additional 10.0 px X offset in right-to-left mode
	 */
	void clip(Pane pane, Text text, DoubleBinding width, DoubleBinding height, boolean mirrorWidth, double additionMirorXOffset) {
		Rectangle lClip = new Rectangle(0,0,0,0);
		if (mirrorWidth && skinnable.getNodeOrientation().equals(NodeOrientation.RIGHT_TO_LEFT)) {
			lClip.xProperty().bind(pane.widthProperty().multiply(-1.0).add(text.getBoundsInParent().getWidth()).add(additionMirorXOffset));
		}
		lClip.widthProperty().bind(width);
		lClip.heightProperty().bind(height);
		text.setClip(lClip);
	}

	/**
	 * 
	 */
	void setupMouseOverAsBusy(final Node node) {
		// play with the mouse pointer to show something can be done here
		node.setOnMouseEntered( (mouseEvent) -> {
			if (!mouseEvent.isPrimaryButtonDown()) {						
				node.setCursor(Cursor.HAND);
				mouseEvent.consume();
			}
		});
		node.setOnMouseExited( (mouseEvent) -> {
			if (!mouseEvent.isPrimaryButtonDown()) {
				node.setCursor(Cursor.DEFAULT);
				mouseEvent.consume();
			}
		});
	}

	/**
	 * 
	 * @param localDateTime
	 * @param minutes
	 * @return
	 */
	LocalDateTime roundTimeToNearestMinutes(LocalDateTime localDateTime, int minutes)
	{
		localDateTime = localDateTime.withSecond(0).withNano(0);
		int lMinutes = localDateTime.getMinute() % minutes;
		if (lMinutes < (minutes/2)) {
			localDateTime = localDateTime.plusMinutes(-1 * lMinutes);
		}
		else {
			localDateTime = localDateTime.plusMinutes(minutes - lMinutes);
		}
		return localDateTime;
	}
	
    /**
     * Has the client added a callback to process the change?
     * @param appointment
     */
	void callAppointmentChangedCallback(Appointment appointment) {
		// ignore temp appointments
		if (!(appointment instanceof AppointmentAbstractPane.AppointmentForDrag)) {
		    Callback<Appointment, Void> lChangedCallback = skinnable.getAppointmentChangedCallback();
		    if (lChangedCallback != null) {
		        lChangedCallback.call(appointment);
		    }
		}
	}

}
