/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"
#include "platform/text/DateTimeFormat.h"

#if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
#include "wtf/text/CString.h"
#include "wtf/text/StringBuilder.h"
#include <gtest/gtest.h>

using namespace blink;

class DateTimeFormatTest : public ::testing::Test {
public:
    typedef DateTimeFormat::FieldType FieldType;

    struct Token {
        String string;
        int count;
        FieldType fieldType;

        Token(FieldType fieldType, int count = 1)
            : count(count)
            , fieldType(fieldType)
        {
            ASSERT(fieldType != DateTimeFormat::FieldTypeLiteral);
        }

        Token(const String& string)
            : string(string)
            , count(0)
            , fieldType(DateTimeFormat::FieldTypeLiteral)
        {
        }

        bool operator==(const Token& other) const
        {
            return fieldType == other.fieldType && count == other.count && string == other.string;
        }

        String toString() const
        {
            switch (fieldType) {
            case DateTimeFormat::FieldTypeInvalid:
                return "*invalid*";
            case DateTimeFormat::FieldTypeLiteral: {
                StringBuilder builder;
                builder.append('"');
                builder.append(string);
                builder.append('"');
                return builder.toString();
            }
            default:
                return String::format("Token(%d, %d)", fieldType, count);
            }
        }
    };

    class Tokens {
    public:
        Tokens() { }

        explicit Tokens(const Vector<Token> tokens)
            : m_tokens(tokens)
        {
        }

        explicit Tokens(const String& string)
        {
            m_tokens.append(Token(string));
        }

        explicit Tokens(Token token1)
        {
            m_tokens.append(token1);
        }

        Tokens(Token token1, Token token2)
        {
            m_tokens.append(token1);
            m_tokens.append(token2);
        }

        Tokens(Token token1, Token token2, Token token3)
        {
            m_tokens.append(token1);
            m_tokens.append(token2);
            m_tokens.append(token3);
        }

        Tokens(Token token1, Token token2, Token token3, Token token4)
        {
            m_tokens.append(token1);
            m_tokens.append(token2);
            m_tokens.append(token3);
            m_tokens.append(token4);
        }

        Tokens(Token token1, Token token2, Token token3, Token token4, Token token5)
        {
            m_tokens.append(token1);
            m_tokens.append(token2);
            m_tokens.append(token3);
            m_tokens.append(token4);
            m_tokens.append(token5);
        }

        Tokens(Token token1, Token token2, Token token3, Token token4, Token token5, Token token6)
        {
            m_tokens.append(token1);
            m_tokens.append(token2);
            m_tokens.append(token3);
            m_tokens.append(token4);
            m_tokens.append(token5);
            m_tokens.append(token6);
        }

        bool operator==(const Tokens& other) const
        {
            return m_tokens == other.m_tokens;
        }

        String toString() const
        {
            StringBuilder builder;
            builder.append("Tokens(");
            for (unsigned index = 0; index < m_tokens.size(); ++index) {
                if (index)
                    builder.append(",");
                builder.append(m_tokens[index].toString());
            }
            builder.append(")");
            return builder.toString();
        }

    private:
        Vector<Token> m_tokens;
    };

protected:
    Tokens parse(const String& formatString)
    {
        TokenHandler handler;
        if (!DateTimeFormat::parse(formatString, handler))
            return Tokens(Token("*failed*"));
        return handler.tokens();
    }

    FieldType single(const char ch)
    {
        char formatString[2];
        formatString[0] = ch;
        formatString[1] = 0;
        TokenHandler handler;
        if (!DateTimeFormat::parse(formatString, handler))
            return DateTimeFormat::FieldTypeInvalid;
        return handler.fieldType(0);
    }

private:
    class TokenHandler : public DateTimeFormat::TokenHandler {
    public:
        virtual ~TokenHandler() { }

        FieldType fieldType(int index) const
        {
            return index >=0 && index < static_cast<int>(m_tokens.size()) ? m_tokens[index].fieldType : DateTimeFormat::FieldTypeInvalid;
        }

        Tokens tokens() const { return Tokens(m_tokens); }

    private:
        virtual void visitField(FieldType fieldType, int count) override
        {
            m_tokens.append(Token(fieldType, count));
        }

        virtual void visitLiteral(const String& string) override
        {
            m_tokens.append(Token(string));
        }

        Vector<Token> m_tokens;
    };
};

std::ostream& operator<<(std::ostream& os, const DateTimeFormatTest::Tokens& tokens)
{
    return os << tokens.toString().ascii().data();
}

TEST_F(DateTimeFormatTest, CommonPattern)
{
    EXPECT_EQ(Tokens(), parse(""));

    EXPECT_EQ(
        Tokens(
            Token(DateTimeFormat::FieldTypeYear, 4), Token("-"),
            Token(DateTimeFormat::FieldTypeMonth, 2), Token("-"),
            Token(DateTimeFormat::FieldTypeDayOfMonth, 2)),
        parse("yyyy-MM-dd"));

    EXPECT_EQ(
        Tokens(
            Token(DateTimeFormat::FieldTypeHour24, 2), Token(":"),
            Token(DateTimeFormat::FieldTypeMinute, 2), Token(":"),
            Token(DateTimeFormat::FieldTypeSecond, 2)),
        parse("kk:mm:ss"));

    EXPECT_EQ(
        Tokens(
            Token(DateTimeFormat::FieldTypeHour12), Token(":"),
            Token(DateTimeFormat::FieldTypeMinute), Token(" "),
            Token(DateTimeFormat::FieldTypePeriod)),
        parse("h:m a"));

    EXPECT_EQ(
        Tokens(
            Token(DateTimeFormat::FieldTypeYear), Token("Nen "),
            Token(DateTimeFormat::FieldTypeMonth), Token("Getsu "),
            Token(DateTimeFormat::FieldTypeDayOfMonth), Token("Nichi")),
        parse("y'Nen' M'Getsu' d'Nichi'"));
}

