/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_CSSEditUtils_h
#define mozilla_CSSEditUtils_h

#include "mozilla/ChangeStyleTransaction.h" // for ChangeStyleTransaction
#include "nsCOMPtr.h"               // for already_AddRefed
#include "nsTArray.h"               // for nsTArray
#include "nscore.h"                 // for nsAString, nsresult, nullptr

class nsComputedDOMStyle;
class nsIAtom;
class nsIContent;
class nsIDOMCSSStyleDeclaration;
class nsIDOMElement;
class nsIDOMNode;
class nsINode;
class nsString;

namespace mozilla {

class HTMLEditor;
namespace dom {
class Element;
} // namespace dom

typedef void (*nsProcessValueFunc)(const nsAString* aInputString,
                                   nsAString& aOutputString,
                                   const char* aDefaultValueString,
                                   const char* aPrependString,
                                   const char* aAppendString);

class CSSEditUtils final
{
public:
  explicit CSSEditUtils(HTMLEditor* aEditor);
  ~CSSEditUtils();

  enum nsCSSEditableProperty
  {
    eCSSEditableProperty_NONE=0,
    eCSSEditableProperty_background_color,
    eCSSEditableProperty_background_image,
    eCSSEditableProperty_border,
    eCSSEditableProperty_caption_side,
    eCSSEditableProperty_color,
    eCSSEditableProperty_float,
    eCSSEditableProperty_font_family,
    eCSSEditableProperty_font_size,
    eCSSEditableProperty_font_style,
    eCSSEditableProperty_font_weight,
    eCSSEditableProperty_height,
    eCSSEditableProperty_list_style_type,
    eCSSEditableProperty_margin_left,
    eCSSEditableProperty_margin_right,
    eCSSEditableProperty_text_align,
    eCSSEditableProperty_text_decoration,
    eCSSEditableProperty_vertical_align,
    eCSSEditableProperty_whitespace,
    eCSSEditableProperty_width
  };

  enum StyleType { eSpecified, eComputed };


  struct CSSEquivTable
  {
    nsCSSEditableProperty cssProperty;
    nsProcessValueFunc processValueFunctor;
    const char* defaultValue;
    const char* prependValue;
    const char* appendValue;
    bool gettable;
    bool caseSensitiveValue;
  };

  /**
   * Answers true if the given combination element_name/attribute_name
   * has a CSS equivalence in this implementation.
   *
   * @param aNode          [IN] A DOM node.
   * @param aProperty      [IN] An atom containing a HTML tag name.
   * @param aAttribute     [IN] A string containing the name of a HTML
   *                            attribute carried by the element above.
   * @return               A boolean saying if the tag/attribute has a CSS
   *                       equiv.
   */
  bool IsCSSEditableProperty(nsINode* aNode, nsIAtom* aProperty,
                             const nsAString* aAttribute);

  /**
   * Adds/remove a CSS declaration to the STYLE atrribute carried by a given
   * element.
   *
   * @param aElement       [IN] A DOM element.
   * @param aProperty      [IN] An atom containing the CSS property to set.
   * @param aValue         [IN] A string containing the value of the CSS
   *                            property.
   * @param aSuppressTransaction [IN] A boolean indicating, when true,
   *                                  that no transaction should be recorded.
   */
  nsresult SetCSSProperty(dom::Element& aElement, nsIAtom& aProperty,
                          const nsAString& aValue, bool aSuppressTxn = false);
  nsresult SetCSSPropertyPixels(dom::Element& aElement,
                                nsIAtom& aProperty, int32_t aIntValue);
  nsresult RemoveCSSProperty(dom::Element& aElement,
                             nsIAtom& aProperty,
                             const nsAString& aPropertyValue,
                             bool aSuppressTxn = false);

  /**
   * Directly adds/remove a CSS declaration to the STYLE atrribute carried by
   * a given element without going through the transaction manager.
   *
   * @param aElement       [IN] A DOM element.
   * @param aProperty      [IN] A string containing the CSS property to
   *                            set/remove.
   * @param aValue         [IN] A string containing the new value of the CSS
   *                            property.
   */
  nsresult SetCSSProperty(nsIDOMElement* aElement,
                          const nsAString& aProperty,
                          const nsAString& aValue);
  nsresult SetCSSPropertyPixels(nsIDOMElement* aElement,
                                const nsAString& aProperty,
                                int32_t aIntValue);

