/*      $NetBSD$ */

/*-
* Copyright (c) 2007 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jachym Holecek <[email protected]>.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by the NetBSD
*      Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
*    contributors may be used to endorse or promote products derived
*    from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 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 THE FOUNDATION OR 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 <sys/errno.h>

#include <prop/proplib.h>

#include "prop_object_impl.h"
#include "prop_codec_impl.h"


extern const struct _prop_codec         prop_codec_xml;
extern const struct _prop_codec         prop_codec_scn;

static prop_codec_t                     prop_codec_table[] = {
#if defined(_PROPLIB_CODEC_XML)
       &prop_codec_xml,
#endif
#if defined(_PROPLIB_CODEC_SCN)
       &prop_codec_scn,
#endif
       NULL
};

/* Default to the codec with smallest external representation available. */
#if !defined(_PROP_DEFAULT_CODEC)
#if defined(_PROPLIB_CODEC_SCN)
#define _PROP_DEFAULT_CODEC     "scn"
#elif defined(_PROPLIB_CODEC_XML)
#define _PROP_DEFAULT_CODEC     "xml"
#else
#define _PROP_DEFAULT_CODEC     ""      /* Runtime failure */
#endif
#endif

static const char *
prop_skip_space(const char *str)
{
       if (str == NULL)
               return (NULL);

       while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r')
               str++;

       return (str);
}

static prop_codec_t
prop_codec_guess(char c)
{
       int                     i;

       /*
        * Must be able to tell codec by the first non-white character.
        * For binary codecs this implies their header must not begin
        * with whitespace characters.
        */
       for (i = 0; prop_codec_table[i]; i++)
               if (strchr((const char *)prop_codec_table[i]->codec_sense,
                   c) != NULL)
                       return (prop_codec_table[i]);

       return (NULL);
}

static prop_object_t
prop_parse_single(prop_codec_t co, const char *str, prop_type_t type)
{
       prop_parser_t           pa;
       prop_object_t           po, pq;

       if (prop_parser_create(co, &pa))
               goto fail_0;

       if (prop_parser_exec(co, pa, (const u_char *)str, strlen(str)))
               goto fail_1;

       if ((po = prop_parser_yield(co, pa)) == NULL)
               goto fail_1;

       /* Expect ${str} contains exactly one object. */
       if ((pq = prop_parser_yield(co, pa)) != NULL) {
               prop_object_release(pq);
               goto fail_2;
       }

       if (prop_object_type(po) != type)
               goto fail_2;

       prop_parser_destroy(co, pa);
       return (po);

fail_2:
       prop_object_release(po);
fail_1:
       prop_parser_destroy(co, pa);
fail_0:
       return (NULL);
}

const char *
prop_codec_list(void)
{
       static boolean_t        doinit = TRUE;
       static char             names[8];       /* XXX Large enough */
       size_t                  idx = 0;
       int                     i;

       /* XXX locking? */
       if (doinit) {
               for (i = 0; prop_codec_table[i]; i++) {
                       strcpy(names + idx, prop_codec_table[i]->codec_name);
                       idx += strlen(prop_codec_table[i]->codec_name);

                       if (prop_codec_table[i + 1])
                               names[idx++] = ' ';
               }

               names[idx] = '\0';
               doinit = FALSE;
       }

       return (names);
}

prop_codec_t
prop_codec_lookup(const char *name)
{
       int                     i;

       if (name == NULL)
               name = _PROP_DEFAULT_CODEC;

       for (i = 0; prop_codec_table[i]; i++)
               if (strcmp(prop_codec_table[i]->codec_name, name) == 0)
                       return (prop_codec_table[i]);

       return (NULL);
}

prop_dictionary_t
prop_dictionary_internalize(const char *str)
{
       prop_codec_t            co;

       if ((str = prop_skip_space(str)) == NULL)
               return (NULL);

       if ((co = prop_codec_guess(*str)) == NULL)
               return (NULL);

       if (co->codec_dictionary_internalize)
               return ((co->codec_dictionary_internalize)(str));

       return (prop_parse_single(co, str, PROP_TYPE_DICTIONARY));
}

prop_array_t
prop_array_internalize(const char *str)
{
       prop_codec_t            co;

       if ((str = prop_skip_space(str)) == NULL)
               return (NULL);

       if ((co = prop_codec_guess(*str)) == NULL)
               return (NULL);

       if (co->codec_array_internalize)
               return ((co->codec_array_internalize)(str));

       return (prop_parse_single(co, str, PROP_TYPE_ARRAY));
}

char *
prop_dictionary_externalize(prop_dictionary_t pd)
{
       prop_codec_t            co = prop_codec_lookup(_PROP_DEFAULT_CODEC);

       _PROP_ASSERT(co);

       if (co->codec_dictionary_externalize)
               return ((co->codec_dictionary_externalize)(pd));

       if (co->codec_externalize_compound)
               return ((co->codec_externalize_compound)(pd));

       return (NULL);
}

char *
prop_array_externalize(prop_array_t pa)
{
       prop_codec_t            co = prop_codec_lookup(_PROP_DEFAULT_CODEC);

       _PROP_ASSERT(co);

       if (co->codec_array_externalize)
               return ((co->codec_array_externalize)(pa));

       if (co->codec_externalize_compound)
               return ((co->codec_externalize_compound)(pa));

       return (NULL);
}

char *
prop_codec_externalize(prop_codec_t co, prop_object_t po)
{
       _PROP_ASSERT(co);

       if (co->codec_externalize_compound)
               return ((co->codec_externalize_compound)(po));

       switch (prop_object_type(po)) {
       case PROP_TYPE_DICTIONARY:
               return ((co->codec_dictionary_externalize)(po));
       case PROP_TYPE_ARRAY:
               return ((co->codec_array_externalize)(po));
       default:
               return (NULL);
       }
}

int
prop_parser_create(prop_codec_t co, prop_parser_t *pp)
{
       _PROP_ASSERT(co);

       if (pp == NULL)
               return (EINVAL);

       if (co->codec_parser_create)
               return ((co->codec_parser_create)(pp));

       return (EOPNOTSUPP);
}

int
prop_parser_exec(prop_codec_t co, prop_parser_t pa, const u_char *str,
   size_t len)
{
       _PROP_ASSERT(co);

       if (co->codec_parser_exec)
               return ((co->codec_parser_exec)(pa, str, len));

       return (EOPNOTSUPP);
}

prop_object_t
prop_parser_yield(prop_codec_t co, prop_parser_t pa)
{
       _PROP_ASSERT(co);

       if (co->codec_parser_yield)
               return ((co->codec_parser_yield)(pa));

       return (NULL);
}

void
prop_parser_destroy(prop_codec_t co, prop_parser_t pa)
{
       _PROP_ASSERT(co);

       if (co->codec_parser_destroy)
               (co->codec_parser_destroy)(pa);
}