/*
 * Copyright (C) 2014-2023 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.css.propertyvalue;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.hashcode.HashCodeGenerator;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
import com.helger.css.CCSS;
import com.helger.css.ICSSWriterSettings;
import com.helger.css.property.ECSSProperty;
import com.helger.css.property.ICSSProperty;

/**
 * Represents the combination of a single CSS property ({@link ICSSProperty})
 * and it's according value plus the important state (<code>!important</code> or
 * not). The main purpose of this class to make building a CSS from scratch
 * simpler. When an existing CSS is read the information resides in a
 * {@link com.helger.css.decl.CSSDeclaration} because it contains the
 * declaration value in a more structured form.<br>
 * Instances of this class are mutable since 3.7.3.
 *
 * @author Philip Helger
 */
@NotThreadSafe
public class CSSValue implements ICSSValue
{
  public static final boolean DEFAULT_CONSISTENCY_CHECKS_ENABLED = true;

  private static final Logger LOGGER = LoggerFactory.getLogger (CSSValue.class);
  private static boolean s_bConsistencyChecksEnabled = DEFAULT_CONSISTENCY_CHECKS_ENABLED;

  /**
   * @return <code>true</code> if consistency checks are enabled (by default),
   *         <code>false</code> if not.
   * @since 5.0.2
   */
  public static boolean areConsistencyChecksEnabled ()
  {
    return s_bConsistencyChecksEnabled;
  }

  /**
   * Enable or disable consistency checks. By default the consistency checks are
   * enabled (for backwards compatibility) but if performance is a real matter,
   * you may want to disable them globally.
   *
   * @param bEnabled
   *        <code>true</code> to enable them, <code>false</code> to disable
   *        them.
   * @since 5.0.2
   */
  public static void setConsistencyChecksEnabled (final boolean bEnabled)
  {
    s_bConsistencyChecksEnabled = bEnabled;
  }

  private ICSSProperty m_aProperty;
  private String m_sValue;
  private boolean m_bIsImportant;

  /**
   * Constructor
   *
   * @param aProperty
   *        The CSS property. May not be <code>null</code>.
   * @param sValue
   *        The String value to use. May be <code>null</code>. The value may
   *        <strong>NOT</strong> contain the <code>!important</code> flag! The
   *        value is internally trimmed to avoid leading and trailing.
   * @param bIsImportant
   *        <code>true</code> if the value should be important,
   *        <code>false</code> otherwise
   */
  public CSSValue (@Nonnull final ICSSProperty aProperty, @Nonnull final String sValue, final boolean bIsImportant)
  {
    setProperty (aProperty);
    setValue (sValue);
    setImportant (bIsImportant);
  }

  /**
   * @return The CSS property used. Never <code>null</code>.
   */
  @Nonnull
  public ICSSProperty getProperty ()
  {
    return m_aProperty;
  }

  /**
   * @return The CSS base property used. Never <code>null</code>.
   */
  @Nonnull
  public ECSSProperty getProp ()
  {
    return m_aProperty.getProp ();
  }

  /**
   * @return The property name including an eventually contained vendor prefix.
   *         Neither <code>null</code> nor empty.
   * @since 3.9.0
   */
  @Nonnull
  @Nonempty
  public String getPropertyName ()
  {
    return m_aProperty.getPropertyName ();
  }

  /**
   * Set the property of this CSS value (e.g. <code>background-color</code>).
   *
   * @param aProperty
   *        The CSS property to set. May not be <code>null</code>.
   * @return this
   * @since 3.7.3
   */
  @Nonnull
  public CSSValue setProperty (@Nonnull final ICSSProperty aProperty)
  {
    m_aProperty = ValueEnforcer.notNull (aProperty, "Property");
    return this;
  }

  /**
   * @return The CSS value used. May not be <code>null</code> but maybe empty.
   */
  @Nonnull
  public String getValue ()
  {
    return m_sValue;
  }

  /**
   * Set the value of this CSS value (e.g. <code>red</code> in case the property
   * is <code>background-color</code>).
   *
   * @param sValue
   *        The value to be set. May not be <code>null</code>. The value may
   *        <strong>NOT</strong> contain the <code>!important</code> flag! The
   *        value is internally trimmed to avoid leading and trailing.
   * @return this
   * @since 3.7.3
   */
  @Nonnull
  public CSSValue setValue (@Nonnull final String sValue)
  {
    ValueEnforcer.notNull (sValue, "Value");

    if (areConsistencyChecksEnabled ())
    {
      if (!m_aProperty.isValidValue (sValue))
        LOGGER.warn ("CSS: the value '" + sValue + "' is not valid for property '" + m_aProperty.getPropertyName () + "'");
      if (sValue.contains (CCSS.IMPORTANT_SUFFIX))
        LOGGER.warn ("CSS: the value '" +
                     sValue +
                     "' should not contain the '" +
                     CCSS.IMPORTANT_SUFFIX +
                     "' string! Use 'setImportant' method instead.");
    }
    m_sValue = sValue.trim ();
    return this;
  }

  /**
   * @return <code>true</code> if it is important, <code>false</code> if not
   */
  public boolean isImportant ()
  {
    return m_bIsImportant;
  }

  /**
   * Set the important flag of this value.
   *
   * @param bIsImportant
   *        <code>true</code> to mark it important, <code>false</code> to remove
   *        it.
   * @return this
   * @since 3.7.3
   */
  @Nonnull
  public CSSValue setImportant (final boolean bIsImportant)
  {
    m_bIsImportant = bIsImportant;
    return this;
  }

  @Nonnull
  public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
  {
    aSettings.checkVersionRequirements (m_aProperty);
    return m_aProperty.getPropertyName () +
           CCSS.SEPARATOR_PROPERTY_VALUE +
           m_sValue +
           (StringHelper.hasText (m_sValue) && m_bIsImportant ? CCSS.IMPORTANT_SUFFIX : "") +
           CCSS.DEFINITION_END;
  }

  @Override
  public boolean equals (final Object o)
  {
    if (o == this)
      return true;
    if (o == null || !getClass ().equals (o.getClass ()))
      return false;
    final CSSValue rhs = (CSSValue) o;
    return m_aProperty.getProp ().equals (rhs.m_aProperty.getProp ()) &&
           m_sValue.equals (rhs.m_sValue) &&
           m_bIsImportant == rhs.m_bIsImportant;
  }

  @Override
  public int hashCode ()
  {
    return new HashCodeGenerator (this).append (m_aProperty.getProp ()).append (m_sValue).append (m_bIsImportant).getHashCode ();
  }

  @Override
  public String toString ()
  {
    return new ToStringGenerator (this).append ("property", m_aProperty)
                                       .append ("value", m_sValue)
                                       .append ("important", m_bIsImportant)
                                       .getToString ();
  }
}
