<?php

namespace Box\Spout\Writer\Style;

/**
 * Class Style
 * Represents a style to be applied to a cell
 *
 * @package Box\Spout\Writer\Style
 */
class Style {

	/** Default font values */
	const DEFAULT_FONT_SIZE  = 11;
	const DEFAULT_FONT_COLOR = Color::BLACK;
	const DEFAULT_FONT_NAME  = 'Arial';

	/** @var int|null Style ID */
	protected $id = null;

	/** @var bool Whether the font should be bold */
	protected $fontBold = false;
	/** @var bool Whether the bold property was set */
	protected $hasSetFontBold = false;

	/** @var bool Whether the font should be italic */
	protected $fontItalic = false;
	/** @var bool Whether the italic property was set */
	protected $hasSetFontItalic = false;

	/** @var bool Whether the font should be underlined */
	protected $fontUnderline = false;
	/** @var bool Whether the underline property was set */
	protected $hasSetFontUnderline = false;

	/** @var bool Whether the font should be struck through */
	protected $fontStrikethrough = false;
	/** @var bool Whether the strikethrough property was set */
	protected $hasSetFontStrikethrough = false;

	/** @var int Font size */
	protected $fontSize = self::DEFAULT_FONT_SIZE;
	/** @var bool Whether the font size property was set */
	protected $hasSetFontSize = false;

	/** @var string Font color */
	protected $fontColor = self::DEFAULT_FONT_COLOR;
	/** @var bool Whether the font color property was set */
	protected $hasSetFontColor = false;

	/** @var string Font name */
	protected $fontName = self::DEFAULT_FONT_NAME;
	/** @var bool Whether the font name property was set */
	protected $hasSetFontName = false;

	/** @var bool Whether specific font properties should be applied */
	protected $shouldApplyFont = false;

	/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
	protected $shouldWrapText = false;
	/** @var bool Whether the wrap text property was set */
	protected $hasSetWrapText = false;

	/**
	 * @var Border
	 */
	protected $border = null;

	/**
	 * @var bool Whether border properties should be applied
	 */
	protected $shouldApplyBorder = false;

	/** @var string Background color */
	protected $backgroundColor = null;

	/** @var bool */
	protected $hasSetBackgroundColor = false;


	/**
	 * @return int|null
	 */
	public function getId() {
		return $this->id;
	}

	/**
	 * @param int $id
	 * @return Style
	 */
	public function setId( $id ) {
		$this->id = $id;
		return $this;
	}

	/**
	 * @return Border
	 */
	public function getBorder() {
		return $this->border;
	}

