/*
 *  Copyright (C) 2006-2019  Ronald Blankendaal
 *
 *  This program 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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.dbgl.gui.controls;

import java.util.Arrays;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.gui.interfaces.DaControlConvertor;
import org.dbgl.service.TextService;
import org.dbgl.util.SystemUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ExpandItem;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;


public abstract class ChangeTrackingControl {

	private static final Color normalFieldColor = Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
	private static final Color conflictingValuesFieldColor = Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED);
	private static final Color changedFieldValueColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);

	private enum ChangeState {
		NORMAL, CONFLICTING, CHANGED
	}

	protected static final DaControlConvertor defaultConvertor = new DaControlConvertorAdapter() {
	};

	protected final Label label_;
	protected final Control[] controls_;
	protected boolean originalValueSet_;

	public ChangeTrackingControl(Label label, Control[] controls) {
		label_ = label;
		controls_ = controls;
	}

	public String getLabelText() {
		return label_.getText();
	}

	protected boolean allControlsDisabled() {
		return Stream.of(controls_).noneMatch(x -> x.isEnabled());
	}

	public String getOriginalValue() {
		return (String)label_.getData();
	}

	protected boolean setOriginalValue(String value, boolean multiEdit) {
		if (!originalValueSet_) {
			label_.setData(value);
			if (multiEdit) {
				updateLabelColorAndTooltip(value == null ? ChangeState.CONFLICTING: ChangeState.NORMAL);
				if (value == null) {
					Stream.of(controls_).filter(x -> x instanceof Button).forEach(x -> {
						((Button)x).setSelection(true);
						((Button)x).setGrayed(true);
					});
				}
			}
			originalValueSet_ = true;
			return true;
		}
		return false;
	}

	protected boolean hasConflictingValues() {
		return originalValueSet_ ? label_.getData() == null: false;
	}

	public boolean hasChangedValue() {
		return label_.getForeground().toString().equals(changedFieldValueColor.toString());
	}

	abstract public String getCurrentValue();

	protected boolean hasOriginalValue() {
		return StringUtils.equals(getOriginalValue(), getCurrentValue());
	}

	protected void updateLabelColorAndTooltipByValue() {
		updateLabelColorAndTooltip(hasOriginalValue() ? (hasConflictingValues() ? ChangeState.CONFLICTING: ChangeState.NORMAL): ChangeState.CHANGED);
	}

	protected void updateLabelColorAndTooltip(ChangeState state) {
		switch (state) {
			case NORMAL:
				label_.setForeground(normalFieldColor);
				label_.setToolTipText(TextService.getInstance().get("dialog.multiprofile.title.unalteredvalue"));
				break;
			case CONFLICTING:
				label_.setForeground(conflictingValuesFieldColor);
				label_.setToolTipText(TextService.getInstance().get("dialog.multiprofile.title.conflictingvalues"));
				break;
			case CHANGED:
				label_.setForeground(changedFieldValueColor);
				label_.setToolTipText(TextService.getInstance().get("dialog.multiprofile.title.alteredvalue"));
				break;
		}
	}

	public void addListeners() {
		final ModifyListener changeMarker = new ModifyListener() {
			public void modifyText(ModifyEvent event) {
				updateLabelColorAndTooltipByValue();
			}
		};

		final SelectionAdapter selectionMarker = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (event.widget instanceof Button) {
					if (hasConflictingValues()) {
						Button button = (Button)event.widget;
						if ((button.getStyle() & SWT.CHECK) == SWT.CHECK) {
							if (button.getSelection()) {
								if (button.getGrayed()) {
									button.setSelection(false);
								}
							} else {
								if (button.getGrayed()) {
									button.setGrayed(false);
								} else {
									button.setGrayed(true);
									button.setSelection(true);
								}
							}
						}
					}
				}
				updateLabelColorAndTooltipByValue();
			}
		};

		for (Control cntrl: controls_) {
			if (cntrl instanceof Combo)
				((Combo)cntrl).addModifyListener(changeMarker);
			else if (cntrl instanceof Text)
				((Text)cntrl).addModifyListener(changeMarker);
			else if (cntrl instanceof Button)
				((Button)cntrl).addSelectionListener(selectionMarker);
			else if (cntrl instanceof Spinner)
				((Spinner)cntrl).addModifyListener(changeMarker);
			else if (cntrl instanceof Scale)
				((Scale)cntrl).addSelectionListener(selectionMarker);
			else if (cntrl instanceof List)
				((List)cntrl).addSelectionListener(selectionMarker);
		}
	}

	protected static String getFieldValue(Control control, boolean isOnOff) {
		if (control.isEnabled()) {
			if (control instanceof Text) {
				String contents = ((Text)control).getText();
				String del = ((Text)control).getLineDelimiter();
				return StringUtils.replace(StringUtils.strip(contents, del), del, SystemUtils.EOLN);
			} else if (control instanceof Combo) {
				return ((Combo)control).getText();
			} else if (control instanceof Button && !((Button)control).getGrayed()) {
				boolean v = ((Button)control).getSelection();
				return isOnOff ? (v ? "on": "off"): String.valueOf(v);
			} else if (control instanceof Scale) {
				return String.valueOf(((Scale)control).getSelection());
			} else if (control instanceof Spinner) {
				return String.valueOf(((Spinner)control).getSelection());
			}
		}
		return null;
	}

	protected static void setFieldIfEnabled(String value, Control control, boolean isOnOff) {
		if (control.isEnabled()) {
			if (control instanceof Text) {
				String newValue = StringUtils.replace(value, SystemUtils.EOLN, ((Text)control).getLineDelimiter());
				if (!((Text)control).getText().equals(newValue))
					((Text)control).setText(newValue);
			} else if (control instanceof Combo) {
				if (!((Combo)control).getText().equals(value))
					((Combo)control).setText(value);
			} else if (control instanceof Button) {
				boolean newValue = isOnOff ? "on".equalsIgnoreCase(value): Boolean.valueOf(value);
				if ((((Button)control).getSelection() != newValue) || ((Button)control).getGrayed()) {
					((Button)control).setSelection(newValue);
					((Button)control).notifyListeners(SWT.Selection, new Event());
				}
			} else if (control instanceof Scale) {
				Integer newValue = Integer.valueOf(value);
				if (((Scale)control).getSelection() != newValue)
					((Scale)control).setSelection(newValue);
			} else if (control instanceof Spinner) {
				Integer newValue = Integer.valueOf(value);
				if (((Spinner)control).getSelection() != newValue)
					((Spinner)control).setSelection(newValue);
			}
		}
	}

	public static void setFieldIfEnabled(List listControl, String[] values) {
		if (listControl.isEnabled() && !Arrays.equals(listControl.getItems(), values))
			listControl.setItems(values);
	}

	public static void setFieldIfEnabled(ExpandItem control, boolean value) {
		control.setExpanded(value);
	}
}
