<?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());
        }
    }
}
