package org.areasy.common.parser.excel.biff;

/*
 * Copyright (c) 2007-2020 AREasy Runtime
 *
 * This library, AREasy Runtime and API for BMC Remedy AR System, is free software ("Licensed Software");
 * you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either version 2.1 of the License,
 * or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * including but not limited to, the implied warranty of MERCHANTABILITY, NONINFRINGEMENT,
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 */

import org.areasy.common.logger.Logger;
import org.areasy.common.logger.LoggerFactory;
import org.areasy.common.parser.excel.CellReferenceHelper;
import org.areasy.common.parser.excel.Range;
import org.areasy.common.parser.excel.biff.drawing.ComboBox;
import org.areasy.common.parser.excel.biff.drawing.Comment;
import org.areasy.common.parser.excel.common.Assert;
import org.areasy.common.parser.excel.write.biff.CellValue;

import java.util.Collection;

/**
 * Container for any additional cell features
 */
public class BaseCellFeatures
{
	/**
	 * The logger
	 */
	public static Logger logger = LoggerFactory.getLog(BaseCellFeatures.class);

	/**
	 * The comment
	 */
	private String comment;

	/**
	 * The comment width in cells
	 */
	private double commentWidth;

	/**
	 * The comment height in cells
	 */
	private double commentHeight;

	/**
	 * A handle to the drawing object
	 */
	private Comment commentDrawing;

	/**
	 * A handle to the combo box object
	 */
	private ComboBox comboBox;

	/**
	 * The data validation settings
	 */
	private DataValiditySettingsRecord validationSettings;

	/**
	 * The DV Parser used to contain the validation details
	 */
	private DVParser dvParser;

	/**
	 * Indicates whether a drop down is required
	 */
	private boolean dropDown;

	/**
	 * Indicates whether this cell features has data validation
	 */
	private boolean dataValidation;

	/**
	 * The cell to which this is attached, and which may need to be notified
	 */
	private CellValue writableCell;

	// Constants
	private final static double defaultCommentWidth = 3;
	private final static double defaultCommentHeight = 4;

	// Validation conditions
	protected static class ValidationCondition
	{
		private DVParser.Condition condition;

		private static ValidationCondition[] types = new ValidationCondition[0];

		ValidationCondition(DVParser.Condition c)
		{
			condition = c;
			ValidationCondition[] oldtypes = types;
			types = new ValidationCondition[oldtypes.length + 1];
			System.arraycopy(oldtypes, 0, types, 0, oldtypes.length);
			types[oldtypes.length] = this;
		}

		public DVParser.Condition getCondition()
		{
			return condition;
		}
	}

	public static final ValidationCondition BETWEEN =
			new ValidationCondition(DVParser.BETWEEN);
	public static final ValidationCondition NOT_BETWEEN =
			new ValidationCondition(DVParser.NOT_BETWEEN);
	public static final ValidationCondition EQUAL =
			new ValidationCondition(DVParser.EQUAL);
	public static final ValidationCondition NOT_EQUAL =
			new ValidationCondition(DVParser.NOT_EQUAL);
	public static final ValidationCondition GREATER_THAN =
			new ValidationCondition(DVParser.GREATER_THAN);
	public static final ValidationCondition LESS_THAN =
			new ValidationCondition(DVParser.LESS_THAN);
	public static final ValidationCondition GREATER_EQUAL =
			new ValidationCondition(DVParser.GREATER_EQUAL);
	public static final ValidationCondition LESS_EQUAL =
			new ValidationCondition(DVParser.LESS_EQUAL);

	/**
	 * Constructor
	 */
	protected BaseCellFeatures()
	{
	}

	/**
	 * Copy constructor
	 *
	 * @param cf cell to copy
	 */
	public BaseCellFeatures(BaseCellFeatures cf)
	{
		// The comment stuff
		comment = cf.comment;
		commentWidth = cf.commentWidth;
		commentHeight = cf.commentHeight;

		// The data validation stuff.
		dropDown = cf.dropDown;
		dataValidation = cf.dataValidation;

		validationSettings = cf.validationSettings; // ?

		if (cf.dvParser != null)
		{
			dvParser = new DVParser(cf.dvParser);
		}
	}

	/**
	 * Accessor for the cell comment
	 */
	protected String getComment()
	{
		return comment;
	}

