#include <uwsgi.h>

#ifdef UWSGI_ROUTING

extern struct uwsgi_server uwsgi;

/*

	by Unbit

	syntax:

	route = /^foobar1(.*)/ cache:key=foo$1poo,content_type=text/html,name=foobar

*/

struct uwsgi_router_cache_conf {

	// the name of the cache
	char *name;
	size_t name_len;

	char *key;
	size_t key_len;

	char *var;
	size_t var_len;

	char *value;
	size_t value_len;

	// use mime types ?
	char *mime;

	char *as_num;

#ifdef UWSGI_ZLIB
	char *gzip;
	size_t gzip_len;
#endif

	char *content_type;
	size_t content_type_len;

	char *content_encoding;
	size_t content_encoding_len;

	struct uwsgi_cache *cache;

	char *expires_str;
	uint64_t expires;

	int64_t default_num;

	uint64_t flags;

	char *status_str;
	int status;
	char *no_offload;

	char *no_cl;
};

// this is allocated for each transformation
struct uwsgi_transformation_cache_conf {
	struct uwsgi_buffer *cache_it;
#ifdef UWSGI_ZLIB
        struct uwsgi_buffer *cache_it_gzip;
#endif

	int status;
	struct uwsgi_buffer *value;

        struct uwsgi_buffer *cache_it_to;
        uint64_t cache_it_expires;
};

static int transform_cache(struct wsgi_request *wsgi_req, struct uwsgi_transformation *ut) {
	struct uwsgi_transformation_cache_conf *utcc = (struct uwsgi_transformation_cache_conf *) ut->data;
	struct uwsgi_buffer *ub = ut->chunk;
	// force value
	if (utcc->value) {
		ub = utcc->value;
	}
	// store only successfull response
	if (wsgi_req->write_errors == 0 && (wsgi_req->status == 200 || (utcc->status && wsgi_req->status == utcc->status))  && ub->pos > 0) {
		if (utcc->cache_it) {
			uwsgi_cache_magic_set(utcc->cache_it->buf, utcc->cache_it->pos, ub->buf, ub->pos, utcc->cache_it_expires,
				UWSGI_CACHE_FLAG_UPDATE, utcc->cache_it_to ? utcc->cache_it_to->buf : NULL);
#ifdef UWSGI_ZLIB
			if (utcc->cache_it_gzip) {
				struct uwsgi_buffer *gzipped = uwsgi_gzip(ub->buf, ub->pos);
				if (gzipped) {
					uwsgi_cache_magic_set(utcc->cache_it_gzip->buf, utcc->cache_it_gzip->pos, gzipped->buf, gzipped->pos, utcc->cache_it_expires,
                                		UWSGI_CACHE_FLAG_UPDATE, utcc->cache_it_to ? utcc->cache_it_to->buf : NULL);
					uwsgi_buffer_destroy(gzipped);
				}
			}
#endif
		}
	}

	// free resources
	if (utcc->cache_it) uwsgi_buffer_destroy(utcc->cache_it);
#ifdef UWSGI_ZLIB
	if (utcc->cache_it_gzip) uwsgi_buffer_destroy(utcc->cache_it_gzip);
#endif
	if (utcc->cache_it_to) uwsgi_buffer_destroy(utcc->cache_it_to);
	if (utcc->value) uwsgi_buffer_destroy(utcc->value);
	free(utcc);
        return 0;
}


// be tolerant on errors
static int uwsgi_routing_func_cache_store(struct wsgi_request *wsgi_req, struct uwsgi_route *ur){
	struct uwsgi_router_cache_conf *urcc = (struct uwsgi_router_cache_conf *) ur->data2;

	struct uwsgi_transformation_cache_conf *utcc = uwsgi_calloc(sizeof(struct uwsgi_transformation_cache_conf));

	// build key and name
        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        utcc->cache_it = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->key, urcc->key_len);
        if (!utcc->cache_it) goto error;
	
	if (urcc->name) {
		utcc->cache_it_to = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->name, urcc->name_len);
		if (!utcc->cache_it_to) goto error;
	}

	if (urcc->value) {
		utcc->value = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->value, urcc->value_len);
                if (!utcc->value) goto error;
	}

	utcc->status = urcc->status;

#ifdef UWSGI_ZLIB
	if (urcc->gzip) {
		utcc->cache_it_gzip = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->gzip, urcc->gzip_len);
        	if (!utcc->cache_it_gzip) goto error;
	}
