// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ListStyleInterpolation_h
#define ListStyleInterpolation_h

#include "core/animation/StyleInterpolation.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSValueList.h"
#include "core/css/resolver/StyleBuilder.h"

namespace blink {

template<typename InterpolationType, typename NonInterpolableData>
class ListStyleInterpolationImpl : public StyleInterpolation {
public:
    static PassRefPtr<ListStyleInterpolationImpl<InterpolationType, NonInterpolableData>> maybeCreateFromList(const CSSValue& start, const CSSValue& end, CSSPropertyID id, InterpolationRange range = RangeAll)
    {
        if (start.isValueList() && end.isValueList() && toCSSValueList(start).length() == toCSSValueList(end).length()) {
            const CSSValueList& startList = toCSSValueList(start);
            const CSSValueList& endList = toCSSValueList(end);

            for (size_t i = 0; i < toCSSValueList(start).length(); i++) {
                if (!InterpolationType::canCreateFrom(*startList.item(i), *endList.item(i))) {
                    return nullptr;
                }
            }

            Vector<typename InterpolationType::NonInterpolableType> startNonInterpolableData;

            OwnPtr<InterpolableValue> startValue = listToInterpolableValue(start, &startNonInterpolableData);
            OwnPtr<InterpolableValue> endValue = listToInterpolableValue(end);

            return adoptRef(new ListStyleInterpolationImpl<InterpolationType, NonInterpolableData>(startValue.release(), endValue.release(), id, startNonInterpolableData, range));
        }
        return nullptr;
    }

    void apply(StyleResolverState& state) const override
    {
        StyleBuilder::applyProperty(m_id, state, interpolableValueToList(m_cachedValue.get(), m_nonInterpolableData, m_range).get());
    }

private:
    ListStyleInterpolationImpl(PassOwnPtr<InterpolableValue> start, PassOwnPtr<InterpolableValue> end, CSSPropertyID id,
        Vector<typename InterpolationType::NonInterpolableType> nonInterpolableData, InterpolationRange range = RangeAll)
        : StyleInterpolation(start, end, id)
        , m_range(range)
    {
        m_nonInterpolableData.swap(nonInterpolableData);
    }

    InterpolationRange m_range;

    Vector<typename InterpolationType::NonInterpolableType> m_nonInterpolableData;

    static PassOwnPtr<InterpolableValue> listToInterpolableValue(const CSSValue& value, Vector<typename InterpolationType::NonInterpolableType>* nonInterpolableData = nullptr)
    {
        const CSSValueList& listValue = toCSSValueList(value);
        if (nonInterpolableData)
            nonInterpolableData->reserveCapacity(listValue.length());
        OwnPtr<InterpolableList> result = InterpolableList::create(listValue.length());
        typename InterpolationType::NonInterpolableType elementData = typename InterpolationType::NonInterpolableType();
        for (size_t i = 0; i < listValue.length(); i++) {
            result->set(i, InterpolationType::toInterpolableValue(*listValue.item(i), elementData));
            if (nonInterpolableData)
                nonInterpolableData->append(elementData);
        }
        return result.release();
    }

    static PassRefPtrWillBeRawPtr<CSSValue> interpolableValueToList(InterpolableValue* value, const Vector<typename InterpolationType::NonInterpolableType>& nonInterpolableData, InterpolationRange range = RangeAll)
    {
        InterpolableList* listValue = toInterpolableList(value);
        RefPtrWillBeRawPtr<CSSValueList> result = CSSValueList::createCommaSeparated();

        ASSERT(nonInterpolableData.size() == listValue->length());

        for (size_t i = 0; i < listValue->length(); i++)
            result->append(InterpolationType::fromInterpolableValue(*(listValue->get(i)), nonInterpolableData[i], range));
        return result.release();
    }

    friend class ListStyleInterpolationTest;
};

template<typename InterpolationType>
class ListStyleInterpolationImpl<InterpolationType, void> : public StyleInterpolation {
public:
    static PassRefPtr<ListStyleInterpolationImpl<InterpolationType, void>> maybeCreateFromList(const CSSValue& start, const CSSValue& end, CSSPropertyID id, InterpolationRange range = RangeAll)
    {
        if (!start.isValueList() || !end.isValueList())
            return nullptr;
        const CSSValueList& startList = toCSSValueList(start);
        const CSSValueList& endList = toCSSValueList(end);
        if (startList.length() != endList.length())
            return nullptr;
        for (const auto& value : startList) {
            if (!InterpolationType::canCreateFrom(*value))
                return nullptr;
        }
        for (const auto& value : endList) {
            if (!InterpolationType::canCreateFrom(*value))
                return nullptr;
        }
        return adoptRef(new ListStyleInterpolationImpl<InterpolationType, void>(listToInterpolableValue(start), listToInterpolableValue(end), id, range));
    }

private:
    ListStyleInterpolationImpl(PassOwnPtr<InterpolableValue> start, PassOwnPtr<InterpolableValue> end, CSSPropertyID id, InterpolationRange range = RangeAll)
        : StyleInterpolation(start, end, id), m_range(range)
    {
    }

    InterpolationRange m_range;

    static PassOwnPtr<InterpolableValue> listToInterpolableValue(const CSSValue& value)
    {
        const CSSValueList& listValue = toCSSValueList(value);
        OwnPtr<InterpolableList> result = InterpolableList::create(listValue.length());
        for (size_t i = 0; i < listValue.length(); i++)
            result->set(i, InterpolationType::toInterpolableValue(*listValue.item(i)));
        return result.release();
    }

    static PassRefPtrWillBeRawPtr<CSSValue> interpolableValueToList(InterpolableValue* value, InterpolationRange range = RangeAll)
    {
        InterpolableList* listValue = toInterpolableList(value);
        RefPtrWillBeRawPtr<CSSValueList> result = CSSValueList::createCommaSeparated();

        for (size_t i = 0; i < listValue->length(); i++)
            result->append(InterpolationType::fromInterpolableValue(*(listValue->get(i)), range));
        return result.release();
    }

    void apply(StyleResolverState& state) const override
    {
        StyleBuilder::applyProperty(m_id, state, interpolableValueToList(m_cachedValue.get(), m_range).get());
    }

    friend class ListStyleInterpolationTest;

};

template<typename InterpolationType>
class ListStyleInterpolation {
public:
    static PassRefPtr<ListStyleInterpolationImpl<InterpolationType, typename InterpolationType::NonInterpolableType>>  maybeCreateFromList(const CSSValue& start, const CSSValue& end, CSSPropertyID id, InterpolationRange range = RangeAll)
    {
        return ListStyleInterpolationImpl<InterpolationType, typename InterpolationType::NonInterpolableType>::maybeCreateFromList(start, end, id, range);
    }
};

} // namespace blink

#endif // ListStyleInterpolation_h
