/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


#include "condor_common.h"
#include "proc.h"
#include "stl_string_utils.h"
#include "condor_debug.h"
#include <charconv> // for std::from_chars

PROC_ID
getProcByString( const char* str )
{
	PROC_ID rval;
	if ( ! StrIsProcId(str, rval.cluster, rval.proc, NULL)) {
		rval.cluster = rval.proc = -1;
	}
	return rval;
}

void
ProcIdToStr( const PROC_ID a, char *buf )
{
	ProcIdToStr( a.cluster, a.proc, buf );
}

void
ProcIdToStr( int cluster, int proc, char *buf ) {
	if ( proc == -1 ) {
		// cluster ad key
		snprintf(buf,PROC_ID_STR_BUFLEN,"0%d.-1",cluster);
	} else {
		// proc ad key
		snprintf(buf,PROC_ID_STR_BUFLEN,"%d.%d",cluster,proc);
	}
}

// parse a string of the form X.Y as a PROC_ID.
// return true if the input string was a valid proc id and it ended with \0 or comma or whitespace.
// a pointer to the first unparsed character is optionally returned.
// input may be X  or X.  or X.Y  if no Y is specified then proc will be set to -1
bool StrIsProcId(const char *str, int &cluster, int &proc, const char ** pend)
{
	bool valid = false;
	char * pe = const_cast<char*>(str);

	const char * p = str;
	cluster = strtol(p, &pe, 10);
	if ((pe > p) && (*pe == 0 || isspace(*pe) || *pe == ',')) {
		// if ate at least one character and ended on , space or \0, then the procid is a simple number
		// it's valid as long as it's positive.
		proc = -1;
		valid = cluster >= 0;
	} else if (*pe == '.') {
		// if we end on a . then cluster must be followed by a proc
		p = ++pe;
		proc = -1;
		if (*p == 0 || isspace(*p) || *pe == ',') {
			// ok, if the cluster is followed by a dot and nothing more
			// this parsed the same as if the dot were not there.
			valid = cluster >= 0;
		} else {
			// if we get to here, we must have a valid proc
			// but the proc is allowed to be negative - it just can't have any spaces in it.
			bool negative = false;
			if (*p == '-') { negative = true; ++p; }
			if (*p < '0' || *p > '9') {
				// if the next character isn't a digit. then this is NOT a valid procid.
				valid = false;
			} else {
				proc = strtol(p, &pe, 10);
				valid = (pe > p && (*pe == 0 || isspace(*pe)));
				if (negative) proc = -proc;
			}
		}
	}

	if (pend) *pend = pe;
	return valid;
}

// parse a string of the form X.Y as a PROC_ID.
// return true if the input string was a valid proc id and it ended with \0 or comma or whitespace.
// a pointer to the first unparsed character is optionally returned.
// input may be X  or X.  or X.Y  if no Y is specified then proc will be set to -1
bool StrIsProcId(std::string_view str, int &cluster, int &proc, const char ** pend)
{
	if (str.empty()) return false;

	const char * p = str.data();
	const char * endp = p + str.size();
	while (p < endp && isspace(*p)) ++p;

	bool valid = false;
	auto res = std::from_chars(p, endp, cluster);
	if (res.ec == std::errc{} && res.ptr > p) { // if success and read at least 1 character
		p = res.ptr;
		if (p >= endp || *p == 0 || isspace(*p) || *p == ',') {
			// if ate at least one character and ended on , space or \0, then this is a bare clusterid
			// it's valid as long as it's positive.
			proc = -1;
			valid = cluster >= 0; // TODO: allow negative cluster id's here??
		} else if (*p == '.') {
			// if we end on a . then cluster must be followed by a proc
			p = ++res.ptr;
			proc = -1;
			if (p >= endp || *p == 0 || isspace(*p) || *p == ',') {
				// ok, if the cluster is followed by a dot and nothing more
				// this parsed the same as if the dot were not there.
				valid = cluster >= 0; // TODO: allow negative cluster id's here??
			} else {
				// if we get to here, we must have a valid proc, procs are allowed to be negative
				res = std::from_chars(p, endp, proc);
				valid = (res.ec == std::errc{} && (res.ptr == endp || *res.ptr == 0 || isspace(*res.ptr) || *res.ptr == ','));
			}
		}
	}
	if (pend) *pend = res.ptr;
	return valid;
}


void JOB_ID_KEY::sprint(std::string & s) const
{
	formatstr(s,"%d.%d", this->cluster, this->proc);
}

