/****************************************************************************

   mod_dav_pgsql (DASL) for apache 2.X
   CopyRight(C) 2005 SolutionBox Inc. All Rights reserved.
   Author : sean kim (sean@solutionbox.co.kr)

   $Id: props.c,v 1.2 2006/09/20 09:22:59 sean Exp $

   Redistribution and use in source and binary forms, with or with out
   modification, are not permitted in outside of SolutionBox Inc.

 ****************************************************************************/

/* TODO
1. remove etags filed
2. change collection to int
3. change all time field to long
*/

#include <httpd.h>
#include <http_protocol.h>
#include <http_log.h>
#include <http_core.h>		/* for ap_construct_url */

#include <apr_strings.h>
#include <apr_hash.h>
#include <apr_dbm.h>
#include <apr_file_io.h>

#include "dav_repos.h"
#include "dbms.h"
#include "util.h"


struct dav_namespace_map
{
    int *ns_map;
};


struct dav_db
{
    apr_pool_t *pool;
    dav_repos_resource *mdb_r;

    /* when used as a property database: */
    int version;		/* *minor* version of this db */

    dav_buffer ns_table;	/* table of namespace URIs */
    short ns_count;		/* number of entries in table */
    int ns_table_dirty;		/* ns_table was modified */
    apr_hash_t *uri_index;

    dav_buffer wb_key;		/* work buffer for dav_key */

    apr_datum_t iter;		/* iteration key */
};

static dav_error *dav_repos_db_open(apr_pool_t * p,
				    const dav_resource * resource, int ro,
				    dav_db ** pdb)
{
	DBG0("dav_repos_db_open: ");
	// 2013.08.22 dadamin
	// 디렉토리 경우 유일한 값을 추출할 수 없어서 속성 변경 불가능 하도록함
	if(resource && resource->info)
	{
		dav_resource_private *ctx = resource->info;
		if(ctx->db_r)
		{
			if(ctx->db_r->m_resource_type) 
			{
				*pdb = NULL;
				return NULL;
			}
		}
	}

	dav_db *db = apr_pcalloc(p, sizeof(*db));

    TRACE();
    /* Set some values for DB */
    db->pool = p;
    db->mdb_r = (dav_repos_resource *) resource->info->db_r;
    *pdb = db;

    db->uri_index = apr_hash_make(p);

    return NULL;
}

static void dav_repos_db_close(dav_db * db)
{
    TRACE();
}

static dav_error *dav_repos_db_define_namespaces(dav_db * db,
						 dav_xmlns_info * xi)
{
    const char *ns, *xmlns;
    dav_repos_property *dead_prop;
    dav_repos_resource *dbr = db->mdb_r;

    DBG0("dav_repos_db_define_namespaces: ");
    TRACE();

    /* Null lock */
    if (dbr == NULL)
		return NULL;

	// 2013.08.22 dadamin
	/* Check all used name space in this resource */
	for (dead_prop = dbr->pr; dead_prop; dead_prop = dead_prop->next) 
	{
		ns = dbms_get_ns(dbr, dead_prop->m_ns_id);
		if (ns && strlen(ns)) 
		{	/* not nullns */
			xmlns = apr_psprintf(dbr->p, "ns%ld", dead_prop->m_ns_id);
			dav_xmlns_add(xi, xmlns, ns);
		}
	}

    /* ### we don't have any other possible namespaces right now. */

    return NULL;
}