#endif
	utcc->cache_it_expires = urcc->expires;

	uwsgi_add_transformation(wsgi_req, transform_cache, utcc);

	return UWSGI_ROUTE_NEXT;

error:
	if (utcc->cache_it) uwsgi_buffer_destroy(utcc->cache_it);
#ifdef UWSGI_ZLIB
	if (utcc->cache_it_gzip) uwsgi_buffer_destroy(utcc->cache_it_gzip);
#endif
	if (utcc->cache_it_to) uwsgi_buffer_destroy(utcc->cache_it_to);
	free(utcc);
	return UWSGI_ROUTE_NEXT;
}

static int uwsgi_routing_func_cache(struct wsgi_request *wsgi_req, struct uwsgi_route *ur){

	char *mime_type = NULL;
	size_t mime_type_len = 0;
	struct uwsgi_router_cache_conf *urcc = (struct uwsgi_router_cache_conf *) ur->data2;

	char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);
	struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->key, urcc->key_len);
        if (!ub) return UWSGI_ROUTE_BREAK;

	uint64_t valsize = 0;
	uint64_t expires = 0;
	char *value = uwsgi_cache_magic_get(ub->buf, ub->pos, &valsize, &expires, urcc->name);
	if (urcc->mime && value) {
		mime_type = uwsgi_get_mime_type(ub->buf, ub->pos, &mime_type_len);	
	}
	uwsgi_buffer_destroy(ub);
	if (value) {
		if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) goto error;
		if (mime_type) {
                        uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_len);
		}
		else {
			if (uwsgi_response_add_content_type(wsgi_req, urcc->content_type, urcc->content_type_len)) goto error;
		}
		if (urcc->content_encoding_len) {
			if (uwsgi_response_add_header(wsgi_req, "Content-Encoding", 16, urcc->content_encoding, urcc->content_encoding_len)) goto error;	
		}
		if (expires) {
			if (uwsgi_response_add_expires(wsgi_req, expires)) goto error;	
		}
		if (!urcc->no_cl) {
			if (uwsgi_response_add_content_length(wsgi_req, valsize)) goto error;
		}
		if (wsgi_req->socket->can_offload && !ur->custom && !urcc->no_offload) {
                	if (!uwsgi_offload_request_memory_do(wsgi_req, value, valsize)) {
                        	wsgi_req->via = UWSGI_VIA_OFFLOAD;
                        	return UWSGI_ROUTE_BREAK;
                	}
		}

		uwsgi_response_write_body_do(wsgi_req, value, valsize);
		free(value);
		if (ur->custom)
			return UWSGI_ROUTE_NEXT;
		return UWSGI_ROUTE_BREAK;
	}
	
	return UWSGI_ROUTE_NEXT;
error:
	free(value);
	return UWSGI_ROUTE_BREAK;
}

// place a cache value in a request var
static int uwsgi_routing_func_cachevar(struct wsgi_request *wsgi_req, struct uwsgi_route *ur){

        struct uwsgi_router_cache_conf *urcc = (struct uwsgi_router_cache_conf *) ur->data2;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->key, urcc->key_len);
        if (!ub) return UWSGI_ROUTE_BREAK;

        uint64_t valsize = 0;
        char *value = uwsgi_cache_magic_get(ub->buf, ub->pos, &valsize, NULL, urcc->name);
        uwsgi_buffer_destroy(ub);
        if (value) {
		if (urcc->as_num) {
			char *tmp = value;
			if (valsize == 8) {
				int64_t *num = (int64_t *) value;
				value = uwsgi_64bit2str(*num);
			}
			else {
				value = uwsgi_64bit2str(0);
			}
			free(tmp);
		}
		if (!uwsgi_req_append(wsgi_req, urcc->var, urcc->var_len, value, valsize)) {
        		free(value);
			return UWSGI_ROUTE_BREAK;
        	}
		free(value);
        }

        return UWSGI_ROUTE_NEXT;
}