	/**
	 * Accessor for the comment width
	 */
	public double getCommentWidth()
	{
		return commentWidth;
	}

	/**
	 * Accessor for the comment height
	 */
	public double getCommentHeight()
	{
		return commentHeight;
	}

	/**
	 * Called by the cell when the features are added
	 *
	 * @param wc the writable cell
	 */
	public final void setWritableCell(CellValue wc)
	{
		writableCell = wc;
	}

	/**
	 * Internal method to set the cell comment.  Used when reading
	 */
	public void setReadComment(String s, double w, double h)
	{
		comment = s;
		commentWidth = w;
		commentHeight = h;
	}

	/**
	 * Internal method to set the data validation.  Used when reading
	 */
	public void setValidationSettings(DataValiditySettingsRecord dvsr)
	{
		Assert.verify(dvsr != null);

		validationSettings = dvsr;
		dataValidation = true;
	}

	/**
	 * Sets the cell comment
	 *
	 * @param s the comment
	 */
	public void setComment(String s)
	{
		setComment(s, defaultCommentWidth, defaultCommentHeight);
	}

	/**
	 * Sets the cell comment
	 *
	 * @param s	  the comment
	 * @param height the height of the comment box in cells
	 * @param width  the width of the comment box in cells
	 */
	public void setComment(String s, double width, double height)
	{
		comment = s;
		commentWidth = width;
		commentHeight = height;

		if (commentDrawing != null)
		{
			commentDrawing.setCommentText(s);
			commentDrawing.setWidth(width);
			commentDrawing.setWidth(height);
			// commentDrawing is set up when trying to modify a copied cell
		}
	}

	/**
	 * Removes the cell comment, if present
	 */
	public void removeComment()
	{
		// Set the comment string to be empty
		comment = null;

		// Remove the drawing from the drawing group
		if (commentDrawing != null)
		{
			// do not call DrawingGroup.remove() because comments are not present
			// on the Workbook DrawingGroup record
			writableCell.removeComment(commentDrawing);
			commentDrawing = null;
		}
	}

	/**
	 * Public function which removes any data validation, if present
	 */
	public void removeDataValidation()
	{
		if (!dataValidation)
		{
			return;
		}

		// If the data validation is shared, then generate a warning
		DVParser dvp = getDVParser();
		if (dvp.extendedCellsValidation())
		{
			logger.warn("Cannot remove data validation from " +
					CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of the shared reference " +
					CellReferenceHelper.getCellReference(dvp.getFirstColumn(),
							dvp.getFirstRow()) +
					"-" +
					CellReferenceHelper.getCellReference(dvp.getLastColumn(),
							dvp.getLastRow()));
			return;
		}

		// Remove the validation from the WritableSheet object if present
		writableCell.removeDataValidation();
		clearValidationSettings();
	}

	/**
	 * Internal function which removes any data validation, including
	 * shared ones, if present.  This is called from DefaultWritableSheet
	 * in response to a call to removeDataValidation
	 */
	public void removeSharedDataValidation()
	{
		if (!dataValidation)
		{
			return;
		}

		// Remove the validation from the WritableSheet object if present
		writableCell.removeDataValidation();
		clearValidationSettings();
	}

	/**
	 * Sets the comment drawing object
	 */
	public final void setCommentDrawing(Comment c)
	{
		commentDrawing = c;
	}

	/**
	 * Accessor for the comment drawing
	 */
	public final Comment getCommentDrawing()
	{
		return commentDrawing;
	}

	/**
	 * Gets the data validation list as a formula.  Used only when reading
	 *
	 * @return the validation formula as a list
	 */
	public String getDataValidationList()
	{
		if (validationSettings == null)
		{
			return null;
		}

		return validationSettings.getValidationFormula();
	}