static dav_error *dav_repos_db_output_value(dav_db * db,
					    const dav_prop_name * name,
					    dav_xmlns_info * xi,
					    apr_text_header * phdr,
					    int *found)
{
	const char *s = NULL;
	const char *val;

	/* Get resource records */
	dav_repos_resource *dbr = db->mdb_r;
	apr_pool_t *pool = db->pool;

	DBG1("dav_repos_db_output_value: name:%s", name->name);

	TRACE();

	if (dbr == NULL) {
		*found = 0;
		return NULL;
	}

	DBG2("OUT %s  [] %s\n", name->ns, name->name);

	if (!strcmp(name->ns, "DAV:")) {
		val = apr_hash_get(dbr->lpr_hash, name->name, APR_HASH_KEY_STRING);
		if (val) {
			s = apr_psprintf(pool, "<D:%s>%s</D:%s>" DEBUG_CR,
					name->name, val, name->name);
		}
		// 2014.07.09 : 사용되지 않는 컬럼 정리(get_content_language,get_etag,supported_lock,lock_discovery)
		else if (!strcmp(name->name, "lockdiscovery") ) {
			s = apr_psprintf(pool, "<D:%s></D:%s>" DEBUG_CR,
					name->name, name->name);
		}
		else if (!strcmp(name->name, "supportedlock")) {

		}
	}

	DBG2("OUT2 %s  [] %s\n", name->ns, name->name);

	if (s == NULL) {		/* Dead props or Not found */
		/* Get the key */
		const char *key =
			dav_repos_build_ns_name_key(name->ns, name->name, pool);

		/* Get the dead prop */
		dav_repos_property *dead_prop =
			apr_hash_get(dbr->pr_hash, key, APR_HASH_KEY_STRING);

		if (dead_prop != NULL) {
			/* Null ns */
			if (strlen(name->ns) == 0 || !strcmp(name->ns, " ")) {
				s = apr_psprintf(pool, "<%s xmlns=\"\">%s</%s>" DEBUG_CR,
						name->name, dead_prop->m_propvalue, name->name);
			} else {		/* Not null ns */
				s = apr_psprintf(pool, "<ns%ld:%s>%s</ns%ld:%s>" DEBUG_CR,
						dead_prop->m_ns_id, name->name,
						dead_prop->m_propvalue, dead_prop->m_ns_id,
						name->name);
			}
		} else {
			// 2013.08.27 dadamin
			// SolDirve PROPFIND 시 네임스페이스 사용하기 때문에
			// 해당 파일(디렉토리 경우 소속된 모든 정보)에 대해서 
			// RCDB 네임스페이스 관련 조회 발생으로 응답 성능 저하가
			// 발생함으로  opendav 지정된(DAV:)와 기본 속성이 외에 값에
			// 대해서 오퍼레이션되지 않게함
#ifdef __USE_NAMESPACE__			
			// 2013.08.22 dadamin
			// t_dav_resource 없는 property에 대해서 조회
			dav_repos_property *pr = apr_palloc(db->pool, sizeof(*pr));
			
			// Let's fill fill pr
			pr->m_ns_id = dbms_get_ns_id(db->mdb_r, name->ns);
			pr->m_propname = name->name;

			if(dbms_get_dead_property(dbr, pr) == 0)
			{
				dbr->pr = pr;
				s = apr_psprintf(pool, "<ns%ld:%s>%s</ns%ld:%s>" DEBUG_CR,
					pr->m_ns_id, pr->m_propname,
					pr->m_propvalue, pr->m_ns_id,
					pr->m_propname);

				// set namespace
				// 해당 네임스페이스 존재하지 않을 시 PROPFIND에서 
				// 올바른 네임스페이스로 리턴되지 않는 것을 방지하기 위해서
				// 관련 네임스페이스를 헤쉬테이블에 저장함
				dbms_set_namespace(dbr, pr, name->ns);
			}
			else 
#endif // __USE_NAMESPACE__
			{
				DBG1("NOFPUND [%s]\n", key);
			}
		}
	}

	if (s) {
		*found = 1;
		apr_text_append(pool, phdr, s);
	}
	else {
		*found = 0;
	}

	return NULL;
}

