-- pgproc module
--
-- © 2010, 2013 David J Goehrig <dave@dloh.org>
--
-- Copyright (c) 2010, 2013, David J Goehrig <dave@dloh.org>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
-- following conditions are met:
--
-- Redistributions of source code must retain the above copyright notice, this list of conditions and the following
-- disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
-- the following disclaimer in the documentation and/or other materials provided with the distribution.
--
-- Neither the name of the project nor the names of its contributors may be used to endorse or promote products derived
-- from this software without specific prior written permission.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
-- CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
local pg = {}

local ffi = require "mffi"
local util = require "util"
local l = require"lang".l
local sql
if ffi.os == "Windows" then
	--[[ -- older libpq binaris
	if ffi.arch == "x64" then
		util.loadDll("graphics/libintl-8.dll")
	else
		util.loadDll("libintl.dll")
	end
	util.loadDll("libeay32.dll")
	util.loadDll("ssleay32.dll")
	sql = util.loadDll("libpq.dll")
	--]]
	util.loadDll("graphics/libwinpthread-1.dll")
	util.loadDll("graphics/libgcc_s_dw2-1.dll")
	util.loadDll("graphics/libiconv-2.dll")
	util.loadDll("graphics/libintl-8.dll")
	util.loadDll("vcruntime140.dll")
	util.loadDll("libcrypto-1_1.dll")
	util.loadDll("libssl-1_1.dll")
	util.loadDll("msvcr120.dll") -- does not work: ffi.loadMsvcr("120")
	sql = util.loadDll("libpq.dll")
else -- if ffi.os == "Linux" then
	sql = util.loadDll("pq")
	if sql == nil and ffi.os == "Linux" then
		util.printWarning("use 'apt-get install libpq5' to install Linux libpq dependencies")
	end
end

pg.ffi = ffi
pg.sql = sql
pg.connection = nil

ffi.cdef([[
typedef enum { CONNECTION_OK, CONNECTION_BAD, CONNECTION_STARTED, CONNECTION_MADE, CONNECTION_AWAITING_RESPONSE, CONNECTION_AUTH_OK, CONNECTION_SETENV, CONNECTION_SSL_STARTUP, CONNECTION_NEEDED } ConnStatusType;

typedef enum { PGRES_EMPTY_QUERY = 0, PGRES_COMMAND_OK, PGRES_TUPLES_OK, PGRES_COPY_OUT, PGRES_COPY_IN, PGRES_BAD_RESPONSE, PGRES_NONFATAL_ERROR, PGRES_COPY_BOTH } ExecStatusType;

typedef struct pg_conn PGconn;
typedef struct pg_result PGresult;
typedef struct pg_cancel PGcancel;
typedef char pqbool;

typedef struct pgNotify { char *relname; int be_pid; char *extra; struct pgNotify *next; } PGnotify;

typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);
typedef void (*PQnoticeProcessor) (void *arg, const char *message);

extern PGconn *PQconnectdb(const char *conninfo);
extern void PQfinish(PGconn *conn);
extern void PQclear(PGresult *res);
extern void PQfreemem(void *ptr);
extern ExecStatusType PQresultStatus(const PGresult *res);
extern int 	 PQgetisnull(const PGresult *res, int tup_num, int field_num);
extern char *PQgetvalue(const PGresult *res, int tup_num, int field_num);
extern char *PQerrorMessage(const PGconn *conn);
extern char *PQresultErrorMessage(const PGresult *res);
extern PGresult *PQexec(PGconn *conn, const char *query);
extern ExecStatusType PQresultStatus(const PGresult *res);
extern int PQntuples(const PGresult *res);
extern int PQnfields(const PGresult *res);
extern char *PQfname(const PGresult *res, int field_num);
extern size_t PQescapeStringConn(PGconn *conn,char *to, const char *from, size_t length, int *error);
extern ConnStatusType PQstatus(const PGconn *conn);

]])