JOB_ID_KEY::operator std::string() const
{
	std::string s;
	if ( proc == -1 ) {
		// cluster ad key
		formatstr(s, "0%d.-1", this->cluster);
	} else {
		// proc ad key
		formatstr(s, "%d.%d", this->cluster, this->proc);
	}
	return s;
}

#ifdef _MSC_VER
#define rotl32(x,y) _rotl(x,y)
#define rotl64(x,y) _rotl64(x,y)
#else
// gcc will optimize this code into a single rot instruction.
inline uint32_t rotl32 ( uint32_t x, int8_t r ) { return (x << r) | (x >> (32 - r)); }
inline uint64_t rotl64 ( uint64_t x, int8_t r ) { return (x << r) | (x >> (64 - r)); }
#endif

// This is extrapolated from the 32bit Murmur3 hash from
// at http://code.google.com/p/smhasher/wiki/MurmurHash3
// 
unsigned int inline MurmurHash32x2(unsigned int u1, unsigned int u2) {
	const unsigned int c1 = 0xcc9e2d51;
	const unsigned int c2 = 0x1b873593;

	// we interleave operations on h1 and h2 in the hope that they will parallelize
	unsigned int h1 = u1 * c1;
	unsigned int h2 = u2 * c1;
	h1 = rotl32(h1,15);
	h2 = rotl32(h2,15);
	h1 *= c2;
	h2 *= c2;

	// mix
	h1 = rotl32(h1,13);
	h1 = h1*5+0xe6546b64;
	h1 ^= h2;

	// finalize to force all bits to avalanche
	h1 ^= h1 >> 16;
	h1 *= 0x85ebca6b;
	h1 ^= h1 >> 13;
	h1 *= 0xc2b2ae35;
	h1 ^= h1 >> 16;
	return h1;
}

unsigned int inline MurmurHash32x2_nofinal(unsigned int u1, unsigned int u2) {
	const unsigned int c1 = 0xcc9e2d51;
	const unsigned int c2 = 0x1b873593;

	// we interleave operations on h1 and h2 in the hope that they will parallelize
	unsigned int h1 = u1 * c1;
	unsigned int h2 = u2 * c1;
	h1 = rotl32(h1,15);
	h2 = rotl32(h2,15);
	h1 *= c2;
	h2 *= c2;

	// mix
	h1 = rotl32(h1,13);
	h1 = h1*5+0xe6546b64;
	h1 ^= h2;

	// finalize to force all bits to avalanche
	//h1 ^= h1 >> 16;
	//h1 *= 0x85ebca6b;
	//h1 ^= h1 >> 13;
	//h1 *= 0xc2b2ae35;
	//h1 ^= h1 >> 16;
	return h1;
}

#define JOB_HASH_ALGOR 2
int job_hash_algorithm = JOB_HASH_ALGOR;

#if JOB_HASH_ALGOR == 0
inline size_t hashkey_compat_hash(const char * p)
{
	size_t hash = 0;

	while (*p) {
		hash = (hash<<5) + hash + (unsigned char)*p++;
	}

	return hash;
}
#endif

size_t JOB_ID_KEY::hash(const JOB_ID_KEY &key) noexcept
{
#if JOB_HASH_ALGOR == 0
	char buf[PROC_ID_STR_BUFLEN];
	ProcIdToStr(key.cluster, key.proc, buf);
	return hashkey_compat_hash(buf);
#elif JOB_HASH_ALGOR == 1
	return key.cluster + key.proc*19;
#elif JOB_HASH_ALGOR == 2
	return key.cluster*1013 + key.proc;
#elif JOB_HASH_ALGOR == 3
	return MurmurHash32x2(key.cluster, key.proc);
#elif JOB_HASH_ALGOR == 4
	return MurmurHash32x2_nofinal(key.cluster, key.proc);
#endif
}


size_t hashFunction(const JOB_ID_KEY &key)
{
#if JOB_HASH_ALGOR == 0
	char buf[PROC_ID_STR_BUFLEN];
	ProcIdToStr(key.cluster, key.proc, buf);
	return hashkey_compat_hash(buf);
#elif JOB_HASH_ALGOR == 1
	return key.cluster + key.proc*19;
#elif JOB_HASH_ALGOR == 2
	return key.cluster*1013 + key.proc;
#elif JOB_HASH_ALGOR == 3
	return MurmurHash32x2(key.cluster, key.proc);
#elif JOB_HASH_ALGOR == 4
	return MurmurHash32x2_nofinal(key.cluster, key.proc);
#endif
}