	/**
	 * @param Border $border
	 * @return Style
	 */
	public function setBorder( Border $border ) {
		$this->shouldApplyBorder = true;
		$this->border            = $border;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function shouldApplyBorder() {
		return $this->shouldApplyBorder;
	}

	/**
	 * @return bool
	 */
	public function isFontBold() {
		return $this->fontBold;
	}

	/**
	 * @return Style
	 */
	public function setFontBold() {
		 $this->fontBold       = true;
		$this->hasSetFontBold  = true;
		$this->shouldApplyFont = true;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isFontItalic() {
		return $this->fontItalic;
	}

	/**
	 * @return Style
	 */
	public function setFontItalic() {
		$this->fontItalic       = true;
		$this->hasSetFontItalic = true;
		$this->shouldApplyFont  = true;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isFontUnderline() {
		 return $this->fontUnderline;
	}

	/**
	 * @return Style
	 */
	public function setFontUnderline() {
		$this->fontUnderline       = true;
		$this->hasSetFontUnderline = true;
		$this->shouldApplyFont     = true;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isFontStrikethrough() {
		 return $this->fontStrikethrough;
	}

	/**
	 * @return Style
	 */
	public function setFontStrikethrough() {
		$this->fontStrikethrough       = true;
		$this->hasSetFontStrikethrough = true;
		$this->shouldApplyFont         = true;
		return $this;
	}

	/**
	 * @return int
	 */
	public function getFontSize() {
		 return $this->fontSize;
	}

	/**
	 * @param int $fontSize Font size, in pixels
	 * @return Style
	 */
	public function setFontSize( $fontSize ) {
		$this->fontSize        = $fontSize;
		$this->hasSetFontSize  = true;
		$this->shouldApplyFont = true;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getFontColor() {
		return $this->fontColor;
	}

	/**
	 * Sets the font color.
	 *
	 * @param string $fontColor ARGB color (@see Color)
	 * @return Style
	 */
	public function setFontColor( $fontColor ) {
		$this->fontColor       = $fontColor;
		$this->hasSetFontColor = true;
		$this->shouldApplyFont = true;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getFontName() {
		 return $this->fontName;
	}

	/**
	 * @param string $fontName Name of the font to use
	 * @return Style
	 */
	public function setFontName( $fontName ) {
		$this->fontName        = $fontName;
		$this->hasSetFontName  = true;
		$this->shouldApplyFont = true;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function shouldWrapText() {
		return $this->shouldWrapText;
	}

	/**
	 * @param bool|void $shouldWrap Should the text be wrapped
	 * @return Style
	 */
	public function setShouldWrapText( $shouldWrap = true ) {
		$this->shouldWrapText = $shouldWrap;
		$this->hasSetWrapText = true;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function hasSetWrapText() {
		return $this->hasSetWrapText;
	}

	/**
	 * @return bool Whether specific font properties should be applied
	 */
	public function shouldApplyFont() {
		 return $this->shouldApplyFont;
	}

	/**
	 * Sets the background color
	 * @param string $color ARGB color (@see Color)
	 * @return Style
	 */
	public function setBackgroundColor( $color ) {
		$this->hasSetBackgroundColor = true;
		$this->backgroundColor       = $color;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getBackgroundColor() {
		return $this->backgroundColor;
	}

	/**
	 *
	 * @return bool Whether the background color should be applied
	 */
	public function shouldApplyBackgroundColor() {
		return $this->hasSetBackgroundColor;
	}

	/**
	 * Serializes the style for future comparison with other styles.
	 * The ID is excluded from the comparison, as we only care about
	 * actual style properties.
	 *
	 * @return string The serialized style
	 */
	public function serialize() {
		// In order to be able to properly compare style, set static ID value
		$currentId = $this->id;
		$this->setId( 0 );

		$serializedStyle = serialize( $this );

		$this->setId( $currentId );

		return $serializedStyle;
	}

	/**
	 * Merges the current style with the given style, using the given style as a base. This means that:
	 *   - if current style and base style both have property A set, use current style property's value
	 *   - if current style has property A set but base style does not, use current style property's value
	 *   - if base style has property A set but current style does not, use base style property's value
	 *
	 * @NOTE: This function returns a new style.
	 *
	 * @param Style $baseStyle
	 * @return Style New style corresponding to the merge of the 2 styles
	 */
	public function mergeWith( $baseStyle ) {
		$mergedStyle = clone $this;

		$this->mergeFontStyles( $mergedStyle, $baseStyle );
		$this->mergeOtherFontProperties( $mergedStyle, $baseStyle );
		$this->mergeCellProperties( $mergedStyle, $baseStyle );

		return $mergedStyle;
	}

	/**
	 * @param Style $styleToUpdate (passed as reference)
	 * @param Style $baseStyle
	 * @return void
	 */
	private function mergeFontStyles( $styleToUpdate, $baseStyle ) {
		if ( ! $this->hasSetFontBold && $baseStyle->isFontBold() ) {
			$styleToUpdate->setFontBold();
		}
		if ( ! $this->hasSetFontItalic && $baseStyle->isFontItalic() ) {
			$styleToUpdate->setFontItalic();
		}
		if ( ! $this->hasSetFontUnderline && $baseStyle->isFontUnderline() ) {
			$styleToUpdate->setFontUnderline();
		}
		if ( ! $this->hasSetFontStrikethrough && $baseStyle->isFontStrikethrough() ) {
			$styleToUpdate->setFontStrikethrough();
		}
	}

	/**
	 * @param Style $styleToUpdate Style to update (passed as reference)
	 * @param Style $baseStyle
	 * @return void
	 */
	private function mergeOtherFontProperties( $styleToUpdate, $baseStyle ) {
		if ( ! $this->hasSetFontSize && $baseStyle->getFontSize() !== self::DEFAULT_FONT_SIZE ) {
			$styleToUpdate->setFontSize( $baseStyle->getFontSize() );
		}
		if ( ! $this->hasSetFontColor && $baseStyle->getFontColor() !== self::DEFAULT_FONT_COLOR ) {
			$styleToUpdate->setFontColor( $baseStyle->getFontColor() );
		}
		if ( ! $this->hasSetFontName && $baseStyle->getFontName() !== self::DEFAULT_FONT_NAME ) {
			$styleToUpdate->setFontName( $baseStyle->getFontName() );
		}
	}

	/**
	 * @param Style $styleToUpdate Style to update (passed as reference)
	 * @param Style $baseStyle
	 * @return void
	 */
	private function mergeCellProperties( $styleToUpdate, $baseStyle ) {
		if ( ! $this->hasSetWrapText && $baseStyle->shouldWrapText() ) {
			$styleToUpdate->setShouldWrapText();
		}
		if ( ! $this->getBorder() && $baseStyle->shouldApplyBorder() ) {
			$styleToUpdate->setBorder( $baseStyle->getBorder() );
		}
		if ( ! $this->hasSetBackgroundColor && $baseStyle->shouldApplyBackgroundColor() ) {
			$styleToUpdate->setBackgroundColor( $baseStyle->getBackgroundColor() );
		}
	}
}