function pg.reset()
	if not pg.result then
		-- util.printError(l("pg.result should exist on reset"))
		return
	end
	sql.PQclear(pg.result)
	pg.result = nil
end

function pg.resultError()
	return ffi.string(sql.PQresultErrorMessage(pg.result))
end

function pg.Error(conn)
	local err = ffi.string(sql.PQerrorMessage(conn))
	err = err:sub(1, -2) -- remove last \n
	return err
end

function pg.connect(connstr)
	pg.connstr = connstr -- or os.getenv('DB_CONNECT_STRING')
	pg.connection = sql.PQconnectdb(pg.connstr)
	if sql.PQstatus(pg.connection) ~= sql.CONNECTION_OK then
		local err = pg.Error(pg.connection)
		pg.connection = nil
		return nil, err
	end
	local rows, err = pg.query("SELECT 1")
	if err then
		pg.connection = nil
		return nil, err
	end
	pg.reset()
	return pg.connection
end

function pg.query(Q, conn)
	if conn and pg.connection ~= conn then
		pg.connection = conn
	end
	if not pg.connection then
		pg.connect()
	end
	if pg.result then
		util.printError(l("pg.result should not exist any more"))
		pg.reset()
	end
	pg.result = sql.PQexec(pg.connection, Q)
	-- async: http://www.postgresql.org/docs/9.1/static/libpq-async.html
	pg.status = sql.PQresultStatus(pg.result)
	if pg.status == sql.PGRES_EMPTY_QUERY or pg.status == sql.PGRES_COMMAND_OK then
		pg.reset()
		return 0
	end
	if pg.status == sql.PGRES_TUPLES_OK then
		return sql.PQntuples(pg.result)
	end
	local err = pg.resultError()
	pg.reset()
	return nil, err
end

function pg.rows()
	if pg.status == sql.PGRES_TUPLES_OK then
		return sql.PQntuples(pg.result)
	end
	return -1
end

function pg.fields()
	if not pg.result then
		return 0
	end
	return sql.PQnfields(pg.result)
end

function pg.field(I)
	if not pg.result then
		return nil
	end
	return ffi.string(sql.PQfname(pg.result, I - 1))
end

function pg.fetch(row, column)
	if not pg.result then
		return nil
	end
	--[[
	if sql.PQgetisnull(pg.result, row-1, column-1) == 1 then
	  return "null"
	end
	]]
	return ffi.string(sql.PQgetvalue(pg.result, row - 1, column - 1))
end

function pg.close(conn)
	if conn then
		pg.connection = conn
	end
	sql.PQfinish(pg.connection)
	pg.connection = nil
	pg.reset()
end

function pg.quote(S)
	if not pg.connection then
		return nil
	end
	local err = ffi.newNoAnchor('int[1]')
	local buffer = ffi.newNoAnchor('char[?]', 2 * #S + 1) -- +1 for \0
	local len_c = sql.PQescapeStringConn(pg.connection, buffer, S, #S, err)
	if err[0] then
		print("Failed to escape " .. S)
		return nil
	end
	return ffi.string(buffer, len_c)
end

function pg.bind(schema)
	local query = "select proc.proname::text from pg_proc proc join pg_namespace namesp on proc.pronamespace = namesp.oid where namesp.nspname = '" .. schema .. "'"
	_G[schema] = {}
	local rows = pg.query(query)
	if rows < 0 then
		print(pg.resultError())
		return -1
	end
	local i = 0
	while i < rows do
		local proc = pg.fetch(i, 0);
		local F = function()
			local query = "select * from  " .. schema .. "." .. proc .. "('"
			return function(...)
				local Q = query .. table.concat({...}, "','") .. "')"
				local rows = pg.query(Q)
				local R = {}
				local k, j = 0, 0
				while k < rows do
					while j < pg.fields() do
						local key = pg.field(j)
						local value = pg.fetch(k, j)
						R[key] = value
						j = j + 1
					end
					k = k + 1
				end
				return R
			end
		end
		_G[schema][proc] = F()
		i = i + 1
	end
end

return pg