  /**
   * Gets the specified/computed style value of a CSS property for a given
   * node (or its element ancestor if it is not an element).
   *
   * @param aNode          [IN] A DOM node.
   * @param aProperty      [IN] An atom containing the CSS property to get.
   * @param aPropertyValue [OUT] The retrieved value of the property.
   */
  nsresult GetSpecifiedProperty(nsINode& aNode, nsIAtom& aProperty,
                                nsAString& aValue);
  nsresult GetComputedProperty(nsINode& aNode, nsIAtom& aProperty,
                               nsAString& aValue);

  /**
   * Removes a CSS property from the specified declarations in STYLE attribute
   * and removes the node if it is an useless span.
   *
   * @param aNode           [IN] The specific node we want to remove a style
   *                             from.
   * @param aProperty       [IN] The CSS property atom to remove.
   * @param aPropertyValue  [IN] The value of the property we have to remove
   *                             if the property accepts more than one value.
   */
  nsresult RemoveCSSInlineStyle(nsIDOMNode* aNode, nsIAtom* aProperty,
                                const nsAString& aPropertyValue);

  /**
   * Answers true is the property can be removed by setting a "none" CSS value
   * on a node.
   *
   * @param aProperty     [IN] An atom containing a CSS property.
   * @param aAttribute    [IN] Pointer to an attribute name or null if this
   *                           information is irrelevant.
   * @return              A boolean saying if the property can be remove by
   *                      setting a "none" value.
   */
  bool IsCSSInvertible(nsIAtom& aProperty, const nsAString* aAttribute);

  /**
   * Get the default browser background color if we need it for
   * GetCSSBackgroundColorState().
   *
   * @param aColor         [OUT] The default color as it is defined in prefs.
   */
  void GetDefaultBackgroundColor(nsAString& aColor);

  /**
   * Get the default length unit used for CSS Indent/Outdent.
   *
   * @param aLengthUnit    [OUT] The default length unit as it is defined in
   *                             prefs.
   */
  void GetDefaultLengthUnit(nsAString & aLengthUnit);

  /**
   * Returns the list of values for the CSS equivalences to
   * the passed HTML style for the passed node.
   *
   * @param aNode          [IN] A DOM node.
   * @param aHTMLProperty  [IN] An atom containing an HTML property.
   * @param aAttribute     [IN] A pointer to an attribute name or nullptr if
   *                            irrelevant.
   * @param aValueString   [OUT] The list of CSS values.
   * @param aStyleType     [IN] eSpecified or eComputed.
   */
  nsresult GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode,
                                                nsIAtom* aHTMLProperty,
                                                const nsAString* aAttribute,
                                                nsAString& aValueString,
                                                StyleType aStyleType);