static dav_error *dav_repos_db_map_namespaces(dav_db * db,
					      const apr_array_header_t *
					      namespaces,
					      dav_namespace_map ** mapping)
{

    dav_namespace_map *m = apr_palloc(db->pool, sizeof(*m));
    int i;
    int *pmap;
    const char **puri;

    TRACE();


    /*
       ** Iterate over the provided namespaces. If a namespace already appears
       ** in our internal map of URI -> ns_id, then store that in the map. If
       ** we don't know the namespace yet, then add it to the map and to our
       ** table of known namespaces.
     */
    pmap = apr_palloc(db->pool, namespaces->nelts * sizeof(*pmap));
    for (i = namespaces->nelts, puri = (const char **) namespaces->elts;
	 i-- > 0; ++puri, ++pmap) {

	const char *uri = *puri;
	apr_size_t uri_len = strlen(uri);
	long ns_id = (long) apr_hash_get(db->uri_index, uri, uri_len);

	if (ns_id == 0) {
	    dav_check_bufsize(db->pool, &db->ns_table, uri_len + 1);
	    memcpy(db->ns_table.buf + db->ns_table.cur_len, uri, uri_len + 1);
	    db->ns_table.cur_len += uri_len + 1;

	    /* copy the uri in case the passed-in namespaces changes in
	       some way. */
	    apr_hash_set(db->uri_index, apr_pstrdup(db->pool, uri), uri_len,
			 (void *) ((long)(db->ns_count + 1)));

	    db->ns_table_dirty = 1;

	    *pmap = db->ns_count++;
	}
	else {
	    *pmap = ns_id - 1;
	}
    }

    m->ns_map = pmap;
    *mapping = m;
    return NULL;
}

static dav_error *dav_repos_db_store(dav_db * db, const dav_prop_name * name,
				     const apr_xml_elem * elem,
				     dav_namespace_map * mapping)
{
    size_t l_val;
#ifdef __OPENDAV_DEBUG__
    char *buf;
#endif // __OPENDAV_DEBUG__
    char *val;
    dav_repos_property *pr = apr_palloc(db->pool, sizeof(*pr));

    TRACE();

	// 2013.08.27 dadamin
	// SolDirve PROPFIND 시 네임스페이스 사용하기 때문에
	// 해당 파일(디렉토리 경우 소속된 모든 정보)에 대해서 
	// RCDB 네임스페이스 관련 조회 발생으로 응답 성능 저하가
	// 발생함으로  opendav 지정된(DAV:)와 기본 속성이 외에 값에
	// 대해서 오퍼레이션되지 않게함
#ifndef __USE_NAMESPACE__
	return solbox_dav_new_error(db->pool, HTTP_BAD_REQUEST, 0,
		"Invalid namespace declaration.");
#endif // !__USE_NAMESPACE__

    /* Litmus test <aa xmlns=""> case */
    /*
       if(name->ns==NULL || strlen(name->ns)==0) {
       return solbox_dav_new_error(db->pool, HTTP_BAD_REQUEST, 0,
       "Invalid namespace declaration.");
       }
     */
    /* quote all the values in the element */
    /* ### be nice to do this without affecting the element itself */
    /* ### of course, the cast indicates Badness is occurring here */
    apr_xml_quote_elem(db->pool, (apr_xml_elem *) elem);

    /* generate a text blob for the xml:lang plus the contents */
    apr_xml_to_text(db->pool, elem, APR_XML_X2T_LANG_INNER, NULL,
		    mapping->ns_map, (const char **) &val, &l_val);
    /* Warning : val[0] value is NULL, so you should use 'val+1' */

    /* Let's fill fill pr */
    pr->m_ns_id = dbms_get_ns_id(db->mdb_r, name->ns);
    pr->m_propname = name->name;
    pr->m_propvalue = val + 1;

    if (dbms_set_dead_property(db->mdb_r, pr) != 0) {
	return solbox_dav_new_error(db->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
			     "An error occurred while getting a dead property.");
    }

#ifdef __OPENDAV_DEBUG__
	buf = apr_psprintf(db->pool, "I want to save [%ld:%s] [%s]",
		       pr->m_ns_id, pr->m_propname, pr->m_propvalue);

    DBG0(buf);
#endif // __OPENDAV_DEBUG__
    return NULL;
}


