/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css.css3;

import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 *
 * flex: flex-grow flex-shrink flex-basis|auto|initial|inherit;
 *
 * The flex property specifies the length of the item, relative to the rest of the flexible items inside the same container.
 *
 * The flex property is a shorthand for the flex-grow, flex-shrink, and the flex-basis properties.
 *
 * Note: If the element is not a flexible item, the flex property has no effect.
 * Default value:  0 1 auto
 * Inherited:      no
 * Animatable:     yes
 * Version:        CSS3
 * JavaScript syntax:      object.style.flex="1"
 *
 *
 * The undefined value for flex-grow, flex-shrink and flex-basis in flex property is 1, 1 and 0% respectively.
 *
 *

 *
 * @author WFF
 * @since 1.0.0
 */
public class Flex extends AbstractCssProperty<Flex>
        implements StateChangeInformer<CssProperty> {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger.getLogger(Flex.class.getName());

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";
    public static final String AUTO = "auto";

    private static final List<String> PREDEFINED_CONSTANTS = Arrays
            .asList(INITIAL, INHERIT, AUTO);

    private String cssValue;

    private FlexGrow flexGrow;

    private FlexShrink flexShrink;

    private FlexBasis flexBasis;

    /**
     * The value 0 1 auto will be assigned as the cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public Flex() {
        setCssValue("0 1 auto");
    }

    /**
     * @param cssValue
     *                     the css value to set.
     */
    public Flex(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param flex
     *                 the {@code Flex} object from which the cssValue to
     *                 set.And, {@code null} will throw
     *                 {@code NullValueException}
     */
    public Flex(final Flex flex) {
        if (flex == null) {
            throw new NullValueException("flex can not be null");
        }
        setCssValue(flex.getCssValue());
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.FLEX;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssValue()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssValue() {
        return cssValue;
    }

    @Override
    public String toString() {
        return getCssName() + ": " + getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be in the format of
     *                     1 1 auto or 1 1 15px.
     *                     {@code null} is considered as an invalid value and it
     *                     will throw {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public Flex setCssValue(final String cssValue) {
        final String trimmedCssValue;
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value format should be as for example \"0 1 auto\" or initial/inherit.");
        } else if ((trimmedCssValue = TagStringUtil
                .toLowerCase(StringUtil.strip(cssValue))).isEmpty()) {
            throw new NullValueException(cssValue
                    + " is an invalid value. The value format should be as for example \"0 1 auto\" or initial/inherit.");
        }

        final String[] cssValueParts = StringUtil.splitBySpace(trimmedCssValue);

        if (cssValueParts.length > 1) {
            if (trimmedCssValue.contains(FlexBasis.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(FlexBasis.INHERIT)) {
                throw new InvalidValueException(
                        "The string 'inherit' makes the given cssValue invalid, please remove 'inherit' from it and try.");
            }
        } else {
            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                if (flexGrow != null) {
                    flexGrow.setAlreadyInUse(false);
                    flexGrow = null;
                }
                if (flexShrink != null) {
                    flexShrink.setAlreadyInUse(false);
                    flexShrink = null;
                }
                if (flexBasis != null) {
                    flexBasis.setAlreadyInUse(false);
                    flexBasis = null;
                }

                this.cssValue = trimmedCssValue;
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }
                return this;
            }
        }

        FlexGrow flexGrow = null;
        FlexShrink flexShrink = null;
        FlexBasis flexBasis = null;

        for (final String eachPart : cssValueParts) {
            if (flexGrow == null && FlexGrow.isValid(eachPart)) {
                if (this.flexGrow == null) {
                    flexGrow = new FlexGrow(eachPart);
                    flexGrow.setStateChangeInformer(this);
                    flexGrow.setAlreadyInUse(true);
                } else {
                    this.flexGrow.setCssValue(eachPart);
                    flexGrow = this.flexGrow;
                }
            } else if (flexShrink == null && FlexShrink.isValid(eachPart)) {
                if (this.flexGrow == null) {
                    flexShrink = new FlexShrink(eachPart);
                    flexShrink.setStateChangeInformer(this);
                    flexShrink.setAlreadyInUse(true);
                } else {
                    this.flexShrink.setCssValue(eachPart);
                    flexShrink = this.flexShrink;
                }
            } else if (flexBasis == null && FlexBasis.isValid(eachPart)) {
                if (this.flexBasis == null) {
                    flexBasis = new FlexBasis(eachPart);
                    flexBasis.setStateChangeInformer(this);
                    flexBasis.setAlreadyInUse(true);
                } else {
                    this.flexBasis.setCssValue(eachPart);
                    flexBasis = this.flexBasis;
                }
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();
        boolean invalid = true;
        if (flexGrow == null) {
            if (this.flexGrow == null) {
                flexGrow = new FlexGrow(1);
                flexGrow.setStateChangeInformer(this);
                flexGrow.setAlreadyInUse(true);
            } else {
                this.flexGrow.setValue(0);
                flexGrow = this.flexGrow;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(flexGrow.getCssValue()).append(' ');

        if (flexShrink == null) {
            if (this.flexShrink == null) {
                flexShrink = new FlexShrink(1);
                flexShrink.setStateChangeInformer(this);
                flexShrink.setAlreadyInUse(true);
            } else {
                this.flexShrink.setValue(1);
                flexShrink = this.flexShrink;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(flexShrink.getCssValue()).append(' ');

        if (flexBasis == null) {
            if (cssValueParts.length == 3) {
                throw new InvalidValueException(
                        "the given cssValue contains invalid flex-basis value.");
            }
            if (this.flexBasis == null) {
                flexBasis = new FlexBasis(0);
                flexBasis.setStateChangeInformer(this);
                flexBasis.setAlreadyInUse(true);
            } else {
                this.flexBasis.setAsAuto();
                flexBasis = this.flexBasis;
            }
        } else {
            invalid = false;
        }

        cssValueBuilder.append(flexBasis.getCssValue());

        if (invalid) {
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example \"0 1 auto\" or initial/inherit.");
        }
        this.cssValue = cssValueBuilder.toString();

        flexGrow.setStateChangeInformer(this);
        flexGrow.setAlreadyInUse(true);

        flexShrink.setStateChangeInformer(this);
        flexShrink.setAlreadyInUse(true);

        flexBasis.setStateChangeInformer(this);
        flexBasis.setAlreadyInUse(true);

        this.flexGrow = flexGrow;
        this.flexShrink = flexShrink;
        this.flexBasis = flexBasis;

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }

        return this;
    }

    /**
     * checks the given css value is valid for this class. It does't do a strict
     * validation.
     *
     * @param cssValue
     * @return
     * @since 1.0.0
     * @author WFF
     */
    public static boolean isValid(final String cssValue) {
        // TODO modify to make a strict validation
        if (cssValue == null || StringUtil.isBlank(cssValue)) {
            return false;
        }

        final String[] cssValueParts = StringUtil.splitBySpace(cssValue);

        FlexGrow flexGrow = null;
        FlexShrink flexShrink = null;
        FlexBasis flexBasis = null;

        for (final String eachPart : cssValueParts) {
            boolean invalid = true;

            if (flexGrow == null && FlexGrow.isValid(eachPart)) {
                flexGrow = new FlexGrow(eachPart);
                invalid = false;
            } else if (flexShrink == null && FlexShrink.isValid(eachPart)) {
                flexShrink = new FlexShrink(eachPart);
                invalid = false;
            } else if (flexBasis == null && FlexBasis.isValid(eachPart)) {
                flexBasis = new FlexBasis(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return flexGrow != null || flexShrink != null || flexBasis != null;
    }

    /**
     * sets as initial
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInitial() {
        setCssValue(INITIAL);
    }

    /**
     * sets as inherit
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInherit() {
        setCssValue(INHERIT);
    }

    /**
     * sets as auto
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsAuto() {
        setCssValue(AUTO);
    }

    public FlexBasis getFlexBasis() {
        return flexBasis;
    }

    public FlexShrink getFlexShrink() {
        return flexShrink;
    }

    public FlexGrow getFlexGrow() {
        return flexGrow;
    }

    public Flex setFlexGrow(final FlexGrow flexGrow) {
        if (flexGrow == null) {
            throw new InvalidValueException("flexGrow cannot be null");
        }

        if (this.flexGrow == flexGrow) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (this.flexGrow != null) {
            this.flexGrow.setCssValue(flexGrow.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given flexGrow to the existing flexGrow object.");
            }
        } else {
            if (flexGrow.isAlreadyInUse()) {
                this.flexGrow = new FlexGrow(flexGrow);
                this.flexGrow.setAlreadyInUse(true);
                this.flexGrow.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of FlexGrow as the given flexGrow object is already used by another object");
                }
            } else {
                this.flexGrow = flexGrow;
                this.flexGrow.setAlreadyInUse(true);
                this.flexGrow.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(flexGrow.getCssValue()).append(' ');

        if (flexShrink == null) {
            flexShrink = new FlexShrink(1);
            flexShrink.setStateChangeInformer(this);
            flexShrink.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexShrink.getCssValue()).append(' ');

        if (flexBasis == null) {
            flexBasis = new FlexBasis(0);
            flexBasis.setStateChangeInformer(this);
            flexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    public Flex setFlexShrink(final FlexShrink flexShrink) {
        if (flexShrink == null) {
            throw new InvalidValueException("flexShrink cannot be null");
        }

        if (this.flexShrink == flexShrink) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (flexGrow == null) {
            flexGrow = new FlexGrow(1);
            flexGrow.setStateChangeInformer(this);
            flexGrow.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexGrow.getCssValue()).append(' ');

        if (this.flexShrink != null) {
            this.flexShrink.setCssValue(flexShrink.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given flexShrink to the existing flexShrink object.");
            }
        } else {
            if (flexShrink.isAlreadyInUse()) {
                this.flexShrink = new FlexShrink(flexShrink);
                this.flexShrink.setAlreadyInUse(true);
                this.flexShrink.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of FlexShrink as the given flexShrink object is already used by another object");
                }
            } else {
                this.flexShrink = flexShrink;
                this.flexShrink.setAlreadyInUse(true);
                this.flexShrink.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(flexShrink.getCssValue()).append(' ');

        if (flexBasis == null) {
            flexBasis = new FlexBasis(0);
            flexBasis.setStateChangeInformer(this);
            flexBasis.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    public Flex setFlexBasis(final FlexBasis flexBasis) {
        if (flexBasis == null) {
            throw new InvalidValueException("flexBasis cannot be null");
        }

        if (this.flexBasis == flexBasis) {
            return this;
        }

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (flexGrow == null) {
            flexGrow = new FlexGrow(1);
            flexGrow.setStateChangeInformer(this);
            flexGrow.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexGrow.getCssValue()).append(' ');

        if (flexShrink == null) {
            flexShrink = new FlexShrink(1);
            flexShrink.setStateChangeInformer(this);
            flexShrink.setAlreadyInUse(true);
        }

        cssValueBuilder.append(flexShrink.getCssValue()).append(' ');

        if (this.flexBasis != null) {
            this.flexBasis.setCssValue(flexBasis.getCssValue());
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "copied the cssValue from the given flexBasis to the existing flexBasis object.");
            }
        } else {
            if (flexBasis.isAlreadyInUse()) {
                this.flexBasis = new FlexBasis(flexBasis);
                this.flexBasis.setAlreadyInUse(true);
                this.flexBasis.setStateChangeInformer(this);
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning(
                            "created a new object of FlexBasis as the given flexBasis object is already used by another object");
                }
            } else {
                this.flexBasis = flexBasis;
                this.flexBasis.setAlreadyInUse(true);
                this.flexBasis.setStateChangeInformer(this);
            }
        }

        cssValueBuilder.append(flexBasis.getCssValue());

        cssValue = cssValueBuilder.toString();

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    @Override
    public void stateChanged(final CssProperty stateChangedObject) {
        if (stateChangedObject instanceof FlexGrow) {

            final FlexGrow flexGrow = (FlexGrow) stateChangedObject;

            final String cssValue = flexGrow.getCssValue();

            if (FlexGrow.INITIAL.equals(cssValue)
                    || FlexGrow.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as flexGrow cssValue");
            }
            final StringBuilder sb = new StringBuilder(flexGrow.getCssValue());
            sb.append(' ').append(flexShrink.getCssValue()).append(' ')
                    .append(flexBasis.getCssValue());
            this.cssValue = sb.toString();
        } else if (stateChangedObject instanceof FlexShrink) {

            final FlexShrink flexShrink = (FlexShrink) stateChangedObject;

            final String cssValue = flexShrink.getCssValue();

            if (FlexShrink.INITIAL.equals(cssValue)
                    || FlexShrink.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as flexShrink cssValue");
            }
            final StringBuilder sb = new StringBuilder(flexGrow.getCssValue());
            sb.append(' ').append(flexShrink.getCssValue()).append(' ')
                    .append(flexBasis.getCssValue());
            this.cssValue = sb.toString();

        } else if (stateChangedObject instanceof FlexBasis) {

            final FlexBasis flexBasis = (FlexBasis) stateChangedObject;

            final String cssValue = flexBasis.getCssValue();

            if (FlexBasis.INITIAL.equals(cssValue)
                    || FlexBasis.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as flexBasis cssValue");
            }

            this.cssValue = new StringBuilder(flexGrow.getCssValue())
                    .append(' ').append(flexShrink.getCssValue()).append(' ')
                    .append(flexBasis.getCssValue()).toString();
        }
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    protected void addPredefinedConstant(final String constant) {
        PREDEFINED_CONSTANTS.add(constant);
    }
}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.webfirmframework.wffweb.tag.html.attribute.core;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;

import com.webfirmframework.wffweb.util.StringUtil;

/**
 * @author WFF
 * @since 2.1.15
 */
public abstract class AbstractValueSetAttribute extends AbstractAttribute {

    private static final long serialVersionUID = 1L;

    /**
     * sets the value for this attribute
     *
     * @param updateClient
     *                         true to update client browser page if it is
     *                         available. The default value is true but it will
     *                         be ignored if there is no client browser page.
     * @param value
     *                         the value for the attribute.
     * @since 2.1.15
     * @author WFF
     */
    @Override
    protected void setAttributeValue(final boolean updateClient,
            final String value) {
        if (value != null) {
            final Collection<String> allValues = extractValues(value);
            super.replaceAllInAttributeValueSet(updateClient, allValues);
        }
    }

    /*
     * splits by space
     */
    private static Collection<String> extractValues(final String value) {
        final String[] inputValues = StringUtil.splitBySpace(value);
        final Collection<String> allValues = new ArrayDeque<>();
        for (final String each : inputValues) {
            // As per Collections.addAll's doc it is faster than
            // allValues.addAll(Arrays.asList(values));
            Collections.addAll(allValues, StringUtil.strip(each));
        }
        return allValues;
    }

    /**
     * sets the value for this attribute
     *
     *
     * @param value
     *                  the value for the attribute.
     * @since 2.1.15
     * @author WFF
     */
    @Override
    protected void setAttributeValue(final String value) {
        if (value != null) {
            replaceAllInAttributeValueSet(value);
        }
    }

    /**
     * Checks if the value is a part of this attribute value.
     *
     * @param value
     *                  the value to be checked
     * @return true if the given value is contained in the value string of this
     *         attribute.
     * @since 2.1.15
     * @author WFF
     */
    public boolean contains(final String value) {
        return super.getAttributeValueSet().contains(value);
    }

    /**
     * Checks if the values are part of this attribute value.
     *
     * @param values
     *                   the values to be checked
     * @return true if the given values are contained in the value string of
     *         this attribute.
     * @since 2.1.15
     * @author WFF
     */
    public boolean containsAll(final Collection<String> values) {
        return super.getAttributeValueSet().containsAll(values);
    }

    @Override
    public String getAttributeValue() {
        return StringUtil.join(' ', getAttributeValueSet());
    }

    /**
     * @param attrValues
     * @since 2.1.15
     * @author WFF
     */
    protected void addAllToAttributeValueSet(final String... attrValues) {
        if (attrValues != null) {

            final Collection<String> allValues = new ArrayDeque<>();
            for (final String attrValue : attrValues) {
                String trimmmedValue = null;
                if (attrValue != null
                        && !(trimmmedValue = StringUtil.strip(attrValue))
                                .isEmpty()) {
                    // As per Collections.addAll's doc it is faster than
                    // allValues.addAll(Arrays.asList(values));
                    Collections.addAll(allValues,
                            StringUtil.splitBySpace(trimmmedValue));
                }
            }

            super.addAllToAttributeValueSet(allValues);
        }
    }

    /**
     * Removes all values from the attributeValueSet and adds the given
     * attribute values.
     *
     * @param attrValues
     * @since 3.0.1
     */
    protected void replaceAllInAttributeValueSet(final String... attrValues) {
        replaceAllInAttributeValueSet(true, attrValues);
    }

    /**
     * Removes all values from the attributeValueSet and adds the given
     * attribute values.
     *
     * @param updateClient
     * @param attrValues
     * @since 3.0.1
     */
    protected void replaceAllInAttributeValueSet(final boolean updateClient,
            final String... attrValues) {
        if (attrValues != null) {

            final Collection<String> allValues = new ArrayDeque<>();
            for (final String attrValue : attrValues) {
                if (attrValue != null) {
                    allValues.addAll(extractValues(attrValue));
                }
            }

            super.replaceAllInAttributeValueSet(updateClient, allValues);
        }
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
package com.webfirmframework.wffweb.css.css3;

import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.CssNameConstants;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.StringBuilderUtil;
import com.webfirmframework.wffweb.util.StringUtil;

/**
 *

 * columns: auto|column-width column-count|initial|inherit;
 *
 * The columns property is a shorthand property for setting column-width and column-count.
 * Default value:  auto auto
 * Inherited:      no
 * Animatable:     yes, see individual properties.
 * Version:        CSS3
 * JavaScript syntax:      object.style.columns="100px 3"
 *

 *
 * @author WFF
 * @since 1.0.0
 */
public class Columns extends AbstractCssProperty<Columns>
        implements StateChangeInformer<CssProperty> {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(Columns.class.getName());

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";

    private static final List<String> PREDEFINED_CONSTANTS = Arrays
            .asList(INITIAL, INHERIT);

    private String cssValue;

    private ColumnWidth columnWidth;

    private ColumnCount columnCount;

    /**
     * The value auto auto will be assigned as the cssValue.
     *
     * @author WFF
     * @since 1.0.0
     */
    public Columns() {
        setCssValue("auto auto");
    }

    /**
     * @param cssValue
     *                     the css value to set.
     */
    public Columns(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param columns
     *                    the {@code Columns} object from which the cssValue to
     *                    set.And, {@code null} will throw
     *                    {@code NullValueException}
     */
    public Columns(final Columns columns) {
        if (columns == null) {
            throw new NullValueException("columns can not be null");
        }
        setCssValue(columns.getCssValue());
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.COLUMNS;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssValue()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssValue() {
        return cssValue;
    }

    @Override
    public String toString() {
        return getCssName() + ": " + getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be in the format of
     *                     55px or 95%. {@code null}
     *                     is considered as an invalid value and it will throw
     *                     {@code NullValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public Columns setCssValue(final String cssValue) {
        // NB: cssValue can contain upper case so it should not be converted to
        // lower case.
        final String trimmedCssValue;
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value format should be as for example 17px 2. Or, initial/inherit.");
        } else if ((trimmedCssValue = StringUtil.strip(cssValue)).isEmpty()) {
            throw new NullValueException(cssValue
                    + " is an invalid value. The value format should be as for example 17px 2. Or, initial/inherit.");
        }

        final String[] cssValueParts = StringUtil.splitBySpace(trimmedCssValue);

        if (cssValueParts.length > 1) {
            if (trimmedCssValue.contains(ColumnWidth.INITIAL)) {
                throw new InvalidValueException(
                        "The string 'initial' makes the given cssValue invalid, please remove 'initial' from it and try.");
            }
            if (trimmedCssValue.contains(ColumnWidth.INHERIT)) {
                throw new InvalidValueException(
                        "The string 'inherit' makes the given cssValue invalid, please remove 'inherit' from it and try.");
            }
        } else {
            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                if (columnWidth != null) {
                    columnWidth.setAlreadyInUse(false);
                    columnWidth = null;
                }

                if (columnCount != null) {
                    columnCount.setAlreadyInUse(false);
                    columnCount = null;
                }

                this.cssValue = trimmedCssValue;
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }
                return this;
            }
        }

        ColumnWidth columnWidth = null;
        ColumnCount columnCount = null;

        for (final String eachPart : cssValueParts) {
            if (columnWidth == null && ColumnWidth.isValid(eachPart)) {
                if (this.columnWidth == null) {
                    columnWidth = new ColumnWidth(eachPart);
                    columnWidth.setStateChangeInformer(this);
                    columnWidth.setAlreadyInUse(true);
                } else {
                    this.columnWidth.setCssValue(eachPart);
                    columnWidth = this.columnWidth;
                }
            } else if (columnCount == null && ColumnCount.isValid(eachPart)) {
                if (this.columnCount == null) {
                    columnCount = new ColumnCount(eachPart);
                    columnCount.setStateChangeInformer(this);
                    columnCount.setAlreadyInUse(true);
                } else {
                    this.columnCount.setCssValue(eachPart);
                    columnCount = this.columnCount;
                }
            } else {
                throw new InvalidValueException(cssValue
                        + " is an invalid value. The value format should be as for example 17px 2. Or, initial/inherit.");
            }
        }

        final StringBuilder cssValueBuilder = new StringBuilder();
        boolean invalid = true;
        if (columnWidth != null) {
            cssValueBuilder.append(columnWidth.getCssValue()).append(' ');
            invalid = false;
        }
        if (columnCount != null) {
            cssValueBuilder.append(columnCount.getCssValue()).append(' ');
            invalid = false;
        }
        if (invalid) {
            throw new InvalidValueException(cssValue
                    + " is an invalid value. The value format should be as for example 17px 2. Or, initial/inherit.");
        }
        this.cssValue = StringBuilderUtil.getTrimmedString(cssValueBuilder);
        this.columnWidth = columnWidth;
        this.columnCount = columnCount;

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }

        return this;
    }

    /**
     * checks the given css value is valid for this class. It does't do a strict
     * validation.
     *
     * @param cssValue
     * @return
     * @since 1.0.0
     * @author WFF
     */
    public static boolean isValid(final String cssValue) {
        if (cssValue == null || StringUtil.isBlank(cssValue)) {
            return false;
        }

        final String[] cssValueParts = StringUtil.splitBySpace(cssValue);

        ColumnWidth columnWidth = null;
        ColumnCount columnCount = null;

        for (final String eachPart : cssValueParts) {
            boolean invalid = true;
            if (columnWidth == null && ColumnWidth.isValid(eachPart)) {
                columnWidth = new ColumnWidth(eachPart);
                invalid = false;
            } else if (columnCount == null && ColumnCount.isValid(eachPart)) {
                columnCount = new ColumnCount(eachPart);
                invalid = false;
            }
            if (invalid) {
                return false;
            }
        }

        return columnWidth != null || columnCount != null;
    }

    /**
     * sets as initial
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInitial() {
        setCssValue(INITIAL);
    }

    /**
     * sets as inherit
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInherit() {
        setCssValue(INHERIT);
    }

    public ColumnWidth getColumnWidth() {
        return columnWidth;
    }

    /**
     * @return the columnCount
     * @author WFF
     * @since 1.0.0
     */
    public ColumnCount getColumnCount() {
        return columnCount;
    }

    public Columns setColumnWidth(final ColumnWidth columnWidth) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (columnWidth != null) {
            final String columnWidthCssValue = columnWidth.getCssValue();
            if (ColumnWidth.INITIAL.equals(columnWidthCssValue)
                    || ColumnWidth.INHERIT.equals(columnWidthCssValue)) {
                throw new InvalidValueException(
                        "columnWidth cannot have initial/inherit as its cssValue");
            }
            cssValueBuilder.append(columnWidthCssValue).append(' ');
        }

        if (columnCount != null) {
            final String columnWidthCssValue = columnCount.getCssValue();
            cssValueBuilder.append(columnWidthCssValue);
        }

        final String trimmedCssValue = StringBuilderUtil
                .getTrimmedString(cssValueBuilder);
        cssValue = trimmedCssValue.isEmpty() ? INHERIT : trimmedCssValue;

        if (columnWidth != null && columnWidth.isAlreadyInUse()
                && this.columnWidth != columnWidth) {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "the given columnWidth is already used by another object so a new object or the previous object (if it exists) of ColumnWidth will be used");
            }
            return setCssValue(cssValue);
        }

        if (this.columnWidth != null) {
            this.columnWidth.setAlreadyInUse(false);
        }

        this.columnWidth = columnWidth;

        if (this.columnWidth != null) {
            this.columnWidth.setStateChangeInformer(this);
            this.columnWidth.setAlreadyInUse(true);
        }

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    public Columns setColumnCount(final ColumnCount columnCount) {

        final StringBuilder cssValueBuilder = new StringBuilder();

        if (columnWidth != null) {
            final String columnWidthCssValue = columnWidth.getCssValue();
            cssValueBuilder.append(columnWidthCssValue).append(' ');
        }

        if (columnCount != null) {
            final String columnWidthCssValue = columnCount.getCssValue();
            if (ColumnCount.INITIAL.equals(columnWidthCssValue)
                    || ColumnCount.INHERIT.equals(columnWidthCssValue)) {
                throw new InvalidValueException(
                        "columnWidth cannot have initial/inherit as its cssValue");
            }
            cssValueBuilder.append(columnWidthCssValue);
        }

        final String trimmedCssValue = StringBuilderUtil
                .getTrimmedString(cssValueBuilder);
        cssValue = trimmedCssValue.isEmpty() ? INHERIT : trimmedCssValue;

        if (columnCount != null && columnCount.isAlreadyInUse()
                && this.columnCount != columnCount) {
            if (LOGGER.isLoggable(Level.WARNING)) {
                LOGGER.warning(
                        "the given columnCount is already used by another object so a new object or the previous object (if it exists) of ColumnWidth will be used");
            }
            return setCssValue(cssValue);
        }

        if (this.columnCount != null) {
            this.columnCount.setAlreadyInUse(false);
        }

        this.columnCount = columnCount;

        if (this.columnCount != null) {
            this.columnCount.setStateChangeInformer(this);
            this.columnCount.setAlreadyInUse(true);
        }

        if (getStateChangeInformer() != null) {
            getStateChangeInformer().stateChanged(this);
        }
        return this;
    }

    @Override
    public void stateChanged(final CssProperty stateChangedObject) {
        if (stateChangedObject instanceof ColumnWidth) {

            final ColumnWidth columnWidth = (ColumnWidth) stateChangedObject;

            final String cssValue = columnWidth.getCssValue();

            if (ColumnWidth.INITIAL.equals(cssValue)
                    || ColumnWidth.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as columnWidth cssValue");
            }

            setColumnWidth(columnWidth);
        } else if (stateChangedObject instanceof ColumnCount) {

            final ColumnCount columnCount = (ColumnCount) stateChangedObject;

            final String cssValue = columnCount.getCssValue();

            if (ColumnCount.INITIAL.equals(cssValue)
                    || ColumnCount.INHERIT.equals(cssValue)) {
                throw new InvalidValueException(
                        "initial/inherit cannot be set as columnCount cssValue");
            }

            setColumnCount(columnCount);
        }
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    protected void addPredefinedConstant(final String constant) {
        PREDEFINED_CONSTANTS.add(constant);
    }
}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.webfirmframework.wffweb.css;

import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.InvalidValueException;
import com.webfirmframework.wffweb.NullValueException;
import com.webfirmframework.wffweb.css.core.AbstractCssProperty;
import com.webfirmframework.wffweb.css.core.CssProperty;
import com.webfirmframework.wffweb.informer.StateChangeInformer;
import com.webfirmframework.wffweb.util.StringUtil;
import com.webfirmframework.wffweb.util.TagStringUtil;

/**
 *

 * The margin shorthand property sets all the margin properties in one declaration. This property can have from one to four values.
 *
 * Examples:
 *
 *     margin:10px 5px 15px 20px;
 *         top margin is 10px
 *         right margin is 5px
 *         bottom margin is 15px
 *         left margin is 20px
 *
 *     margin:10px 5px 15px;
 *         top margin is 10px
 *         right and left margin are 5px
 *         bottom margin is 15px
 *
 *     margin:10px 5px;
 *         top and bottom margin are 10px
 *         right and left margin are 5px
 *
 *     margin:10px;
 *         all four margins are 10px
 *
 * Note: Negative values are not allowed.
 * Default value:  0
 * Inherited:      no
 * Animatable:     yes
 *
 * Version:        CSS1
 * JavaScript syntax:      object.style.margin="100px 20px"
 *

 *
 *
 * @author WFF
 * @since 1.0.0
 */
public class Margin extends AbstractCssProperty<Margin>
        implements StateChangeInformer<CssProperty> {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger
            .getLogger(Margin.class.getName());

    public static final String INITIAL = "initial";
    public static final String INHERIT = "inherit";
    private static final String AUTO = "auto";

    private static final List<String> PREDEFINED_CONSTANTS = Arrays
            .asList(INITIAL, INHERIT, AUTO);

    private String cssValue;

    private MarginTop marginTop;
    private MarginRight marginRight;
    private MarginBottom marginBottom;
    private MarginLeft marginLeft;

    /**
     * The {@code initial} will be set as the value
     */
    public Margin() {
        setCssValue(INITIAL);
    }

    /**
     * @param cssValue
     *                     the css value to set.
     */
    public Margin(final String cssValue) {
        setCssValue(cssValue);
    }

    /**
     * @param marginBottom
     *                         the {@code MarginBottom} object from which the
     *                         cssValue to set.And, {@code null} will throw
     *                         {@code NullValueException}
     */
    public Margin(final Margin marginBottom) {
        if (marginBottom == null) {
            throw new NullValueException("marginBottom can not be null");
        }
        setCssValue(marginBottom.getCssValue());
    }

    /**
     * the margin length to set. The alternative method {@code setCssValue} can
     * also be used.
     *
     * @param value
     * @return the current object
     * @since 1.0.0
     * @author WFF
     */
    public Margin setValue(final String value) {
        setCssValue(value);
        return this;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssName()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssName() {
        return CssNameConstants.MARGIN;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.css.CssProperty#getCssValue()
     *
     * @since 1.0.0
     *
     * @author WFF
     */
    @Override
    public String getCssValue() {
        return cssValue;
    }

    @Override
    public String toString() {
        return getCssName() + ": " + getCssValue();
    }

    /**
     * gets the value, {@code getCssValue} method can also be used to get the
     * same value.
     *
     * @return the value in String.
     * @since 1.0.0
     * @author WFF
     */
    public String getValue() {
        return getCssValue();
    }

    /**
     * @param cssValue
     *                     the value should be a length value, for example
     *                     5px. {@code null} is considered as an
     *                     invalid value and it will throw
     *                     {@code NullValueException}.And an empty string is
     *                     also considered as an invalid value and it will throw
     *                     {@code InvalidValueException}.
     * @since 1.0.0
     * @author WFF
     */
    @Override
    public Margin setCssValue(final String cssValue) {
        if (cssValue == null) {
            throw new NullValueException(
                    "null is an invalid value. The value should be css length for example 2px. Or, initial/inherit.");
        } else if (StringUtil.isBlank(cssValue)) {
            throw new InvalidValueException(
                    "blank string is an invalid value. The value should be css length for example 5px. Or, initial/inherit.");
        } else {
            final String trimmedCssValue = StringUtil.strip(cssValue);

            if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
                this.cssValue = trimmedCssValue;
                if (marginTop != null) {
                    marginTop.setAlreadyInUse(false);
                    marginTop = null;
                }
                if (marginRight != null) {
                    marginRight.setAlreadyInUse(false);
                    marginRight = null;
                }
                if (marginBottom != null) {
                    marginBottom.setAlreadyInUse(false);
                    marginBottom = null;
                }
                if (marginLeft != null) {
                    marginLeft.setAlreadyInUse(false);
                    marginLeft = null;
                }
                if (getStateChangeInformer() != null) {
                    getStateChangeInformer().stateChanged(this);
                }

                return this;
            }

            final String marginString = StringUtil
                    .convertToSingleSpace(trimmedCssValue);

            final String[] extractedMargins = StringUtil
                    .splitBySpace(marginString);

            if (extractedMargins.length == 1) {
                if (marginTop == null) {
                    marginTop = new MarginTop(extractedMargins[0]);
                    marginTop.setStateChangeInformer(this);
                    marginTop.setAlreadyInUse(true);
                } else {
                    marginTop.setCssValue(extractedMargins[0]);
                }
                if (marginRight == null) {
                    marginRight = new MarginRight(extractedMargins[0]);
                    marginRight.setStateChangeInformer(this);
                    marginRight.setAlreadyInUse(true);
                } else {
                    marginRight.setCssValue(extractedMargins[0]);
                }
                if (marginBottom == null) {
                    marginBottom = new MarginBottom(extractedMargins[0]);
                    marginBottom.setStateChangeInformer(this);
                    marginBottom.setAlreadyInUse(true);
                } else {
                    marginBottom.setCssValue(extractedMargins[0]);
                }
                if (marginLeft == null) {
                    marginLeft = new MarginLeft(extractedMargins[0]);
                    marginLeft.setStateChangeInformer(this);
                    marginLeft.setAlreadyInUse(true);
                } else {
                    marginLeft.setCssValue(extractedMargins[0]);
                }
            } else if (extractedMargins.length == 2) {
                if (marginTop == null) {
                    marginTop = new MarginTop(extractedMargins[0]);
                    marginTop.setStateChangeInformer(this);
                    marginTop.setAlreadyInUse(true);
                } else {
                    marginTop.setCssValue(extractedMargins[0]);
                }
                if (marginBottom == null) {
                    marginBottom = new MarginBottom(extractedMargins[0]);
                    marginBottom.setStateChangeInformer(this);
                    marginBottom.setAlreadyInUse(true);
                } else {
                    marginBottom.setCssValue(extractedMargins[0]);
                }
                if (marginRight == null) {
                    marginRight = new MarginRight(extractedMargins[1]);
                    marginRight.setStateChangeInformer(this);
                    marginRight.setAlreadyInUse(true);
                } else {
                    marginRight.setCssValue(extractedMargins[1]);
                }
                if (marginLeft == null) {
                    marginLeft = new MarginLeft(extractedMargins[1]);
                    marginLeft.setStateChangeInformer(this);
                    marginLeft.setAlreadyInUse(true);
                } else {
                    marginLeft.setCssValue(extractedMargins[1]);
                }
            } else if (extractedMargins.length == 3) {
                if (marginTop == null) {
                    marginTop = new MarginTop(extractedMargins[0]);
                    marginTop.setStateChangeInformer(this);
                    marginTop.setAlreadyInUse(true);
                } else {
                    marginTop.setCssValue(extractedMargins[0]);
                }
                if (marginRight == null) {
                    marginRight = new MarginRight(extractedMargins[1]);
                    marginRight.setStateChangeInformer(this);
                    marginRight.setAlreadyInUse(true);
                } else {
                    marginRight.setCssValue(extractedMargins[1]);
                }
                if (marginLeft == null) {
                    marginLeft = new MarginLeft(extractedMargins[1]);
                    marginLeft.setStateChangeInformer(this);
                    marginLeft.setAlreadyInUse(true);
                } else {
                    marginLeft.setCssValue(extractedMargins[1]);
                }
                if (marginBottom == null) {
                    marginBottom = new MarginBottom(extractedMargins[2]);
                    marginBottom.setStateChangeInformer(this);
                    marginBottom.setAlreadyInUse(true);
                } else {
                    marginBottom.setCssValue(extractedMargins[2]);
                }
            } else if (extractedMargins.length == 4) {
                if (marginTop == null) {
                    marginTop = new MarginTop(extractedMargins[0]);
                    marginTop.setStateChangeInformer(this);
                    marginTop.setAlreadyInUse(true);
                } else {
                    marginTop.setCssValue(extractedMargins[0]);
                }
                if (marginRight == null) {
                    marginRight = new MarginRight(extractedMargins[1]);
                    marginRight.setStateChangeInformer(this);
                    marginRight.setAlreadyInUse(true);
                } else {
                    marginRight.setCssValue(extractedMargins[1]);
                }
                if (marginBottom == null) {
                    marginBottom = new MarginBottom(extractedMargins[2]);
                    marginBottom.setStateChangeInformer(this);
                    marginBottom.setAlreadyInUse(true);
                } else {
                    marginBottom.setCssValue(extractedMargins[2]);
                }
                if (marginLeft == null) {
                    marginLeft = new MarginLeft(extractedMargins[3]);
                    marginLeft.setStateChangeInformer(this);
                    marginLeft.setAlreadyInUse(true);
                } else {
                    marginLeft.setCssValue(extractedMargins[3]);
                }
            } else {
                throw new InvalidValueException(
                        "the given cssValue is invalid");
            }

            this.cssValue = marginString;
            if (getStateChangeInformer() != null) {
                getStateChangeInformer().stateChanged(this);
            }
        }
        return this;
    }

    /**
     * sets as {@code initial}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInitial() {
        setCssValue(INITIAL);
    }

    /**
     * sets as {@code inherit}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsInherit() {
        setCssValue(INHERIT);
    }

    /**
     * sets as {@code inherit}
     *
     * @since 1.0.0
     * @author WFF
     */
    public void setAsAuto() {
        setCssValue(AUTO);
    }

    /**
     * @return the marginTop
     * @author WFF
     * @since 1.0.0
     */
    public MarginTop getMarginTop() {
        return marginTop;
    }

    /**
     * sets the top, right, bottom and left width in {@code Margin}. If the
     * given argument is already used by another object, then the
     * existing/cloned object will be used. And throws
     * {@code NullValueException} if any of the given argument is null.
     *
     * @param marginTop
     * @param marginRight
     * @param marginBottom
     * @param marginLeft
     * @author WFF
     * @since 1.0.0
     */
    public void setMargin(final MarginTop marginTop,
            final MarginRight marginRight, final MarginBottom marginBottom,
            final MarginLeft marginLeft) {

        if (marginTop != null && marginRight != null && marginBottom != null
                && marginLeft != null) {

            if (MarginTop.INITIAL.equals(marginTop.getCssValue())
                    || MarginTop.INHERIT.equals(marginTop.getCssValue())
                    || MarginRight.INITIAL.equals(marginRight.getCssValue())
                    || MarginRight.INHERIT.equals(marginRight.getCssValue())
                    || MarginBottom.INITIAL.equals(marginBottom.getCssValue())
                    || MarginBottom.INHERIT.equals(marginBottom.getCssValue())
                    || MarginLeft.INITIAL.equals(marginLeft.getCssValue())
                    || MarginLeft.INHERIT.equals(marginLeft.getCssValue())) {
                throw new InvalidValueException(
                        "Any or all of the given arguments have initial/inherit constant value as its cssValue");
            }

            try {
                final MarginTop marginTopTemp;
                final MarginRight marginRightTemp;
                final MarginBottom marginBottomTemp;
                final MarginLeft marginLeftTemp;

                if (this.marginTop != null) {
                    this.marginTop.setAlreadyInUse(false);
                    this.marginRight.setAlreadyInUse(false);
                    this.marginBottom.setAlreadyInUse(false);
                    this.marginLeft.setAlreadyInUse(false);
                }

                if (marginTop.isAlreadyInUse() && this.marginTop != marginTop) {
                    if (this.marginTop != null) {
                        marginTopTemp = this.marginTop
                                .setCssValue(marginTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginTop is already used by another object so the existing object is used");
                        }
                    } else {
                        marginTopTemp = marginTop.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginTop is already used by another object so its clone is assigned");
                        }
                    }
                } else {
                    marginTopTemp = marginTop;
                }

                if (marginRight.isAlreadyInUse()
                        && this.marginRight != marginRight) {
                    if (this.marginRight != null) {
                        marginRightTemp = this.marginRight
                                .setCssValue(marginTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginRight is already used by another object so the existing object is used");
                        }
                    } else {
                        marginRightTemp = marginRight.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginRight is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    marginRightTemp = marginRight;
                }

                if (marginBottom.isAlreadyInUse()
                        && this.marginBottom != marginBottom) {
                    if (this.marginBottom != null) {
                        marginBottomTemp = this.marginBottom
                                .setCssValue(marginTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginBottom is already used by another object so the existing object is used");
                        }
                    } else {
                        marginBottomTemp = marginBottom.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginBottom is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    marginBottomTemp = marginBottom;
                }

                if (marginLeft.isAlreadyInUse()
                        && this.marginLeft != marginLeft) {
                    if (this.marginLeft != null) {
                        marginLeftTemp = this.marginLeft
                                .setCssValue(marginTop.getCssValue());
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginLeft is already used by another object so the existing object is used");
                        }
                    } else {
                        marginLeftTemp = marginLeft.clone();
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.warning(
                                    "the given marginLeft is already used by another object so its clone is assigned");
                        }
                    }

                } else {
                    marginLeftTemp = marginLeft;
                }

                marginTopTemp.setAlreadyInUse(true);
                marginTopTemp.setStateChangeInformer(this);

                marginRightTemp.setAlreadyInUse(true);
                marginRightTemp.setStateChangeInformer(this);

                marginBottomTemp.setAlreadyInUse(true);
                marginBottomTemp.setStateChangeInformer(this);

                marginLeftTemp.setAlreadyInUse(true);
                marginLeftTemp.setStateChangeInformer(this);

                assignProducedCssValue(marginTop, marginRight, marginBottom,
                        marginLeft);

                this.marginTop = marginTopTemp;
                this.marginRight = marginRightTemp;
                this.marginBottom = marginBottomTemp;
                this.marginLeft = marginLeftTemp;

            } catch (final CloneNotSupportedException e) {
                throw new InvalidValueException(e);
            } catch (final Exception e) {
                throw new InvalidValueException(e);
            }

        } else {
            throw new NullValueException("cannot accept null arguments");
        }

    }

    /**
     * @return the marginRight
     * @author WFF
     * @since 1.0.0
     */
    public MarginRight getMarginRight() {
        return marginRight;
    }

    /**
     * @return the marginBottom
     * @author WFF
     * @since 1.0.0
     */
    public MarginBottom getMarginBottom() {
        return marginBottom;
    }

    /**
     * @return the marginLeft
     * @author WFF
     * @since 1.0.0
     */
    public MarginLeft getMarginLeft() {
        return marginLeft;
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    private void assignProducedCssValue(final MarginTop marginTop,
            final MarginRight marginRight, final MarginBottom marginBottom,
            final MarginLeft marginLeft) {

        final String marginTopCssValue = marginTop.getCssValue();
        final String marginRightCssValue = marginRight.getCssValue();
        final String marginBottomCssValue = marginBottom.getCssValue();
        final String marginLeftCssValue = marginLeft.getCssValue();

        if (marginTopCssValue.equals(marginRightCssValue)
                && marginRightCssValue.equals(marginBottomCssValue)
                && marginBottomCssValue.equals(marginLeftCssValue)) {

            cssValue = marginTopCssValue;

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        } else if (marginTopCssValue.equals(marginBottomCssValue)
                && marginRightCssValue.equals(marginLeftCssValue)) {

            final StringBuilder cssValueBuilder = new StringBuilder(
                    marginTopCssValue);
            cssValueBuilder.append(' ').append(marginRightCssValue);

            cssValue = cssValueBuilder.toString();

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }

        } else if (marginRightCssValue.equals(marginLeftCssValue)) {
            final StringBuilder cssValueBuilder = new StringBuilder(
                    marginTopCssValue);
            cssValueBuilder.append(' ').append(marginRightCssValue).append(' ')
                    .append(marginBottomCssValue);

            cssValue = cssValueBuilder.toString();

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        } else {
            cssValue = new StringBuilder(marginTopCssValue).append(' ')
                    .append(marginRightCssValue).append(' ')
                    .append(marginBottomCssValue).append(' ')
                    .append(marginLeftCssValue).toString();

            final StateChangeInformer<CssProperty> stateChangeInformer = getStateChangeInformer();
            if (stateChangeInformer != null) {
                stateChangeInformer.stateChanged(this);
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.webfirmframework.wffweb.informer.StateChangeInformer#stateChanged(
     * java.lang .Object)
     */
    @Override
    public void stateChanged(final CssProperty stateChangedObject) {

        if (stateChangedObject instanceof MarginTop) {
            final MarginTop marginTop = (MarginTop) stateChangedObject;
            if (MarginTop.INITIAL.equals(marginTop.getCssValue())
                    || MarginTop.INHERIT.equals(marginTop.getCssValue())) {
                throw new InvalidValueException(
                        "marginTop cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof MarginRight) {
            final MarginRight marginRight = (MarginRight) stateChangedObject;
            if (MarginRight.INITIAL.equals(marginRight.getCssValue())
                    || MarginRight.INHERIT.equals(marginRight.getCssValue())) {
                throw new InvalidValueException(
                        "marginRight cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof MarginBottom) {
            final MarginBottom marginBottom = (MarginBottom) stateChangedObject;
            if (MarginBottom.INITIAL.equals(marginBottom.getCssValue())
                    || MarginBottom.INHERIT
                            .equals(marginBottom.getCssValue())) {
                throw new InvalidValueException(
                        "marginBottom cannot have initial/inherit as its cssValue");
            }
        } else if (stateChangedObject instanceof MarginLeft) {
            final MarginLeft marginLeft = (MarginLeft) stateChangedObject;
            if (MarginLeft.INITIAL.equals(marginLeft.getCssValue())
                    || MarginLeft.INHERIT.equals(marginLeft.getCssValue())) {
                throw new InvalidValueException(
                        "marginLeft cannot have initial/inherit as its cssValue");
            }
        }

        assignProducedCssValue(marginTop, marginRight, marginBottom,
                marginLeft);

    }

    /**
     * @return true if its cssValue is any of the values
     *          initial, inherit or transparent.
     * @author WFF
     * @since 1.0.0
     */
    public boolean hasPredefinedConstantValue() {
        return PREDEFINED_CONSTANTS.contains(cssValue);
    }

    /**
     *
     * @author WFF
     * @since 1.0.0
     */
    public static boolean isValid(final String cssValue) {
        final String trimmedCssValue;
        if (cssValue == null || (trimmedCssValue = TagStringUtil
                .toLowerCase(StringUtil.strip(cssValue))).isEmpty()) {
            return false;
        }

        if (PREDEFINED_CONSTANTS.contains(trimmedCssValue)) {
            return true;
        }

        final String[] cssValueParts = StringUtil.splitBySpace(trimmedCssValue);

        for (final String eachPart : cssValueParts) {
            final boolean valid = MarginTop.isValid(eachPart);
            if ((valid
                    && (INITIAL.equals(eachPart) || INHERIT.equals(eachPart)))
                    || !valid) {
                return false;
            }
        }

        return true;
    }
}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.webfirmframework.wffweb.tag.html.formsandinputs;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import com.webfirmframework.wffweb.settings.WffConfiguration;
import com.webfirmframework.wffweb.tag.html.AbstractHtml;
import com.webfirmframework.wffweb.tag.html.TagNameConstants;
import com.webfirmframework.wffweb.tag.html.attribute.AttributeNameConstants;
import com.webfirmframework.wffweb.tag.html.attribute.Type;
import com.webfirmframework.wffweb.tag.html.attribute.core.AbstractAttribute;
import com.webfirmframework.wffweb.tag.html.identifier.FormAttributable;
import com.webfirmframework.wffweb.tag.html.identifier.GlobalAttributable;

/**
 * @author WFF
 * @since 1.0.0
 * @version 1.0.0
 *
 */
public class Form extends AbstractHtml {

    private static final long serialVersionUID = 1_0_0L;

    public static final Logger LOGGER = Logger.getLogger(Form.class.getName());

    {
        init();
    }

    /**
     *
     * @param base
     *                       i.e. parent tag of this tag
     * @param attributes
     *                       An array of {@code AbstractAttribute}
     *
     * @since 1.0.0
     */
    public Form(final AbstractHtml base,
            final AbstractAttribute... attributes) {
        super(TagNameConstants.FORM, base, attributes);
        if (WffConfiguration.isDirectionWarningOn()) {
            warnForUnsupportedAttributes(attributes);
        }
    }

    private static void warnForUnsupportedAttributes(
            final AbstractAttribute... attributes) {
        for (final AbstractAttribute abstractAttribute : attributes) {
            if (!(abstractAttribute != null
                    && (abstractAttribute instanceof FormAttributable
                            || abstractAttribute instanceof GlobalAttributable))) {
                LOGGER.warning(abstractAttribute
                        + " is not an instance of FormAttribute");
            }
        }
    }

    /**
     * invokes only once per object
     *
     * @author WFF
     * @since 1.0.0
     */
    protected void init() {
        // to override and use this method
    }

    /**
     * prepares and gets the js object for the given tag names under this form
     * tag. This js object may be used to return in onsubmit attribute.
     *
     * @param onlyForTagNames
     *
     *                            TagNameConstants.INPUT,
     *                            TagNameConstants.TEXTAREA and
     *                            TagNameConstants.SELECT. Pass object of
     *                            {@code List}, {@code HashSet} based on the
     *                            number of elements in it.
     *
     * @return the js object string for the given tag names. The returned js
     *         string will be as {name1.name1.value} where name1 is the value of
     *         name attribute of the field.
     * @since 2.1.8
     * @since 2.1.13 changed {@code Collection<Set> onlyForTagNames} to
     *        {@code Collection<String> onlyForTagNames}.
     * @author WFF
     */
    public String getNameBasedJsObject(
            final Collection<String> onlyForTagNames) {

        // "{" should not be changed to '{'
        final StringBuilder jsObjectBuilder = new StringBuilder("{");
        final Set<String> appendedValues = new HashSet<>();

        loopThroughAllNestedChildren(child -> {

            final String tagName = child.getTagName();

            if (onlyForTagNames.contains(tagName)) {

                final AbstractAttribute nameAttr = child
                        .getAttributeByName(AttributeNameConstants.NAME);
                final AbstractAttribute typeAttr = child
                        .getAttributeByName(AttributeNameConstants.TYPE);

                if (nameAttr != null) {
                    final String nameAttrValue = nameAttr.getAttributeValue();

                    if (!appendedValues.contains(nameAttrValue)) {

                        if (typeAttr != null) {
                            final String typeAttrValue = typeAttr
                                    .getAttributeValue();
                            if (Type.CHECKBOX.equals(typeAttrValue)
                                    || Type.RADIO.equals(typeAttrValue)) {
                                jsObjectBuilder.append(nameAttrValue)
                                        .append(':').append(nameAttrValue)
                                        .append(".checked,");
                                appendedValues.add(nameAttrValue);

                            } else {
                                jsObjectBuilder.append(nameAttrValue)
                                        .append(':').append(nameAttrValue)
                                        .append(".value,");
                                appendedValues.add(nameAttrValue);

                            }
                        } else {
                            jsObjectBuilder.append(nameAttrValue).append(':')
                                    .append(nameAttrValue).append(".value,");
                            appendedValues.add(nameAttrValue);

                        }

                    }
                }

            }
            return true;
        }, false, this);

        return jsObjectBuilder.replace(jsObjectBuilder.length() - 1,
                jsObjectBuilder.length(), "}").toString();
    }

    /**
     * prepares and gets the js object for the given tag names under this form
     * tag. This js object may be used to return in onsubmit attribute.
     *
     *
     * @param onlyForTagNames
     *
     *                            TagNameConstants.INPUT,
     *                            TagNameConstants.TEXTAREA and
     *                            TagNameConstants.SELECT Pass object of
     *                            {@code List}, {@code HashSet} based on the
     *                            number of elements in it.
     * @return the js object string for the given tag names. The returned js
     *         string will be as {name1:document.getElementById('name1').value}
     *         where name1 is the value of id attribute of the field.
     * @since 2.1.13
     * @author WFF
     */
    public String getIdBasedJsObject(final Collection<String> onlyForTagNames) {
        return getIdBasedJsObject("document.getElementById", onlyForTagNames);
    }

    /**
     * prepares and gets the js object for the given tag names under this form
     * tag. This js object may be used to return in onsubmit attribute.
     *
     *
     * @param functionName
     *                            the function name to get the element, eg:
     *                            document.getElementById
     * @param onlyForTagNames
     *
     *                            TagNameConstants.INPUT,
     *                            TagNameConstants.TEXTAREA and
     *                            TagNameConstants.SELECT Pass object of
     *                            {@code List}, {@code HashSet} based on the
     *                            number of elements in it.
     *
     *
     * @return the js object string for the given tag names. The returned js
     *         string will be as {name1:document.getElementById('name1').value}
     *         where name1 is the value of id attribute of the field.
     * @since 3.0.1
     * @author WFF
     */
    public String getIdBasedJsObject(final String functionName,
            final Collection<String> onlyForTagNames) {

        // "{" should not be changed to '{' if passing arg in constructor of
        // StringBuilder
        final StringBuilder jsObjectBuilder = new StringBuilder("{");
        final Set<String> appendedValues = new HashSet<>();

        loopThroughAllNestedChildren(child -> {

            final String tagName = child.getTagName();

            if (onlyForTagNames.contains(tagName)) {

                final AbstractAttribute idAttr = child
                        .getAttributeByName(AttributeNameConstants.ID);

                final AbstractAttribute typeAttr = child
                        .getAttributeByName(AttributeNameConstants.TYPE);

                if (idAttr != null) {
                    final String idAttrValue = idAttr.getAttributeValue();

                    if (!appendedValues.contains(idAttrValue)) {

                        final String docById = functionName + "('" + idAttrValue
                                + "')";

                        if (typeAttr != null) {

                            final String typeAttrValue = typeAttr
                                    .getAttributeValue();

                            if (Type.CHECKBOX.equals(typeAttrValue)
                                    || Type.RADIO.equals(typeAttrValue)) {

                                jsObjectBuilder.append(idAttrValue).append(':')
                                        .append(docById).append(".checked,");
                                appendedValues.add(idAttrValue);

                            } else {
                                jsObjectBuilder.append(idAttrValue).append(':')
                                        .append(docById).append(".value,");
                                appendedValues.add(idAttrValue);

                            }
                        } else {
                            jsObjectBuilder.append(idAttrValue).append(':')
                                    .append(docById).append(".value,");
                            appendedValues.add(idAttrValue);

                        }

                    }
                }

            }
            return true;
        }, false, this);

        return jsObjectBuilder.replace(jsObjectBuilder.length() - 1,
                jsObjectBuilder.length(), "}").toString();
    }

    /**
     *
     * prepares and gets the js object for the input tag names
     * (TagNameConstants.INPUT, TagNameConstants.TEXTAREA and
     * TagNameConstants.SELECT) under this form tag.

     * NB:- If there are any missing input tag types, please inform
     * webfirmframework to update this method.

     * This js object may be used to return in onsubmit attribute.
     *
     * @return the js object string for field names of TagNameConstants.INPUT,
     *         TagNameConstants.TEXTAREA and TagNameConstants.SELECT. The
     *         returned js string will be as {name1:name1.value} where name1 is
     *         the value of name attribute of the field. If the input type is
     *         checkbox/radio then checked property will be included instead of
     *         value property.
     * @since 2.1.8
     * @author WFF
     */
    public String getNameBasedJsObject() {

        final Set<String> tagNames = new HashSet<>();
        tagNames.add(TagNameConstants.INPUT);
        tagNames.add(TagNameConstants.TEXTAREA);
        tagNames.add(TagNameConstants.SELECT);

        return getNameBasedJsObject(tagNames);
    }

    /**
     *
     * prepares and gets the js object for the input tag names
     * (TagNameConstants.INPUT, TagNameConstants.TEXTAREA and
     * TagNameConstants.SELECT) under this form tag.

     * NB:- If there are any missing input tag types, please inform
     * webfirmframework to update this method.

     * This js object may be used to return in onsubmit attribute.
     *
     * @return the js object string for field names of TagNameConstants.INPUT,
     *         TagNameConstants.TEXTAREA and TagNameConstants.SELECT. The
     *         returned js string will be as
     *         {name1:document.getElementById('name1').value} where name1 is the
     *         value of id attribute of the field. If the input type is
     *         checkbox/radio then checked property will be included instead of
     *         value property.
     * @since 2.1.13
     * @author WFF
     */
    public String getIdBasedJsObject() {
        final Set<String> tagNames = new HashSet<>();
        tagNames.add(TagNameConstants.INPUT);
        tagNames.add(TagNameConstants.TEXTAREA);
        tagNames.add(TagNameConstants.SELECT);
        return getIdBasedJsObject(tagNames);
    }

    /**
     *
     * prepares and gets the js object for the input tag names
     * (TagNameConstants.INPUT, TagNameConstants.TEXTAREA and
     * TagNameConstants.SELECT) under this form tag.

     * NB:- If there are any missing input tag types, please inform
     * webfirmframework to update this method.

     * This js object may be used to return in onsubmit attribute.
     *
     * @param functionName
     *                         function name to get element, Eg:
     *                         document.getElementById
     *
     * @return the js object string for field names of TagNameConstants.INPUT,
     *         TagNameConstants.TEXTAREA and TagNameConstants.SELECT. The
     *         returned js string will be as {name1:gebi('name1').value} where
     *         name1 is the value of id attribute of the field and gebi is the
     *         function name to get element. If the input type is checkbox/radio
     *         then checked property will be included instead of value property.
     * @since 3.0.1
     * @author WFF
     */
    public String getIdBasedJsObject(final String functionName) {
        final Set<String> tagNames = new HashSet<>();
        tagNames.add(TagNameConstants.INPUT);
        tagNames.add(TagNameConstants.TEXTAREA);
        tagNames.add(TagNameConstants.SELECT);
        return getIdBasedJsObject(functionName, tagNames);
    }

    /**
     *
     * prepares and gets the js object for the input tag names
     * (TagNameConstants.INPUT, TagNameConstants.TEXTAREA and
     * TagNameConstants.SELECT) under this form tag.

     * NB:- If there are any missing input tag types, please inform
     * webfirmframework to update this method.

     * This js object may be used to return in onsubmit attribute.
     *
     *
     * @param additionalTagNames
     *
     *                               Tag names other than
     *                               TagNameConstants.INPUT,
     *                               TagNameConstants.TEXTAREA and
     *                               TagNameConstants.SELECT. Pass object of
     *                               {@code List}, {@code HashSet} based on the
     *                               number of elements in it.
     *
     * @return the js object string for field names of TagNameConstants.INPUT,
     *         TagNameConstants.TEXTAREA and TagNameConstants.SELECT. The
     *         returned js string will be as
     *         {name1:document.getElementById('name1').value} where name1 is the
     *         value of id attribute of the field. If the input type is
     *         checkbox/radio then checked property will be included instead of
     *         value property.
     * @since 2.1.13
     * @author WFF
     */
    public String getIdBasedJsObjectPlus(
            final Collection<String> additionalTagNames) {

        final Set<String> tagNames = new HashSet<>();
        tagNames.addAll(additionalTagNames);
        tagNames.add(TagNameConstants.INPUT);
        tagNames.add(TagNameConstants.TEXTAREA);
        tagNames.add(TagNameConstants.SELECT);

        return getIdBasedJsObject(tagNames);
    }

    /**
     *
     * prepares and gets the js object for the input tag names
     * (TagNameConstants.INPUT, TagNameConstants.TEXTAREA and
     * TagNameConstants.SELECT) under this form tag.

     * NB:- If there are any missing input tag types, please inform
     * webfirmframework to update this method.

     * This js object may be used to return in onsubmit attribute.
     *
     *
     * @param functionName
     *                               function name to get element, Eg:
     *                               document.getElementById
     * @param additionalTagNames
     *
     *                               Tag names other than
     *                               TagNameConstants.INPUT,
     *                               TagNameConstants.TEXTAREA and
     *                               TagNameConstants.SELECT. Pass object of
     *                               {@code List}, {@code HashSet} based on the
     *                               number of elements in it.
     *
     * @return the js object string for field names of TagNameConstants.INPUT,
     *         TagNameConstants.TEXTAREA and TagNameConstants.SELECT. The
     *         returned js string will be as {name1:gebi('name1').value} where
     *         name1 is the value of id attribute of the field and gebi is the
     *         function name to get the element. If the input type is
     *         checkbox/radio then checked property will be included instead of
     *         value property.
     * @since 3.0.1
     * @author WFF
     */
    public String getIdBasedJsObjectPlus(final String functionName,
            final Collection<String> additionalTagNames) {

        final Set<String> tagNames = new HashSet<>();
        tagNames.addAll(additionalTagNames);
        tagNames.add(TagNameConstants.INPUT);
        tagNames.add(TagNameConstants.TEXTAREA);
        tagNames.add(TagNameConstants.SELECT);

        return getIdBasedJsObject(functionName, tagNames);
    }

}


/*
 * Copyright 2014-2019 Web Firm Framework
 *
 * 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.
 * @author WFF
 */
/**
 *
 */
package com.webfirmframework.wffweb.view;

import java.nio.charset.Charset;

import com.webfirmframework.wffweb.io.OutputBuffer;

/**
 * @author WFF
 * @since 1.0.0
 * @version 1.0.0
 *
 */
public abstract class AbstractHtmlView implements HtmlView {

    private static final long serialVersionUID = 1_0_0L;

    private boolean preserveOutputBufferContent;

    private Charset charset = Charset.defaultCharset();

    private static final ThreadLocal<OutputBuffer> outputBufferTL = new ThreadLocal<OutputBuffer>() {

        /*
         * (non-Javadoc)
         *
         * @see java.lang.ThreadLocal#initialValue()
         */
        @Override
        protected OutputBuffer initialValue() {
            return new OutputBuffer();
        }
    };

    /*
     * (non-Javadoc)
     *
     * @see com.webfirmframework.wffweb.tag.Base#toValueString()
     */
    @Override
    public String toHtmlString() {
        final OutputBuffer outputBuffer = outputBufferTL.get();
        if (!preserveOutputBufferContent) {
            outputBuffer.setLength(0);
        }
        develop(outputBuffer);
        return outputBuffer.toBuilderString();
    }

    @Override
    public String toHtmlString(final Charset charset) {
        final Charset previousCharset = this.charset;
        try {
            this.charset = charset;
            return toHtmlString();
        } finally {
            this.charset = previousCharset;
        }
    }

    @Override
    public String toHtmlString(final String charset) {
        final Charset previousCharset = this.charset;
        try {
            this.charset = Charset.forName(charset);
            return toHtmlString();
        } finally {
            this.charset = previousCharset;
        }
    }

    @Override
    public String toHtmlString(final boolean rebuild) {
        final OutputBuffer outputBuffer = outputBufferTL.get();
        outputBuffer.setRebuild(rebuild);
        if (!preserveOutputBufferContent) {
            outputBuffer.setLength(0);
        }
        develop(outputBuffer);
        return outputBuffer.toBuilderString();
    }

    @Override
    public String toHtmlString(final boolean rebuild, final Charset charset) {
        final Charset previousCharset = this.charset;
        try {
            this.charset = charset;
            return toHtmlString(rebuild);
        } finally {
            this.charset = previousCharset;
        }
    }

    @Override
    public String toHtmlString(final boolean rebuild, final String charset) {
        final Charset previousCharset = this.charset;
        try {
            this.charset = Charset.forName(charset);
            return toHtmlString(rebuild);
        } finally {
            this.charset = previousCharset;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    // it is not a best practice to print html string by this method because if
    // it is used in ThreadLocal class it may cause memory leak.
    @Override
    public String toString() {
        return super.toString();
    }

    /**
     * @return the preserveOutputBufferContent
     */
    public boolean isPreserveOutputBufferContent() {
        return preserveOutputBufferContent;
    }

    /**
     * To preserve the content in {@code OutputBuffer} in
     * {@code HtmlView#develop(OutputBuffer)} method argument so that the new
     * content can be appended in the next invocation of
     * {@code HtmlView#develop(OutputBuffer)} method. The next invocation can be
     * done by invoking methods like {@code AbstractHtmlView#toHtmlString()} and
     * {@code AbstractHtmlView#toHtmlString(boolean)}.
     *
     * @param preserveOutputBufferContent
     *                                        true to preserve and
     *                                        false for not to
     *                                        preserve. The default values is
     *                                        false.
     *
     *
     */
    public void setPreserveOutputBufferContent(
            final boolean preserveOutputBufferContent) {
        this.preserveOutputBufferContent = preserveOutputBufferContent;
    }

}