  /**
   * Does the node aNode (or his parent if it is not an element node) carries
   * the CSS equivalent styles to the HTML style for this node ?
   *
   * @param aNode          [IN] A DOM node.
   * @param aHTMLProperty  [IN] An atom containing an HTML property.
   * @param aAttribute     [IN] A pointer to an attribute name or nullptr if
   *                            irrelevant.
   * @param aIsSet         [OUT] A boolean being true if the css properties are
   *                             set.
   * @param aValueString   [IN/OUT] The attribute value (in) the list of CSS
   *                                values (out).
   * @param aStyleType     [IN] eSpecified or eComputed.
   *
   * The nsIContent variant returns aIsSet instead of using an out parameter.
   */
  bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
                                           nsIAtom* aProperty,
                                           const nsAString* aAttribute,
                                           const nsAString& aValue,
                                           StyleType aStyleType);

  bool IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aContent,
                                           nsIAtom* aProperty,
                                           const nsAString* aAttribute,
                                           nsAString& aValue,
                                           StyleType aStyleType);

  nsresult IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode* aNode,
                                               nsIAtom* aHTMLProperty,
                                               const nsAString* aAttribute,
                                               bool& aIsSet,
                                               nsAString& aValueString,
                                               StyleType aStyleType);

  /**
   * Adds to the node the CSS inline styles equivalent to the HTML style
   * and return the number of CSS properties set by the call.
   *
   * @param aNode          [IN] A DOM node.
   * @param aHTMLProperty  [IN] An atom containing an HTML property.
   * @param aAttribute     [IN] A pointer to an attribute name or nullptr if
   *                            irrelevant.
   * @param aValue         [IN] The attribute value.
   * @param aCount         [OUT] The number of CSS properties set by the call.
   * @param aSuppressTransaction [IN] A boolean indicating, when true,
   *                                  that no transaction should be recorded.
   *
   * aCount is returned by the dom::Element variant instead of being an out
   * parameter.
   */
  int32_t SetCSSEquivalentToHTMLStyle(dom::Element* aElement,
                                      nsIAtom* aProperty,
                                      const nsAString* aAttribute,
                                      const nsAString* aValue,
                                      bool aSuppressTransaction);
  nsresult SetCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
                                       nsIAtom* aHTMLProperty,
                                       const nsAString* aAttribute,
                                       const nsAString* aValue,
                                       int32_t* aCount,
                                       bool aSuppressTransaction);

  /**
   * Removes from the node the CSS inline styles equivalent to the HTML style.
   *
   * @param aNode          [IN] A DOM node.
   * @param aHTMLProperty  [IN] An atom containing an HTML property.
   * @param aAttribute     [IN] A pointer to an attribute name or nullptr if
   *                            irrelevant.
   * @param aValue         [IN] The attribute value.
   * @param aSuppressTransaction [IN] A boolean indicating, when true,
   *                                  that no transaction should be recorded.
   */
  nsresult RemoveCSSEquivalentToHTMLStyle(nsIDOMNode* aNode,
                                          nsIAtom* aHTMLProperty,
                                          const nsAString* aAttribute,
                                          const nsAString* aValue,
                                          bool aSuppressTransaction);

  /**
   * Removes from the node the CSS inline styles equivalent to the HTML style.
   *
   * @param aElement       [IN] A DOM Element (must not be null).
   * @param aHTMLProperty  [IN] An atom containing an HTML property.
   * @param aAttribute     [IN] A pointer to an attribute name or nullptr if
   *                            irrelevant.
   * @param aValue         [IN] The attribute value.
   * @param aSuppressTransaction [IN] A boolean indicating, when true,
   *                                  that no transaction should be recorded.
   */
  nsresult RemoveCSSEquivalentToHTMLStyle(dom::Element* aElement,
                                          nsIAtom* aHTMLProperty,
                                          const nsAString* aAttribute,
                                          const nsAString* aValue,
                                          bool aSuppressTransaction);

  /**
   * Parses a "xxxx.xxxxxuuu" string where x is a digit and u an alpha char
   * we need such a parser because
   * nsIDOMCSSStyleDeclaration::GetPropertyCSSValue() is not implemented.
   *
   * @param aString        [IN] Input string to parse.
   * @param aValue         [OUT] Numeric part.
   * @param aUnit          [OUT] Unit part.
   */
  void ParseLength(const nsAString& aString, float* aValue, nsIAtom** aUnit);

  /**
   * Sets the mIsCSSPrefChecked private member; used as callback from observer
   *  when the CSS pref state is changed.
   *
   * @param aIsCSSPrefChecked [IN] The new boolean state for the pref.
   */
  void SetCSSEnabled(bool aIsCSSPrefChecked);

  /**
   * Retrieves the mIsCSSPrefChecked private member, true if the CSS pref is
   * checked, false if it is not.
   *
   * @return                 the boolean value of the CSS pref.
   */
  bool IsCSSPrefChecked();

  /**
   * ElementsSameStyle compares two elements and checks if they have the same
   * specified CSS declarations in the STYLE attribute.
   * The answer is always false if at least one of them carries an ID or a
   * class.
   *
   * @param aFirstNode           [IN] A DOM node.
   * @param aSecondNode          [IN] A DOM node.
   * @return                     true if the two elements are considered to
   *                             have same styles.
   */
  bool ElementsSameStyle(dom::Element* aFirstNode,
                         dom::Element* aSecondNode);
  bool ElementsSameStyle(nsIDOMNode* aFirstNode, nsIDOMNode* aSecondNode);

  /**
   * Get the specified inline styles (style attribute) for an element.
   *
   * @param aElement        [IN] The element node.
   * @param aCssDecl        [OUT] The CSS declaration corresponding to the
   *                              style attribute.
   * @param aLength         [OUT] The number of declarations in aCssDecl.
   */
  nsresult GetInlineStyles(dom::Element* aElement,
                           nsIDOMCSSStyleDeclaration** aCssDecl,
                           uint32_t* aLength);
  nsresult GetInlineStyles(nsIDOMElement* aElement,
                           nsIDOMCSSStyleDeclaration** aCssDecl,
                           uint32_t* aLength);
private:
  nsresult GetInlineStyles(nsISupports* aElement,
                           nsIDOMCSSStyleDeclaration** aCssDecl,
                           uint32_t* aLength);