static dav_error *dav_repos_db_remove(dav_db * db, const dav_prop_name * name)
{
	// 2013.08.27 dadamin
	// SolDirve PROPFIND 시 네임스페이스 사용하기 때문에
	// 해당 파일(디렉토리 경우 소속된 모든 정보)에 대해서 
	// RCDB 네임스페이스 관련 조회 발생으로 응답 성능 저하가
	// 발생함으로  opendav 지정된(DAV:)와 기본 속성이 외에 값에
	// 대해서 오퍼레이션되지 않게함
#ifndef __USE_NAMESPACE__
	return solbox_dav_new_error(db->pool, HTTP_BAD_REQUEST, 0,
		"Invalid namespace declaration.");
#endif // !__USE_NAMESPACE__

#ifdef __OPENDAV_DEBUG__
	char *buf;
#endif // __OPENDAV_DEBUG__
    dav_repos_property *pr = apr_palloc(db->pool, sizeof(*pr));

    pr->m_ns_id = dbms_get_ns_id(db->mdb_r, name->ns);
    pr->m_propname = name->name;

    if (dbms_del_dead_property(db->mdb_r, pr) != 0) {
	return solbox_dav_new_error(db->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
			     "An error occurred while deleting a dead property.");
    }

    TRACE();
#ifdef __OPENDAV_DEBUG__
    buf = apr_psprintf(db->pool, "I want to delete [%s] ", pr->m_propname);
    DBG0(buf);
#endif // __OPENDAV_DEBUG__
    return NULL;
}


static int dav_repos_db_exists(dav_db * db, const dav_prop_name * name)
{
    /* Get resource records */
    dav_repos_resource *dbr = db->mdb_r;

    /* Build the key */
    const char *key;

    TRACE();

    if (dbr == NULL)
	return 0;

    key = dav_repos_build_ns_name_key(name->ns, name->name, dbr->p);

    /* Get the the prop */
    if (apr_hash_get(dbr->pr_hash, key, APR_HASH_KEY_STRING))
	return 1;

    return 0;
}

static dav_error *dav_repos_db_first_name(dav_db * db, dav_prop_name * pname)
{
    const char *key = NULL;
    apr_ssize_t klen;
    dav_repos_property *pr;

    /* Get resource records */
    dav_repos_resource *dbr = db->mdb_r;

    TRACE();

	if (dbr == NULL) {
		pname->ns = NULL;
		pname->name = NULL;
		return NULL;
	}

    dbr->pr_hash_index = apr_hash_first(db->pool, dbr->pr_hash);
	if (dbr->pr_hash_index == NULL) {
		pname->ns = NULL;
		pname->name = NULL;
		return NULL;
	}

    apr_hash_this(dbr->pr_hash_index, (void *) &key, &klen, (void *) &pr);

    /* Get ns and name from the key */
    dav_repos_get_ns_name_key(key, pname, db->pool);

    return NULL;
}

static dav_error *dav_repos_db_next_name(dav_db * db, dav_prop_name * pname)
{
    const char *key = NULL;
    apr_ssize_t klen;
    dav_repos_property *pr;

    /* Get resource records */
    dav_repos_resource *dbr = db->mdb_r;

    TRACE();
    pname->ns = NULL;
    pname->name = NULL;

    if (dbr == NULL) {
	return NULL;
    }

    /* Run first_name first */
    if (dbr->pr_hash_index == NULL)
	return NULL;

    dbr->pr_hash_index = apr_hash_next(dbr->pr_hash_index);

    /* End of hash */
    if (dbr->pr_hash_index == NULL)
	return NULL;

    apr_hash_this(dbr->pr_hash_index, (void *) &key, &klen, (void *) &pr);

    /* Get ns and name from the key */
    dav_repos_get_ns_name_key(key, pname, db->pool);

    return NULL;
}

static dav_error *dav_repos_db_get_rollback(dav_db * db,
					    const dav_prop_name * name,
					    dav_deadprop_rollback **
					    prollback)
{
    TRACE();
    return NULL;
}

static dav_error *dav_repos_db_apply_rollback(dav_db * db,
					      dav_deadprop_rollback *
					      rollback)
{
    TRACE();
    return NULL;
}

const dav_hooks_propdb dav_repos_hooks_propdb = {
    dav_repos_db_open,
    dav_repos_db_close,
    dav_repos_db_define_namespaces,
    dav_repos_db_output_value,
    dav_repos_db_map_namespaces,
    dav_repos_db_store,
    dav_repos_db_remove,
    dav_repos_db_exists,
    dav_repos_db_first_name,
    dav_repos_db_next_name,
    dav_repos_db_get_rollback,
    dav_repos_db_apply_rollback,
};