// set a cache item
static int uwsgi_routing_func_cacheset(struct wsgi_request *wsgi_req, struct uwsgi_route *ur){

        struct uwsgi_router_cache_conf *urcc = (struct uwsgi_router_cache_conf *) ur->data2;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->key, urcc->key_len);
        if (!ub) return UWSGI_ROUTE_BREAK;

        struct uwsgi_buffer *ub_val = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->value, urcc->value_len);
        if (!ub_val) {
		uwsgi_buffer_destroy(ub);
		return UWSGI_ROUTE_BREAK;
	}

        if (uwsgi_cache_magic_set(ub->buf, ub->pos, ub_val->buf, ub_val->pos, urcc->expires, 0, urcc->name)) {
		uwsgi_buffer_destroy(ub);
		uwsgi_buffer_destroy(ub_val);
		return UWSGI_ROUTE_BREAK;
	}
        uwsgi_buffer_destroy(ub);
	uwsgi_buffer_destroy(ub_val);
        return UWSGI_ROUTE_NEXT;
}

// cacheinc/cachedec/cachemul/cachediv
static int uwsgi_routing_func_cachemath(struct wsgi_request *wsgi_req, struct uwsgi_route *ur){

        struct uwsgi_router_cache_conf *urcc = (struct uwsgi_router_cache_conf *) ur->data2;

        char **subject = (char **) (((char *)(wsgi_req))+ur->subject);
        uint16_t *subject_len = (uint16_t *)  (((char *)(wsgi_req))+ur->subject_len);

        struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->key, urcc->key_len);
        if (!ub) return UWSGI_ROUTE_BREAK;

	int64_t num = urcc->default_num;

	if (urcc->value) {
        	struct uwsgi_buffer *ub_val = uwsgi_routing_translate(wsgi_req, ur, *subject, *subject_len, urcc->value, urcc->value_len);
        	if (!ub_val) {
                	uwsgi_buffer_destroy(ub);
                	return UWSGI_ROUTE_BREAK;
        	}
		num = strtol(ub_val->buf, NULL, 10);
                uwsgi_buffer_destroy(ub_val);
	}

        if (uwsgi_cache_magic_set(ub->buf, ub->pos, (char *) &num, 8, urcc->expires, urcc->flags, urcc->name)) {
                uwsgi_buffer_destroy(ub);
                return UWSGI_ROUTE_BREAK;
        }
        uwsgi_buffer_destroy(ub);
        return UWSGI_ROUTE_NEXT;
}



static int uwsgi_router_cache_store(struct uwsgi_route *ur, char *args) {
	ur->func = uwsgi_routing_func_cache_store;
	ur->data = args;
	ur->data_len = strlen(args);
	struct uwsgi_router_cache_conf *urcc = uwsgi_calloc(sizeof(struct uwsgi_router_cache_conf));
	if (uwsgi_kvlist_parse(ur->data, ur->data_len, ',', '=',
                        "key", &urcc->key,
#ifdef UWSGI_ZLIB
                        "gzip", &urcc->gzip,
#endif
                        "name", &urcc->name,
                        "value", &urcc->value,
			"status", &urcc->status_str,
			"code", &urcc->status_str,
                        "expires", &urcc->expires_str, NULL)) {
                        uwsgi_log("invalid cachestore route syntax: %s\n", args);
			goto error;
                }

                if (urcc->key) {
                        urcc->key_len = strlen(urcc->key);
                }

#ifdef UWSGI_ZLIB
		if (urcc->gzip) {
			urcc->gzip_len = strlen(urcc->gzip);
		}
#endif

		if (urcc->name) {
                        urcc->name_len = strlen(urcc->name);
		}

                if (!urcc->key) {
                        uwsgi_log("invalid cachestore route syntax: you need to specify a cache key\n");
			goto error;
                }

		if (urcc->expires_str) {
			urcc->expires = strtoul(urcc->expires_str, NULL, 10);
		}

		if (urcc->value) {
			urcc->value_len = strlen(urcc->value);
		}

		if (urcc->status_str) {
                        urcc->status = atoi(urcc->status_str);
                }

	ur->data2 = urcc;
        return 0;
error:
	if (urcc->key) free(urcc->key);
	if (urcc->name) free(urcc->name);
	if (urcc->expires_str) free(urcc->expires_str);
	free(urcc);
	return -1;
}

