#include "ncdispatch.h"
#include "ncuri.h"

#define MAXSERVERURL 4096

extern int NCSUBSTRATE_intialize(void);

/* Define vectors of zeros and ones for use with various nc_get_varX function*/
size_t nc_sizevector0[NC_MAX_VAR_DIMS];
size_t nc_sizevector1[NC_MAX_VAR_DIMS];
ptrdiff_t nc_ptrdiffvector1[NC_MAX_VAR_DIMS];

/* Define the known protocols and their manipulations */
static struct NCPROTOCOLLIST {
    char* protocol;
    char* substitute;
    int   modelflags;
} ncprotolist[] = {
    {"http",NULL,0},
    {"https",NULL,0},
    {"file",NULL,NC_DISPATCH_NCD},
    {"dods","http",NC_DISPATCH_NCD},
    {"dodss","https",NC_DISPATCH_NCD},
    {"cdmr","http",NC_DISPATCH_NCR|NC_DISPATCH_NC4},
    {"cdmrs","https",NC_DISPATCH_NCR|NC_DISPATCH_NC4},
    {"cdmremote","http",NC_DISPATCH_NCR|NC_DISPATCH_NC4},
    {"cdmremotes","https",NC_DISPATCH_NCR|NC_DISPATCH_NC4},
    {NULL,NULL,0} /* Terminate search */
};

/* Define the default servers to ping in order;
   make the order attempt to optimize
   against future changes.
*/
static const char* default_servers[] = {
"http://remotetest.unidata.ucar.edu",
NULL
};

/*
static nc_type longtype = (sizeof(long) == sizeof(int)?NC_INT:NC_INT64);
static nc_type ulongtype = (sizeof(unsigned long) == sizeof(unsigned int)?NC_UINT:NC_UINT64);
*/

/* Allow dispatch to do initialization */
int
NCDISPATCH_initialize(void)
{
    extern int NCSUBSTRATE_initialize(void);
    int i;
    NCSUBSTRATE_initialize();
    for(i=0;i<NC_MAX_VAR_DIMS;i++) {
	nc_sizevector0[i] = 0;
        nc_sizevector1[i] = 1;
        nc_ptrdiffvector1[i] = 1;
    }
    return NC_NOERR;
}

/* search list of servers and return first that succeeds when
   concatenated with the specified path part.
   Search list can be prefixed by the second argument.
*/
char*
NC_findtestserver(const char* path, const char** servers)
{
#ifdef USE_DAP
#ifdef ENABLE_DAP_REMOTE_TESTS
    /* NCDAP_ping is defined in libdap2/ncdap.c */
    const char** svc;
    int stat;
    char* url = (char*)malloc(MAXSERVERURL);

    if(path == NULL) path = "";
    if(strlen(path) > 0 && path[0] == '/')
	path++;

    if(servers != NULL) {
        for(svc=servers;*svc != NULL;svc++) {
            snprintf(url,MAXSERVERURL,"%s/%s",*svc,path);
            stat = NCDAP_ping(url);
            if(stat == NC_NOERR)
                return url;
        }
    }
    /* not found in user supplied list; try defaults */
    for(svc=default_servers;*svc != NULL;svc++) {
        snprintf(url,MAXSERVERURL,"%s/%s",*svc,path);
        stat = NCDAP_ping(url);
        if(stat == NC_NOERR)
            return url;
    }
    if(url) free(url);
#endif
#endif
    return NULL;
}


/* return 1 if path looks like a url; 0 otherwise */
int
NC_testurl(const char* path)
{
    int isurl = 0;
    NCURI* tmpurl = NULL;
    char* p;

    if(path == NULL) return 0;

    /* find leading non-blank */
    for(p=(char*)path;*p;p++) {if(*p != ' ') break;}

    /* Do some initial checking to see if this looks like a file path */
    if(*p == '/') return 0; /* probably an absolute file path */

    /* Ok, try to parse as a url */
    if(ncuriparse(path,&tmpurl)) {
	/* Do some extra testing to make sure this really is a url */
        /* Look for a knownprotocol */
        struct NCPROTOCOLLIST* protolist;
        for(protolist=ncprotolist;protolist->protocol;protolist++) {
	    if(strcmp(tmpurl->protocol,protolist->protocol) == 0) {
	        isurl=1;
		break;
	    }
	}
	ncurifree(tmpurl);
	return isurl;
    }
    return 0;
}

/*
Return the OR of some of the NC_DISPATCH flags
Assumes that the path is known to be a url
*/

int
NC_urlmodel(const char* path)
{
    int model = 0;
    NCURI* tmpurl = NULL;
    struct NCPROTOCOLLIST* protolist;

    if(!ncuriparse(path,&tmpurl)) goto done;

    /* Look at any prefixed parameters */
    if(ncurilookup(tmpurl,"netcdf4",NULL)
       || ncurilookup(tmpurl,"netcdf-4",NULL)) {
	model = (NC_DISPATCH_NC4|NC_DISPATCH_NCD);
    } else if(ncurilookup(tmpurl,"netcdf3",NULL)
              || ncurilookup(tmpurl,"netcdf-3",NULL)) {
	model = (NC_DISPATCH_NC3|NC_DISPATCH_NCD);
    } else if(ncurilookup(tmpurl,"cdmremote",NULL)
	      || ncurilookup(tmpurl,"cdmr",NULL)) {
	model = (NC_DISPATCH_NCR|NC_DISPATCH_NC4);
    }

    if(model == 0) {
        for(protolist=ncprotolist;protolist->protocol;protolist++) {
	    if(strcmp(tmpurl->protocol,protolist->protocol) == 0) {
    	        model |= protolist->modelflags;
    	        if(protolist->substitute) {
		    free(tmpurl->protocol);
    		    tmpurl->protocol = strdup(protolist->substitute);
    	        }
    	        break;
	    }
	}
    }

    /* Force NC_DISPATCH_NC3 if necessary */
    if((model & NC_DISPATCH_NC4) == 0)
	model |= (NC_DISPATCH_NC3 | NC_DISPATCH_NCD);

done:
    ncurifree(tmpurl);
    return model;
}

/* Override dispatch table management */
static NC_Dispatch* NC_dispatch_override = NULL;

/* Override dispatch table management */
NC_Dispatch*
NC_get_dispatch_override(void) {
    return NC_dispatch_override;
}

void NC_set_dispatch_override(NC_Dispatch* d)
{
    NC_dispatch_override = d;
}

/* Overlay by treating the tables as arrays of void*.
   Overlay rules are:
        overlay    base    merge
        -------    ----    -----
          null     null     null
          null      y        y
           x       null      x
           x        y        x
*/

int
NC_dispatch_overlay(const NC_Dispatch* overlay, const NC_Dispatch* base, NC_Dispatch* merge)
{
    void** voverlay = (void**)overlay;
    void** vmerge;
    int i;
    size_t count = sizeof(NC_Dispatch) / sizeof(void*);
    /* dispatch table must be exact multiple of sizeof(void*) */
    assert(count * sizeof(void*) == sizeof(NC_Dispatch));
    *merge = *base;
    vmerge = (void**)merge;
    for(i=0;i<count;i++) {
        if(voverlay[i] == NULL) continue;
        vmerge[i] = voverlay[i];
    }
    /* Finally, the merge model should always be the overlay model */
    merge->model = overlay->model;
    return NC_NOERR;
}
