/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkJSON_DEFINED
#define SkJSON_DEFINED

#include "SkTypes.h"

class SkStream;
class SkString;

class SkJSON {
public:
    enum Type {
        kObject,
        kArray,
        kString,
        kInt,
        kFloat,
        kBool,
    };
    
    class Array;
    
    class Object {
    private:
        struct Slot;

    public:
        Object();
        Object(const Object&);
        ~Object();

        /**
         *  Create a new slot with the specified name and value. The name
         *  parameter is copied, but ownership of the Object parameter is
         *  transferred. The Object parameter may be null, but the name must
         *  not be null.
         */
        void addObject(const char name[], Object* value);
        
        /**
         *  Create a new slot with the specified name and value. The name
         *  parameter is copied, but ownership of the Array parameter is
         *  transferred. The Array parameter may be null, but the name must
         *  not be null.
         */
        void addArray(const char name[], Array* value);
        
        /**
         *  Create a new slot with the specified name and value. Both parameters
         *  are copied. The value parameter may be null, but the name must
         *  not be null.
         */
        void addString(const char name[], const char value[]);
        
        /**
         *  Create a new slot with the specified name and value. The name
         *  parameter is copied, and must not be null.
         */
        void addInt(const char name[], int32_t value);
        
        /**
         *  Create a new slot with the specified name and value. The name
         *  parameter is copied, and must not be null.
         */
        void addFloat(const char name[], float value);
        
        /**
         *  Create a new slot with the specified name and value. The name
         *  parameter is copied, and must not be null.
         */
        void addBool(const char name[], bool value);

        /**
         *  Return the number of slots/fields in this object. These can be
         *  iterated using Iter.
         */
        int count() const;

        /**
         *  Returns true if a slot matching the name and Type is found.
         */
        bool find(const char name[], Type) const;
        bool findObject(const char name[], Object** = NULL) const;
        bool findArray(const char name[], Array** = NULL) const;
        bool findString(const char name[], SkString* = NULL) const;
        bool findInt(const char name[], int32_t* = NULL) const;
        bool findFloat(const char name[], float* = NULL) const;
        bool findBool(const char name[], bool* = NULL) const;

        /**
         *  Finds the first slot matching the name and Type and removes it.
         *  Returns true if found, false if not.
         */
        bool remove(const char name[], Type);

        void toDebugf() const;

        /**
         *  Iterator class which returns all of the fields/slots in an Object,
         *  in the order that they were added.
         */
        class Iter {
        public:
            Iter(const Object&);
            
            /**
             *  Returns true when there are no more entries in the iterator.
             *  In this case, no other methods should be called.
             */
            bool done() const;

            /**
             *  Moves the iterator to the next element. Should only be called
             *  if done() returns false.
             */
            void next();

            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false.
             */
            Type type() const;
            
            /**
             *  Returns the name of the current element. Should only be called
             *  if done() returns false.
             */
            const char* name() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kObject.
             */
            Object* objectValue() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kArray.
             */
            Array* arrayValue() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kString.
             */
            const char* stringValue() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kInt.
             */
            int32_t intValue() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kFloat.
             */
            float floatValue() const;
            
            /**
             *  Returns the type of the current element. Should only be called
             *  if done() returns false and type() returns kBool.
             */
            bool boolValue() const;

        private:
            Slot* fSlot;
        };

    private:
        Slot* fHead;
        Slot* fTail;
        
        const Slot* findSlot(const char name[], Type) const;
        Slot* addSlot(Slot*);
        void dumpLevel(int level) const;
        
        friend class Array;
    };
    
    class Array {
    public:
        /**
         *  Creates an array with the specified Type and element count. All
         *  entries are initialized to NULL/0/false.
         */
        Array(Type, int count);

        /**
         *  Creates an array of ints, initialized by copying the specified
         *  values.
         */
        Array(const int32_t values[], int count);
        
        /**
         *  Creates an array of floats, initialized by copying the specified
         *  values.
         */
        Array(const float values[], int count);
        
        /**
         *  Creates an array of bools, initialized by copying the specified
         *  values.
         */
        Array(const bool values[], int count);
        
        Array(const Array&);
        ~Array();
        
        int count() const { return fCount; }
        Type type() const { return fType; }

        /**
         *  Replace the element at the specified index with the specified
         *  Object (which may be null). Ownership of the Object is transferred.
         *  Should only be called if the Array's type is kObject.
         */
        void setObject(int index, Object*);
        
        /**
         *  Replace the element at the specified index with the specified
         *  Array (which may be null). Ownership of the Array is transferred.
         *  Should only be called if the Array's type is kArray.
         */
        void setArray(int index, Array*);

        /**
         *  Replace the element at the specified index with a copy of the
         *  specified string (which may be null). Should only be called if the
         *  Array's type is kString.
         */
        void setString(int index, const char str[]);

        Object* const* objects() const {
            SkASSERT(kObject == fType);
            return fArray.fObjects;
        }
        Array* const* arrays() const {
            SkASSERT(kObject == fType);
            return fArray.fArrays;
        }
        const char* const* strings() const {
            SkASSERT(kString == fType);
            return fArray.fStrings;
        }
        int32_t* ints() const {
            SkASSERT(kInt == fType);
            return fArray.fInts;
        }
        float* floats() const {
            SkASSERT(kFloat == fType);
            return fArray.fFloats;
        }
        bool* bools() const {
            SkASSERT(kBool == fType);
            return fArray.fBools;
        }

    private:
        int fCount;
        Type fType;
        union {
            void*    fVoids;
            Object** fObjects;
            Array**  fArrays;
            char**   fStrings;
            int32_t* fInts;
            float*   fFloats;
            bool*    fBools;
        } fArray;
        
        void init(Type, int count, const void* src);
        void dumpLevel(int level) const;
        
        friend class Object;
    };
};

#endif