public:
  /**
   * Returns aNode itself if it is an element node, or the first ancestors
   * being an element node if aNode is not one itself.
   *
   * @param aNode           [IN] A node
   * @param aElement        [OUT] The deepest element node containing aNode
   *                              (possibly aNode itself)
   */
  dom::Element* GetElementContainerOrSelf(nsINode* aNode);
  already_AddRefed<nsIDOMElement> GetElementContainerOrSelf(nsIDOMNode* aNode);

  /**
   * Gets the computed style for a given element.  Can return null.
   */
  already_AddRefed<nsComputedDOMStyle> GetComputedStyle(dom::Element* aElement);

private:
  /**
   * Retrieves the CSS property atom from an enum.
   *
   * @param aProperty          [IN] The enum value for the property.
   * @param aAtom              [OUT] The corresponding atom.
   */
  void GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom** aAtom);

  /**
   * Retrieves the CSS declarations equivalent to a HTML style value for
   * a given equivalence table.
   *
   * @param aPropertyArray     [OUT] The array of css properties.
   * @param aValueArray        [OUT] The array of values for the CSS properties
   *                                 above.
   * @param aEquivTable        [IN] The equivalence table.
   * @param aValue             [IN] The HTML style value.
   * @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
   *                                 the current method is made for
   *                                 GetCSSEquivalentToHTMLInlineStyleSet() or
   *                                 RemoveCSSEquivalentToHTMLInlineStyleSet().
   */
  void BuildCSSDeclarations(nsTArray<nsIAtom*>& aPropertyArray,
                            nsTArray<nsString>& cssValueArray,
                            const CSSEquivTable* aEquivTable,
                            const nsAString* aValue,
                            bool aGetOrRemoveRequest);

  /**
   * Retrieves the CSS declarations equivalent to the given HTML
   * property/attribute/value for a given node.
   *
   * @param aNode              [IN] The DOM node.
   * @param aHTMLProperty      [IN] An atom containing an HTML property.
   * @param aAttribute         [IN] A pointer to an attribute name or nullptr
   *                                if irrelevant
   * @param aValue             [IN] The attribute value.
   * @param aPropertyArray     [OUT] The array of CSS properties.
   * @param aValueArray        [OUT] The array of values for the CSS properties
   *                                 above.
   * @param aGetOrRemoveRequest [IN] A boolean value being true if the call to
   *                                 the current method is made for
   *                                 GetCSSEquivalentToHTMLInlineStyleSet() or
   *                                 RemoveCSSEquivalentToHTMLInlineStyleSet().
   */
  void GenerateCSSDeclarationsFromHTMLStyle(dom::Element* aNode,
                                            nsIAtom* aHTMLProperty,
                                            const nsAString* aAttribute,
                                            const nsAString* aValue,
                                            nsTArray<nsIAtom*>& aPropertyArray,
                                            nsTArray<nsString>& aValueArray,
                                            bool aGetOrRemoveRequest);

  /**
   * Creates a Transaction for setting or removing a CSS property.  Never
   * returns null.
   *
   * @param aElement           [IN] A DOM element.
   * @param aProperty          [IN] A CSS property.
   * @param aValue             [IN] The value to set for this CSS property.
   * @param aChangeType        [IN] eSet to set, eRemove to remove.
   */
  already_AddRefed<ChangeStyleTransaction>
  CreateCSSPropertyTxn(dom::Element& aElement,
                       nsIAtom& aProperty, const nsAString& aValue,
                       ChangeStyleTransaction::EChangeType aChangeType);

  /**
   * Back-end for GetSpecifiedProperty and GetComputedProperty.
   *
   * @param aNode               [IN] A DOM node.
   * @param aProperty           [IN] A CSS property.
   * @param aValue              [OUT] The retrieved value for this property.
   * @param aStyleType          [IN] eSpecified or eComputed.
   */
  nsresult GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty,
                                    nsAString& aValue, StyleType aStyleType);

private:
  HTMLEditor* mHTMLEditor;
  bool mIsCSSPrefChecked;
};

#define NS_EDITOR_INDENT_INCREMENT_IN        0.4134f
#define NS_EDITOR_INDENT_INCREMENT_CM        1.05f
#define NS_EDITOR_INDENT_INCREMENT_MM        10.5f
#define NS_EDITOR_INDENT_INCREMENT_PT        29.76f
#define NS_EDITOR_INDENT_INCREMENT_PC        2.48f
#define NS_EDITOR_INDENT_INCREMENT_EM        3
#define NS_EDITOR_INDENT_INCREMENT_EX        6
#define NS_EDITOR_INDENT_INCREMENT_PX        40
#define NS_EDITOR_INDENT_INCREMENT_PERCENT   4

} // namespace mozilla

#endif // #ifndef mozilla_CSSEditUtils_h