############################################################################
#                                                                          #
# Copyright (c) 2019-2020 Carl Drougge                                     #
#                                                                          #
# 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.                                           #
#                                                                          #
############################################################################

from __future__ import print_function
from __future__ import division
from __future__ import unicode_literals

import sys
import hashlib
from importlib import import_module
from collections import namedtuple

from accelerator.compat import unicode, str_types


_prologue_code_template = r'''
/* This is autogenerated from %s.py */

#define PY_SSIZE_T_CLEAN 1
#include <Python.h>
#include <stdint.h>

static PyObject *nullmarker = 0;

static int str_or_0(PyObject *obj, const char **res) {
	if (obj == nullmarker) {
		*res = 0;
		return 0;
	}
	*res = PyBytes_AsString(obj);
	return !*res;
}

static PyObject *py_set_null(PyObject *dummy, PyObject *obj)
{
	Py_CLEAR(nullmarker);
	Py_INCREF(obj);
	nullmarker = obj;
	Py_RETURN_NONE;
}
'''

_init_code_template = r'''
static PyMethodDef module_methods[] = {
	{"set_null", py_set_null, METH_O, 0},
	%(methods)s,
	{NULL, NULL, 0, NULL}
};

#if PY_MAJOR_VERSION < 3
#  define INITERR
#else
#  define INITERR 0
static struct PyModuleDef moduledef = {
	PyModuleDef_HEAD_INIT,
	"_%(name)s",        /*m_name*/
	0,                  /*m_doc*/
	-1,                 /*m_size*/
	module_methods,     /*m_methods*/
	0,                  /*m_reload*/
	0,                  /*m_traverse*/
	0,                  /*m_clear*/
	0,                  /*m_free*/
};
#endif

#if PY_MAJOR_VERSION >= 3
__attribute__ ((visibility("default"))) PyMODINIT_FUNC PyInit__%(name)s(void)
{
	PyObject *m = PyModule_Create(&moduledef);
	if (!m) return 0;
	PyModule_AddObject(m, "source_hash", PyUnicode_FromString(source_hash));
	return m;
}
#else
__attribute__ ((visibility("default"))) PyMODINIT_FUNC init_%(name)s(void)
{
	PyObject *m = Py_InitModule3("_%(name)s", module_methods, NULL);
	if (!m) return;
	PyModule_AddObject(m, "source_hash", PyUnicode_FromString(source_hash));
}
#endif
'''

_method_def_template = r'''{"%s", py_%s, METH_VARARGS, 0}'''

CStuff = namedtuple('CStuff', 'backend NULL mk_uint64 bytesargs')

def make_source(name, functions, protos, extra_functions, extra_method_defs, wrapper_template):
	code = []
	code.append(_prologue_code_template % (name,))
	code.append(functions)
	code.append(extra_functions)
	method_defs = []
	for p in protos:
		funcname = p[11:].split('(')[0]
		code.append(wrapper_template % (funcname, funcname,))
		method_defs.append(_method_def_template % (funcname, funcname,))
	method_defs.extend(extra_method_defs)
	code.append(_init_code_template % dict(methods=',\n\t'.join(method_defs), name=name,))
	hash = hashlib.sha1(b''.join(c.encode('ascii') for c in code)).hexdigest()
	code.insert(-1, 'static char source_hash[] = "%s";\n' % (hash,))
	code = ''.join(code)
	return code, hash

def init(name, hash, protos, extra_protos, functions):
	backend = import_module('accelerator.standard_methods._' + name)
	if hash == backend.source_hash:
		NULL = object()
		backend.set_null(NULL)
		def mk_uint64(count=1):
			return [0] * count
		def str2c(s):
			if isinstance(s, unicode):
				s = s.encode('utf-8')
			return s
	else:
		print('[%s] Backend modified since module was compiled, falling back to cffi for development.' % (name,), file=sys.stderr)
		import cffi
		ffi = cffi.FFI()

		NULL = ffi.NULL
		def mk_uint64(count=1):
			return ffi.new('uint64_t []', [0] * count)
		def str2c(s):
			if isinstance(s, unicode):
				s = s.encode('utf-8')
			return ffi.new('char []', s)

		# cffi apparently doesn't know about off_t.
		# "typedef int... off_t" isn't allowed with verify,
		# so I guess we'll just assume that ssize_t is the same as off_t.
		ffi.cdef('typedef ssize_t off_t;')
		ffi.cdef(''.join(protos + extra_protos))
		backend = ffi.verify(functions, libraries=['z'], extra_compile_args=['-std=c99', '-DCFFI_ATE_MY_GIL'])

	# make any unicode args bytes, for C calls.
	def bytesargs(*a):
		return [
			str2c(v) if isinstance(v, str_types)
			else bytesargs(*v) if isinstance(v, list) and v and isinstance(v[0], str_types)
			else NULL if v is None
			else v
			for v in a
		]

	return CStuff(backend, NULL, mk_uint64, bytesargs)