static int uwsgi_router_cache(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_cache;
        ur->data = args;
        ur->data_len = strlen(args);
	struct uwsgi_router_cache_conf *urcc = uwsgi_calloc(sizeof(struct uwsgi_router_cache_conf));
                if (uwsgi_kvlist_parse(ur->data, ur->data_len, ',', '=',
                        "key", &urcc->key,
                        "content_type", &urcc->content_type,
                        "content_encoding", &urcc->content_encoding,
                        "mime", &urcc->mime,
                        "name", &urcc->name,
                        "no_offload", &urcc->no_offload,
                        "no_content_length", &urcc->no_cl,
                        "no_cl", &urcc->no_cl,
                        "nocl", &urcc->no_cl,
                        NULL)) {
			uwsgi_log("invalid route syntax: %s\n", args);
			exit(1);
                }

		if (urcc->key) {
			urcc->key_len = strlen(urcc->key);
		}

		if (!urcc->key) {
			uwsgi_log("invalid route syntax: you need to specify a cache key\n");
			exit(1);
		}

                if (!urcc->content_type) urcc->content_type = "text/html";

                urcc->content_type_len = strlen(urcc->content_type);

		if (urcc->content_encoding) {
			urcc->content_encoding_len = strlen(urcc->content_encoding);
		}

                ur->data2 = urcc;
	return 0;
}

static int uwsgi_router_cache_continue(struct uwsgi_route *ur, char *args) {
	uwsgi_router_cache(ur, args);
	ur->custom = 1;
	return 0;
}

static struct uwsgi_router_cache_conf *uwsgi_router_cachemath(struct uwsgi_route *ur, char *args) {
	ur->func = uwsgi_routing_func_cachemath;
	ur->data = args;
        ur->data_len = strlen(args);

	struct uwsgi_router_cache_conf *urcc = uwsgi_calloc(sizeof(struct uwsgi_router_cache_conf));
	if (uwsgi_kvlist_parse(ur->data, ur->data_len, ',', '=',
                        "key", &urcc->key,
                        "value", &urcc->value,
                        "name", &urcc->name,
                        "expires", &urcc->expires_str, NULL)) {
                        uwsgi_log("invalid cachemath route syntax: %s\n", args);
                        goto error;
                }

                if (urcc->key) {
                        urcc->key_len = strlen(urcc->key);
                }

                if (urcc->value) {
                        urcc->value_len = strlen(urcc->value);
                }

                if (urcc->name) {
                        urcc->name_len = strlen(urcc->name);
                }

                if (!urcc->key) {
                        uwsgi_log("invalid cachemath route syntax: you need to specify a cache key\n");
                        goto error;
                }

                if (urcc->expires_str) {
                        urcc->expires = strtoul(urcc->expires_str, NULL, 10);
                }

	urcc->flags = UWSGI_CACHE_FLAG_UPDATE|UWSGI_CACHE_FLAG_MATH|UWSGI_CACHE_FLAG_FIXEXPIRE;
        ur->data2 = urcc;
        return urcc;
error:
        if (urcc->key) free(urcc->key);
        if (urcc->name) free(urcc->name);
        if (urcc->value) free(urcc->value);
        if (urcc->expires_str) free(urcc->expires_str);
        free(urcc);
        return NULL;	
}

static int uwsgi_router_cacheinc(struct uwsgi_route *ur, char *args) {
	struct uwsgi_router_cache_conf *urcc = uwsgi_router_cachemath(ur, args);
	if (!urcc) return -1;
	urcc->default_num = 1;
	urcc->flags |= UWSGI_CACHE_FLAG_INC;
	return 0;
}

static int uwsgi_router_cachedec(struct uwsgi_route *ur, char *args) {
        struct uwsgi_router_cache_conf *urcc = uwsgi_router_cachemath(ur, args);
        if (!urcc) return -1;
        urcc->default_num = 1;
        urcc->flags |= UWSGI_CACHE_FLAG_DEC;
        return 0;
}

static int uwsgi_router_cachemul(struct uwsgi_route *ur, char *args) {
        struct uwsgi_router_cache_conf *urcc = uwsgi_router_cachemath(ur, args);
        if (!urcc) return -1;
        urcc->default_num = 2;
        urcc->flags |= UWSGI_CACHE_FLAG_MUL;
        return 0;
}

static int uwsgi_router_cachediv(struct uwsgi_route *ur, char *args) {
        struct uwsgi_router_cache_conf *urcc = uwsgi_router_cachemath(ur, args);
        if (!urcc) return -1;
        urcc->default_num = 2;
        urcc->flags |= UWSGI_CACHE_FLAG_DIV;
        return 0;
}