TEST_F(DateTimeFormatTest, MissingClosingQuote)
{
    EXPECT_EQ(Tokens("*failed*"), parse("'foo"));
    EXPECT_EQ(Tokens("*failed*"), parse("fo'o"));
    EXPECT_EQ(Tokens("*failed*"), parse("foo'"));
}

TEST_F(DateTimeFormatTest, Quote)
{
    EXPECT_EQ(Tokens("FooBar"), parse("'FooBar'"));
    EXPECT_EQ(Tokens("'"), parse("''"));
    EXPECT_EQ(Tokens("'-'"), parse("''-''"));
    EXPECT_EQ(Tokens("Foo'Bar"), parse("'Foo''Bar'"));
    EXPECT_EQ(
        Tokens(Token(DateTimeFormat::FieldTypeEra), Token("'s")),
        parse("G'''s'"));
    EXPECT_EQ(
        Tokens(Token(DateTimeFormat::FieldTypeEra), Token("'"), Token(DateTimeFormat::FieldTypeSecond)),
        parse("G''s"));
}

TEST_F(DateTimeFormatTest, SingleLowerCaseCharacter)
{
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('b'));
    EXPECT_EQ(DateTimeFormat::FieldTypeLocalDayOfWeekStandAlon, single('c'));
    EXPECT_EQ(DateTimeFormat::FieldTypeDayOfMonth, single('d'));
    EXPECT_EQ(DateTimeFormat::FieldTypeLocalDayOfWeek, single('e'));
    EXPECT_EQ(DateTimeFormat::FieldTypeModifiedJulianDay, single('g'));
    EXPECT_EQ(DateTimeFormat::FieldTypeHour12, single('h'));
    EXPECT_EQ(DateTimeFormat::FieldTypeHour24, single('k'));
    EXPECT_EQ(DateTimeFormat::FieldTypeMinute, single('m'));
    EXPECT_EQ(DateTimeFormat::FieldTypeQuaterStandAlone, single('q'));
    EXPECT_EQ(DateTimeFormat::FieldTypeSecond, single('s'));
    EXPECT_EQ(DateTimeFormat::FieldTypeExtendedYear, single('u'));
    EXPECT_EQ(DateTimeFormat::FieldTypeNonLocationZone, single('v'));
    EXPECT_EQ(DateTimeFormat::FieldTypeWeekOfMonth, single('W'));
    EXPECT_EQ(DateTimeFormat::FieldTypeYear, single('y'));
    EXPECT_EQ(DateTimeFormat::FieldTypeZone, single('z'));
}

TEST_F(DateTimeFormatTest, SingleLowerCaseInvalid)
{
    EXPECT_EQ(DateTimeFormat::FieldTypePeriod, single('a'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('f'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('i'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('j'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('l'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('n'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('o'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('p'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('r'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('t'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('x'));
}

TEST_F(DateTimeFormatTest, SingleUpperCaseCharacter)
{
    EXPECT_EQ(DateTimeFormat::FieldTypeMillisecondsInDay, single('A'));
    EXPECT_EQ(DateTimeFormat::FieldTypeDayOfYear, single('D'));
    EXPECT_EQ(DateTimeFormat::FieldTypeDayOfWeek, single('E'));
    EXPECT_EQ(DateTimeFormat::FieldTypeDayOfWeekInMonth, single('F'));
    EXPECT_EQ(DateTimeFormat::FieldTypeEra, single('G'));
    EXPECT_EQ(DateTimeFormat::FieldTypeHour23, single('H'));
    EXPECT_EQ(DateTimeFormat::FieldTypeHour11, single('K'));
    EXPECT_EQ(DateTimeFormat::FieldTypeMonthStandAlone, single('L'));
    EXPECT_EQ(DateTimeFormat::FieldTypeMonth, single('M'));
    EXPECT_EQ(DateTimeFormat::FieldTypeQuater, single('Q'));
    EXPECT_EQ(DateTimeFormat::FieldTypeFractionalSecond, single('S'));
    EXPECT_EQ(DateTimeFormat::FieldTypeWeekOfYear, single('w'));
    EXPECT_EQ(DateTimeFormat::FieldTypeYearOfWeekOfYear, single('Y'));
    EXPECT_EQ(DateTimeFormat::FieldTypeRFC822Zone, single('Z'));
}

TEST_F(DateTimeFormatTest, SingleUpperCaseInvalid)
{
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('B'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('C'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('I'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('J'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('N'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('O'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('P'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('R'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('T'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('U'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('V'));
    EXPECT_EQ(DateTimeFormat::FieldTypeInvalid, single('X'));
}

#endif