	/**
	 * The list of items to validate for this cell.  For each object in the
	 * collection, the toString() method will be called and the data entered
	 * will be validated against that string
	 *
	 * @param c the list of valid values
	 */
	public void setDataValidationList(Collection c)
	{
		if (dataValidation && getDVParser().extendedCellsValidation())
		{
			logger.warn("Cannot set data validation on " +
					CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of a shared data validation");
			return;
		}
		clearValidationSettings();
		dvParser = new DVParser(c);
		dropDown = true;
		dataValidation = true;
	}

	/**
	 * The list of items to validate for this cell in the form of a cell range.
	 */
	public void setDataValidationRange(int col1, int r1, int col2, int r2)
	{
		if (dataValidation && getDVParser().extendedCellsValidation())
		{
			logger.warn("Cannot set data validation on " +
					CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of a shared data validation");
			return;
		}
		clearValidationSettings();
		dvParser = new DVParser(col1, r1, col2, r2);
		dropDown = true;
		dataValidation = true;
	}

	/**
	 * Sets the data validation based upon a named range
	 */
	public void setDataValidationRange(String namedRange)
	{
		if (dataValidation && getDVParser().extendedCellsValidation())
		{
			logger.warn("Cannot set data validation on " +
					CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of a shared data validation");
			return;
		}
		clearValidationSettings();
		dvParser = new DVParser(namedRange);
		dropDown = true;
		dataValidation = true;
	}

	/**
	 * Sets the data validation based upon a numerical condition
	 */
	public void setNumberValidation(double val, ValidationCondition c)
	{
		if (dataValidation && getDVParser().extendedCellsValidation())
		{
			logger.warn("Cannot set data validation on " +
					CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of a shared data validation");
			return;
		}
		clearValidationSettings();
		dvParser = new DVParser(val, Double.NaN, c.getCondition());
		dropDown = false;
		dataValidation = true;
	}

	public void setNumberValidation(double val1, double val2,
									ValidationCondition c)
	{
		if (dataValidation && getDVParser().extendedCellsValidation())
		{
			logger.warn("Cannot set data validation on " +
					org.areasy.common.parser.excel.CellReferenceHelper.getCellReference(writableCell) +
					" as it is part of a shared data validation");
			return;
		}
		clearValidationSettings();
		dvParser = new DVParser(val1, val2, c.getCondition());
		dropDown = false;
		dataValidation = true;
	}

	/**
	 * Accessor for the data validation
	 *
	 * @return TRUE if this has a data validation associated with it,
	 *         FALSE otherwise
	 */
	public boolean hasDataValidation()
	{
		return dataValidation;
	}

	/**
	 * Clears out any existing validation settings
	 */
	private void clearValidationSettings()
	{
		validationSettings = null;
		dvParser = null;
		dropDown = false;
		comboBox = null;
		dataValidation = false;
	}

	/**
	 * Accessor for whether a drop down is required
	 *
	 * @return TRUE if this requires a drop down, FALSE otherwise
	 */
	public boolean hasDropDown()
	{
		return dropDown;
	}

	/**
	 * Sets the combo box drawing object for list validations
	 *
	 * @param cb the combo box
	 */
	public void setComboBox(ComboBox cb)
	{
		comboBox = cb;
	}

	/**
	 * Gets the dv parser
	 */
	public DVParser getDVParser()
	{
		// straightforward - this was created as  a writable cell
		if (dvParser != null)
		{
			return dvParser;
		}

		// this was copied from a readable cell, and then copied again
		if (validationSettings != null)
		{
			dvParser = new DVParser(validationSettings.getDVParser());
			return dvParser;
		}

		return null; // keep the compiler happy
	}

	/**
	 * Use the same data validation logic as the specified cell features
	 *
	 * @param source the data validation to reuse
	 */
	public void shareDataValidation(BaseCellFeatures source)
	{
		if (dataValidation)
		{
			logger.warn("Attempting to share a data validation on cell " +
					CellReferenceHelper.getCellReference(writableCell) +
					" which already has a data validation");
			return;
		}
		clearValidationSettings();
		dvParser = source.getDVParser();
		validationSettings = null;
		dataValidation = true;
		dropDown = source.dropDown;
		comboBox = source.comboBox;
	}

	/**
	 * Gets the range of cells to which the data validation applies.  If the
	 * validation applies to just this cell, this will be reflected in the
	 * returned range
	 *
	 * @return the range to which the same validation extends, or NULL if this
	 *         cell doesn't have a validation
	 */
	public Range getSharedDataValidationRange()
	{
		if (!dataValidation)
		{
			return null;
		}

		DVParser dvp = getDVParser();

		return new DefaultSheetRange(writableCell.getSheet(),
				dvp.getFirstColumn(),
				dvp.getFirstRow(),
				dvp.getLastColumn(),
				dvp.getLastRow());
	}
}