static int uwsgi_router_cacheset(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_cacheset;
        ur->data = args;
        ur->data_len = strlen(args);
        struct uwsgi_router_cache_conf *urcc = uwsgi_calloc(sizeof(struct uwsgi_router_cache_conf));
        if (uwsgi_kvlist_parse(ur->data, ur->data_len, ',', '=',
                        "key", &urcc->key,
                        "value", &urcc->value,
                        "name", &urcc->name,
                        "expires", &urcc->expires_str, NULL)) {
                        uwsgi_log("invalid cacheset route syntax: %s\n", args);
                        goto error;
                }

                if (urcc->key) {
                        urcc->key_len = strlen(urcc->key);
                }

                if (urcc->value) {
                        urcc->value_len = strlen(urcc->value);
                }

                if (urcc->name) {
                        urcc->name_len = strlen(urcc->name);
                }

                if (!urcc->key || !urcc->value) {
                        uwsgi_log("invalid cacheset route syntax: you need to specify a cache key and a value\n");
                        goto error;
                }

                if (urcc->expires_str) {
                        urcc->expires = strtoul(urcc->expires_str, NULL, 10);
                }

        ur->data2 = urcc;
        return 0;
error:
        if (urcc->key) free(urcc->key);
        if (urcc->name) free(urcc->name);
        if (urcc->value) free(urcc->value);
        if (urcc->expires_str) free(urcc->expires_str);
        free(urcc);
        return -1;
}


static int uwsgi_router_cachevar(struct uwsgi_route *ur, char *args) {
        ur->func = uwsgi_routing_func_cachevar;
        ur->data = args;
        ur->data_len = strlen(args);
        struct uwsgi_router_cache_conf *urcc = uwsgi_calloc(sizeof(struct uwsgi_router_cache_conf));
                if (uwsgi_kvlist_parse(ur->data, ur->data_len, ',', '=',
                        "key", &urcc->key,
                        "var", &urcc->var,
                        "name", &urcc->name,
                        "num", &urcc->as_num,
                        "as_num", &urcc->as_num,
                        NULL)) {
                        uwsgi_log("invalid route syntax: %s\n", args);
                        exit(1);
                }

                if (urcc->key) {
                        urcc->key_len = strlen(urcc->key);
                }

                if (urcc->var) {
                        urcc->var_len = strlen(urcc->var);
                }

                if (!urcc->key || !urcc->var) {
                        uwsgi_log("invalid route syntax: you need to specify a cache key and a request var\n");
                        exit(1);
                }

                ur->data2 = urcc;
        return 0;
}

static int uwsgi_route_condition_incache(struct wsgi_request *wsgi_req, struct uwsgi_route *ur) {
	int ret = 0;
	char *key = NULL;
	size_t key_len = 0;
	char *name = NULL;

	if (uwsgi_kvlist_parse(ur->subject_str, ur->subject_str_len, ',', '=',
        	"key", &key,
                "name", &name,
                NULL)) {
		return 0;
	}

	if (!key) goto end;

	key_len = strlen(key);
	struct uwsgi_buffer *ub = uwsgi_routing_translate(wsgi_req, ur, NULL, 0, key, key_len);
        if (!ub) goto end;

        ret = uwsgi_cache_magic_exists(ub->buf, ub->pos, name);
	uwsgi_buffer_destroy(ub);

end:
	if (key) free(key);
	if (name) free(name);
	return ret;
}

static void router_cache_register() {
	uwsgi_register_router("cache", uwsgi_router_cache);
	uwsgi_register_router("cache-continue", uwsgi_router_cache_continue);
	uwsgi_register_router("cachevar", uwsgi_router_cachevar);
	uwsgi_register_router("cacheset", uwsgi_router_cacheset);
	uwsgi_register_router("cachestore", uwsgi_router_cache_store);
	uwsgi_register_router("cache-store", uwsgi_router_cache_store);
	uwsgi_register_route_condition("incache", uwsgi_route_condition_incache);

	uwsgi_register_router("cacheinc", uwsgi_router_cacheinc);
	uwsgi_register_router("cachedec", uwsgi_router_cachedec);
	uwsgi_register_router("cachemul", uwsgi_router_cachemul);
	uwsgi_register_router("cachediv", uwsgi_router_cachediv);
}

struct uwsgi_plugin router_cache_plugin = {
	.name = "router_cache",
	.on_load = router_cache_register,
};

#else
struct uwsgi_plugin router_cache_plugin = {
	.name = "router_cache",
};
#endif
