%%%-------------------------------------------------------------------
%%% File    : p1_mysql.erl
%%% Author  : Magnus Ahltorp <ahltorp@nada.kth.se>
%%% Descrip.: MySQL client.
%%%
%%% Created :  4 Aug 2005 by Magnus Ahltorp <ahltorp@nada.kth.se>
%%%
%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
%%% See the file COPYING
%%%
%%% Usage:
%%%
%%%
%%% Call one of the start-functions before any call to fetch/2
%%%
%%%   start_link(Id, Host, User, Password, Database)
%%%   start_link(Id, Host, Port, User, Password, Database)
%%%   start_link(Id, Host, User, Password, Database, LogFun)
%%%   start_link(Id, Host, Port, User, Password, Database, LogFun)
%%%
%%% Id is a connection group identifier. If you want to have more
%%% than one connection to a server (or a set of MySQL replicas),
%%% add more with
%%%
%%%   connect(Id, Host, Port, User, Password, Database, Reconnect)
%%%
%%% use 'undefined' as Port to get default MySQL port number (3306).
%%% MySQL querys will be sent in a per-Id round-robin fashion.
%%% Set Reconnect to 'true' if you want the dispatcher to try and
%%% open a new connection, should this one die.
%%%
%%% When you have a p1_mysql_dispatcher running, this is how you make a
%%% query :
%%%
%%%   fetch(Id, "select * from hello") -> Result
%%%     Result = {data, MySQLRes} | {updated, MySQLRes} |
%%%              {error, MySQLRes}
%%%
%%% Actual data can be extracted from MySQLRes by calling the following API
%%% functions:
%%%     - on data received:
%%%          FieldInfo = p1_mysql:get_result_field_info(MysqlRes)
%%%          AllRows   = p1_mysql:get_result_rows(MysqlRes)
%%%         with FieldInfo = list() of {Table, Field, Length, Name}
%%%          and AllRows   = list() of list() representing records
%%%     - on update:
%%%          Affected  = p1_mysql:get_result_affected_rows(MysqlRes)
%%%         with Affected  = integer()
%%%     - on error:
%%%          Reason    = p1_mysql:get_result_reason(MysqlRes)
%%%         with Reason    = string()
%%%
%%% If you just want a single MySQL connection, or want to manage your
%%% connections yourself, you can use the p1_mysql_conn module as a
%%% stand-alone single MySQL connection. See the comment at the top of
%%% p1_mysql_conn.erl.
%%%
%%%-------------------------------------------------------------------
-module(p1_mysql).

-behaviour(gen_server).

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([start_link/5,
	 start_link/6,
	 start_link/7,

	 fetch/2,
	 fetch/3,

	 get_result_field_info/1,
	 get_result_rows/1,
	 get_result_affected_rows/1,
	 get_result_reason/1,

	 quote/1,
	 asciz_binary/2,

	 connect/7,
	 connect/8,
	 stop/0,

     gc_each/1
	]).

%%--------------------------------------------------------------------
%% Internal exports - just for p1_mysql_* modules
%%--------------------------------------------------------------------
-export([log/3,
	 log/4
	]).

%%--------------------------------------------------------------------
%% Internal exports - gen_server callbacks
%%--------------------------------------------------------------------
-export([init/1,
	 handle_call/3,
	 handle_cast/2,
	 handle_info/2,
	 terminate/2,
	 code_change/3
	]).

%%--------------------------------------------------------------------
%% Records
%%--------------------------------------------------------------------
-include("p1_mysql.hrl").
-record(state, {
	  conn_list,	%% list() of p1_mysql_connection record()
	  log_fun,	%% undefined | function for logging,
      gc_tref   %% undefined | timer:TRef
	 }).

-record(p1_mysql_connection, {
	  id,		%% term(), user of 'p1_mysql' modules id of this socket group
	  conn_pid,	%% pid(), p1_mysql_conn process
	  reconnect,	%% true | false, should p1_mysql_dispatcher try to reconnect if this connection dies?
	  host,		%% undefined | string()
	  port,		%% undefined | integer()
	  user,		%% undefined | string()
	  password,	%% undefined | string()
	  database,	%% undefined | string(),
          ssl_opts = []	%% list()
	 }).

%%--------------------------------------------------------------------
%% Macros
%%--------------------------------------------------------------------
-define(SERVER, p1_mysql_dispatcher).
-define(CONNECT_TIMEOUT, 5000).
-define(LOCAL_FILES, 128).

-define(PORT, 3306).


%%====================================================================
%% External functions
%%====================================================================

%%--------------------------------------------------------------------
%% Function: start_link(Id, Host, User, Password, Database)
%%           start_link(Id, Host, Port, User, Password, Database)
%%           start_link(Id, Host, User, Password, Database, LogFun)
%%           start_link(Id, Host, Port, User, Password, Database,
%%                      LogFun)
%%           Id       = term(), first connection-group Id
%%           Host     = string()
%%           Port     = integer()
%%           User     = string()
%%           Password = string()
%%           Database = string()
%%           LogFun   = undefined | function() of arity 3
%% Descrip.: Starts the MySQL client gen_server process.
%% Returns : {ok, Pid} | ignore | {error, Error}
%%--------------------------------------------------------------------
start_link(Id, Host, User, Password, Database) when is_list(Host), is_list(User), is_list(Password),
						    is_list(Database) ->
    start_link(Id, Host, ?PORT, User, Password, Database, undefined).

start_link(Id, Host, Port, User, Password, Database) when is_list(Host), is_integer(Port), is_list(User),
							  is_list(Password), is_list(Database) ->
    start_link(Id, Host, Port, User, Password, Database, undefined);

start_link(Id, Host, User, Password, Database, LogFun) when is_list(Host), is_list(User), is_list(Password),
							    is_list(Database) ->
    start_link(Id, Host, ?PORT, User, Password, Database, LogFun).

start_link(Id, Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User),
								  is_list(Password), is_list(Database) ->
    crypto:start(),
    gen_server:start_link({local, ?SERVER}, ?MODULE, [Id, Host, Port, User, Password, Database, LogFun], []).

stop() ->
    gen_server:call(?SERVER, stop).

gc_each(Millisec) ->
    gen_server:call(?SERVER, {gc_each, Millisec}).

%%--------------------------------------------------------------------
%% Function: fetch(Id, Query)
%%           fetch(Id, Query, Timeout)
%%           Id      = term(), connection-group Id
%%           Query   = string(), MySQL query in verbatim
%%           Timeout = integer() | infinity, gen_server timeout value
%% Descrip.: Send a query and wait for the result.
%% Returns : {data, MySQLRes}    |
%%           {updated, MySQLRes} |
%%           {error, MySQLRes}
%%           MySQLRes = term()
%%--------------------------------------------------------------------
fetch(Id, Query) when is_list(Query) ->
    gen_server:call(?SERVER, {fetch, Id, Query}).
fetch(Id, Query, Timeout) when is_list(Query) ->
    gen_server:call(?SERVER, {fetch, Id, Query}, Timeout).

%%--------------------------------------------------------------------
%% Function: get_result_field_info(MySQLRes)
%%           MySQLRes = term(), result of fetch function on "data"
%% Descrip.: Extract the FieldInfo from MySQL Result on data received
%% Returns : FieldInfo
%%           FieldInfo = list() of {Table, Field, Length, Name}
%%--------------------------------------------------------------------
get_result_field_info(#p1_mysql_result{fieldinfo = FieldInfo}) ->
    FieldInfo.

%%--------------------------------------------------------------------
%% Function: get_result_rows(MySQLRes)
%%           MySQLRes = term(), result of fetch function on "data"
%% Descrip.: Extract the Rows from MySQL Result on data received
%% Returns : Rows
%%           Rows = list() of list() representing records
%%--------------------------------------------------------------------
get_result_rows(#p1_mysql_result{rows=AllRows}) ->
    AllRows.

%%--------------------------------------------------------------------
%% Function: get_result_affected_rows(MySQLRes)
%%           MySQLRes = term(), result of fetch function on "updated"
%% Descrip.: Extract the Rows from MySQL Result on update
%% Returns : AffectedRows
%%           AffectedRows = integer()
%%--------------------------------------------------------------------
get_result_affected_rows(#p1_mysql_result{affectedrows=AffectedRows}) ->
    AffectedRows.

%%--------------------------------------------------------------------
%% Function: get_result_reason(MySQLRes)
%%           MySQLRes = term(), result of fetch function on "error"
%% Descrip.: Extract the error Reason from MySQL Result on error
%% Returns : Reason
%%           Reason    = string()
%%--------------------------------------------------------------------
get_result_reason(#p1_mysql_result{error=Reason}) ->
    Reason.

%%--------------------------------------------------------------------
%% Function: quote(String)
%%           String = string()
%% Descrip.: Quote a string so that it can be included safely in a
%%           MySQL query.
%% Returns : Quoted = string()
%%--------------------------------------------------------------------
quote(String) when is_list(String) ->
    [34 | lists:reverse([34 | quote(String, [])])].	%% 34 is $"

quote([], Acc) ->
    Acc;
quote([0 | Rest], Acc) ->
    quote(Rest, [$0, $\\ | Acc]);
quote([10 | Rest], Acc) ->
    quote(Rest, [$n, $\\ | Acc]);
quote([13 | Rest], Acc) ->
    quote(Rest, [$r, $\\ | Acc]);
quote([$\\ | Rest], Acc) ->
    quote(Rest, [$\\ , $\\ | Acc]);
quote([39 | Rest], Acc) ->		%% 39 is $'
    quote(Rest, [39, $\\ | Acc]);	%% 39 is $'
quote([34 | Rest], Acc) ->		%% 34 is $"
    quote(Rest, [34, $\\ | Acc]);	%% 34 is $"
quote([26 | Rest], Acc) ->
    quote(Rest, [$Z, $\\ | Acc]);
quote([C | Rest], Acc) ->
    quote(Rest, [C | Acc]).

%%--------------------------------------------------------------------
%% Function: asciz_binary(Data, Acc)
%%           Data = binary()
%%           Acc  = list(), input accumulator
%% Descrip.: Find the first zero-byte in Data and add everything
%%           before it to Acc, as a string.
%% Returns : {NewList, Rest}
%%           NewList = list(), Acc plus what we extracted from Data
%%           Rest    = binary(), whatever was left of Data, not
%%                     including the zero-byte
%%--------------------------------------------------------------------
asciz_binary(<<>>, Acc) ->
    {lists:reverse(Acc), <<>>};
asciz_binary(<<0:8, Rest/binary>>, Acc) ->
    {lists:reverse(Acc), Rest};
asciz_binary(<<C:8, Rest/binary>>, Acc) ->
    asciz_binary(Rest, [C | Acc]).

%%--------------------------------------------------------------------
%% Function: connect(Id, Host, Port, User, Password, Database,
%%                   Reconnect)
%%           Id        = term(), connection-group Id
%%           Host      = string()
%%           Port      = undefined | integer()
%%           User      = string()
%%           Password  = string()
%%           Database  = string()
%%           Reconnect = true | false
%% Descrip.: Starts a MySQL connection and, if successfull, registers
%%           it with the p1_mysql_dispatcher.
%% Returns : {ok, ConnPid} | {error, Reason}
%%--------------------------------------------------------------------
connect(Id, Host, undefined, User, Password, Database, Reconnect) ->
    connect(Id, Host, ?PORT, User, Password, Database, Reconnect);
connect(Id, Host, Port, User, Password, Database, Reconnect) ->
    connect(Id, Host, Port, User, Password, Database, Reconnect, []).
connect(Id, Host, Port, User, Password, Database, Reconnect, SSLOpts) ->
    {ok, LogFun} = gen_server:call(?SERVER, get_logfun),
    case p1_mysql_conn:start(Host, Port, User, Password, Database, LogFun, SSLOpts) of
	{ok, ConnPid} ->
	    MysqlConn =
		case Reconnect of
		    true ->
			#p1_mysql_connection{id        = Id,
					  conn_pid  = ConnPid,
					  reconnect = true,
					  host      = Host,
					  port      = Port,
					  user      = User,
					  password  = Password,
					  database  = Database,
					  ssl_opts = SSLOpts
					 };
		    false ->
			#p1_mysql_connection{id        = Id,
					  conn_pid  = ConnPid,
					  reconnect = false
					 }
		end,
	    case gen_server:call(?SERVER, {add_mysql_connection, MysqlConn}) of
		ok ->
		    {ok, ConnPid};
		Res ->
		    Res
	    end;
	{error, Reason} ->
	    {error, Reason}
    end.

%%--------------------------------------------------------------------
%% Function: log(LogFun, Level, Format)
%%           log(LogFun, Level, Format, Arguments)
%%           LogFun    = undefined | function() with arity 3
%%           Level     = debug | normal | error
%%           Format    = string()
%%           Arguments = list() of term()
%% Descrip.: Either call the function LogFun with the Level, Format
%%           and Arguments as parameters or log it to the console if
%%           LogFun is undefined.
%% Returns : void()
%%
%% Note    : Exported only for use by the p1_mysql_* modules.
%%
%%--------------------------------------------------------------------
log(LogFun, Level, Format) ->
    log(LogFun, Level, Format, []).

log(LogFun, Level, Format, Arguments) when is_function(LogFun) ->
    LogFun(Level, Format, Arguments);
log(undefined, _Level, Format, Arguments) ->
    %% default is to log to console
    io:format(Format, Arguments),
    io:format("~n", []).


%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%%           Args = [Id, Host, Port, User, Password, Database, LogFun]
%%             Id       = term(), connection-group Id
%%             Host     = string()
%%             Port     = integer()
%%             User     = string()
%%             Password = string()
%%             Database = string()
%%             LogFun   = undefined | function() with arity 3
%% Descrip.: Initiates the gen_server (MySQL dispatcher).
%%--------------------------------------------------------------------
init([Id, Host, Port, User, Password, Database, LogFun]) ->
    case p1_mysql_conn:start(Host, Port, User, Password, Database, LogFun) of
	{ok, ConnPid} ->
	    MysqlConn = #p1_mysql_connection{id        = Id,
					  conn_pid  = ConnPid,
					  reconnect = true,
					  host	    = Host,
					  port      = Port,
					  user      = User,
					  password  = Password,
					  database  = Database,
					  ssl_opts = []
					 },
	    try add_mysql_conn(MysqlConn, []) of
		{ok, ConnList} ->
		    {ok, #state{log_fun    = LogFun,
				conn_list = ConnList,
				gc_tref = undefined
			       }}
	    catch _:_ ->
		    Msg = "p1_mysql: Failed adding first MySQL connection handler to my list, exiting",
		    log(LogFun, error, Msg),
		    {error, Msg}
	    end;
	{error, Reason} ->
	    log(LogFun, error, "p1_mysql: Failed starting first MySQL connection handler, exiting"),
	    {stop, {error, Reason}}
    end.

%%--------------------------------------------------------------------
%% Function: handle_call(Msg, From, State)
%% Descrip.: Handling call messages.
%% Returns : {reply, Reply, State}          |
%%           {reply, Reply, State, Timeout} |
%%           {noreply, State}               |
%%           {noreply, State, Timeout}      |
%%           {stop, Reason, Reply, State}   | (terminate/2 is called)
%%           {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------


%%--------------------------------------------------------------------
%% Function: handle_call({fetch, Id, Query}, From, State)
%%           Id    = term(), connection-group id
%%           Query = string(), MySQL query
%% Descrip.: Make a MySQL query. Use the first connection matching Id
%%           in our connection-list. Don't block the p1_mysql_dispatcher
%%           by returning {noreply, ...} here and let the p1_mysql_conn
%%           do gen_server:reply(...) when it has an answer.
%% Returns : {noreply, NewState}             |
%%           {reply, {error, Reason}, State}
%%           NewState = state record()
%%           Reason   = atom() | string()
%%--------------------------------------------------------------------
handle_call({fetch, Id, Query}, From, State) ->
    log(State#state.log_fun, debug, "p1_mysql: fetch ~p (id ~p)", [Query, Id]),
    case get_next_mysql_connection_for_id(Id, State#state.conn_list) of
	{ok, MysqlConn, RestOfConnList} when is_record(MysqlConn, p1_mysql_connection) ->
	    p1_mysql_conn:fetch(MysqlConn#p1_mysql_connection.conn_pid, Query, From),
	    %% move this mysql socket to the back of the list
	    NewConnList = RestOfConnList ++ [MysqlConn],
	    %% The ConnPid process does a gen_server:reply() when it has an answer
	    {noreply, State#state{conn_list = NewConnList}};
	nomatch ->
	    %% we have no active connection matching Id
	    {reply, {error, no_connection}, State}
    end;

%%--------------------------------------------------------------------
%% Function: handle_call({add_mysql_connection, Conn}, From, State)
%%           Conn = p1_mysql_connection record()
%% Descrip.: Add Conn to our list of connections.
%% Returns : {reply, Reply, NewState}
%%           Reply = ok | {error, Reason}
%%           NewState = state record()
%%           Reason   = string()
%%--------------------------------------------------------------------
handle_call({add_mysql_connection, Conn}, _From, State) when is_record(Conn, p1_mysql_connection) ->
    try add_mysql_conn(Conn, State#state.conn_list) of
	{ok, NewConnList} ->
	    {Id, ConnPid} = {Conn#p1_mysql_connection.id, Conn#p1_mysql_connection.conn_pid},
	    log(State#state.log_fun, normal, "p1_mysql: Added connection with id '~p' (pid ~p) to my list",
		[Id, ConnPid]),
	    {reply, ok, State#state{conn_list = NewConnList}}
    catch _:_ ->
	    {reply, {error, "failed adding MySQL connection to my list"}, State}
    end;

%%--------------------------------------------------------------------
%% Function: handle_call(get_logfun, From, State)
%% Descrip.: Fetch our logfun.
%% Returns : {reply, {ok, LogFun}, State}
%%           LogFun = undefined | function() with arity 3
%%--------------------------------------------------------------------
handle_call(get_logfun, _From, State) ->
    {reply, {ok, State#state.log_fun}, State};

handle_call(stop, _From, State) ->
    {stop, normal, State};

handle_call({gc_each, Millisec}, _From, State) ->
    case State#state.gc_tref of
        undefined -> ok;
        TRef ->
            timer:cancel(TRef)
    end,
    case timer:send_interval(Millisec, gc) of
        {ok, NewTRef} ->
            {reply, ok, State#state{gc_tref = NewTRef}};
        {error, Reason} ->
            {reply, {error, Reason}, State}
    end;

handle_call(Unknown, _From, State) ->
    log(State#state.log_fun, error, "p1_mysql: Received unknown gen_server call : ~p", [Unknown]),
    {reply, {error, "unknown gen_server call in p1_mysql client"}, State}.


%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State)
%% Descrip.: Handling cast messages
%% Returns : {noreply, State}          |
%%           {noreply, State, Timeout} |
%%           {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast(Unknown, State) ->
    log(State#state.log_fun, error, "p1_mysql: Received unknown gen_server cast : ~p", [Unknown]),
    {noreply, State}.


%%--------------------------------------------------------------------
%% Function: handle_info(Msg, State)
%% Descrip.: Handling all non call/cast messages
%% Returns : {noreply, State}          |
%%           {noreply, State, Timeout} |
%%           {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
%% Function: handle_info({'DOWN', ...}, State)
%% Descrip.: Handle a message that one of our monitored processes
%%           (p1_mysql_conn processes in our connection list) has exited.
%%           Remove the entry from our list.
%% Returns : {noreply, NewState}   |
%%           {stop, normal, State}
%%           NewState = state record()
%%
%% Note    : For now, we stop if our connection list becomes empty.
%%           We should try to reconnect for a while first, to not
%%           eventually stop the whole OTP application if the MySQL-
%%           server is shut down and the p1_mysql_dispatcher was super-
%%           vised by an OTP supervisor.
%%--------------------------------------------------------------------
handle_info({'DOWN', _MonitorRef, process, Pid, Info}, State) ->
    LogFun = State#state.log_fun,
    case remove_mysql_connection_using_pid(Pid, State#state.conn_list, []) of
	{ok, Conn, NewConnList} ->
	    LogLevel = case Info of
			   normal -> normal;
			   _ -> error
		       end,
	    log(LogFun, LogLevel, "p1_mysql: MySQL connection pid ~p exited : ~p", [Pid, Info]),
	    log(LogFun, normal, "p1_mysql: Removed MySQL connection with pid ~p from list",
		[Pid]),
	    case Conn#p1_mysql_connection.reconnect of
		true ->
		    start_reconnect(Conn, LogFun);
		false ->
		    ok
	    end,
	    {noreply, State#state{conn_list = NewConnList}};
	nomatch ->
	    log(LogFun, error, "p1_mysql: Received 'DOWN' signal from pid ~p not in my list", [Pid]),
	    {noreply, State}
    end;

handle_info(gc, #state{conn_list = Connections} = State) ->
    [erlang:garbage_collect(C#p1_mysql_connection.conn_pid) || C <- Connections],
    erlang:garbage_collect(self()),
    {noreply, State};


handle_info(Info, State) ->
    log(State#state.log_fun, error, "p1_mysql: Received unknown signal : ~p", [Info]),
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State)
%% Descrip.: Shutdown the server
%% Returns : Reason
%%--------------------------------------------------------------------
terminate(Reason, State) ->
    LogFun = State#state.log_fun,
    LogLevel = case Reason of
		   normal -> debug;
		   _ -> error
	       end,
    log(LogFun, LogLevel, "p1_mysql: Terminating with reason : ~p", [Reason]),
    lists:foreach(fun(MysqlConn) ->
			  MysqlConn#p1_mysql_connection.conn_pid ! close
		  end, State#state.conn_list),
    Reason.

%%--------------------------------------------------------------------
%% Function: code_change(_OldVsn, State, _Extra)
%% Descrip.: Convert process state when code is changed
%% Returns : {ok, State}
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%====================================================================
%% Internal functions
%%====================================================================

%%--------------------------------------------------------------------
%% Function: add_mysql_conn(Conn, ConnList)
%%           Conn     = p1_mysql_connection record()
%%           ConnList = list() of p1_mysql_connection record()
%% Descrip.: Set up process monitoring of the p1_mysql_conn process and
%%           then add it (first) to ConnList.
%% Returns : NewConnList = list() of p1_mysql_connection record()
%%--------------------------------------------------------------------
add_mysql_conn(Conn, ConnList) when is_record(Conn, p1_mysql_connection), is_list(ConnList) ->
    erlang:monitor(process, Conn#p1_mysql_connection.conn_pid),
    {ok, [Conn | ConnList]}.

%%--------------------------------------------------------------------
%% Function: remove_mysql_connection_using_pid(Pid, ConnList)
%%           Pid      = pid()
%%           ConnList = list() of p1_mysql_connection record()
%% Descrip.: Removes the first p1_mysql_connection in ConnList that has
%%           a pid matching Pid.
%% Returns : {ok, Conn, NewConnList} | nomatch
%%           Conn        = p1_mysql_connection record()
%%           NewConnList = list() of p1_mysql_connection record()
%%--------------------------------------------------------------------
remove_mysql_connection_using_pid(Pid, [#p1_mysql_connection{conn_pid = Pid} = H | T], Res) ->
    {ok, H, lists:reverse(Res) ++ T};
remove_mysql_connection_using_pid(Pid, [H | T], Res) when is_record(H, p1_mysql_connection) ->
    remove_mysql_connection_using_pid(Pid, T, [H | Res]);
remove_mysql_connection_using_pid(_Pid, [], _Res) ->
    nomatch.

%%--------------------------------------------------------------------
%% Function: get_next_mysql_connection_for_id(Id, ConnList)
%%           Id       = term(), connection-group id
%%           ConnList = list() of p1_mysql_connection record()
%% Descrip.: Find the first p1_mysql_connection in ConnList that has an
%%           id matching Id.
%% Returns : {ok, Conn, NewConnList} | nomatch
%%           Conn        = p1_mysql_connection record()
%%           NewConnList = list() of p1_mysql_connection record(), same
%%                         as ConnList but without Conn
%%--------------------------------------------------------------------
get_next_mysql_connection_for_id(Id, ConnList) ->
    get_next_mysql_connection_for_id(Id, ConnList, []).

get_next_mysql_connection_for_id(Id, [#p1_mysql_connection{id = Id} = H | T], Res) ->
    {ok, H, lists:reverse(Res) ++ T};
get_next_mysql_connection_for_id(Id, [H | T], Res) when is_record(H, p1_mysql_connection) ->
    get_next_mysql_connection_for_id(Id, T, [H | Res]);
get_next_mysql_connection_for_id(_Id, [], _Res) ->
    nomatch.

%%--------------------------------------------------------------------
%% Function: start_reconnect(Conn, LogFun)
%%           Conn   = p1_mysql_connection record()
%%           LogFun = undefined | function() with arity 3
%% Descrip.: Spawns a process that will try to re-establish a new
%%           connection instead of the one in Conn which has just
%%           died.
%% Returns : ok
%%--------------------------------------------------------------------
start_reconnect(Conn, LogFun) when is_record(Conn, p1_mysql_connection) ->
    Pid = spawn(fun () ->
			reconnect_loop(Conn#p1_mysql_connection{conn_pid = undefined}, LogFun, 0)
		end),
    {Id, Host, Port} = {Conn#p1_mysql_connection.id, Conn#p1_mysql_connection.host, Conn#p1_mysql_connection.port},
    log(LogFun, debug, "p1_mysql: Started pid ~p to try and reconnect to ~p:~s:~p (replacing "
	"connection with pid ~p)", [Pid, Id, Host, Port, Conn#p1_mysql_connection.conn_pid]),
    ok.

%%--------------------------------------------------------------------
%% Function: reconnect_loop(Conn, LogFun, 0)
%%           Conn   = p1_mysql_connection record()
%%           LogFun = undefined | function() with arity 3
%% Descrip.: Loop indefinately until we are able to reconnect to the
%%           server specified in the now dead connection Conn.
%% Returns : ok
%%--------------------------------------------------------------------
reconnect_loop(Conn, LogFun, N) when is_record(Conn, p1_mysql_connection) ->
    {Id, Host, Port} = {Conn#p1_mysql_connection.id, Conn#p1_mysql_connection.host, Conn#p1_mysql_connection.port},
    case connect(Id,
		 Host,
		 Port,
		 Conn#p1_mysql_connection.user,
		 Conn#p1_mysql_connection.password,
		 Conn#p1_mysql_connection.database,
		 Conn#p1_mysql_connection.reconnect,
		 Conn#p1_mysql_connection.ssl_opts) of
	{ok, ConnPid} ->
	    log(LogFun, debug, "p1_mysql_reconnect: Managed to reconnect to ~p:~s:~p (connection pid ~p)",
		[Id, Host, Port, ConnPid]),
	    ok;
	{error, Reason} ->
	    %% log every once in a while
	    NewN = case N of
		       10 ->
			   log(LogFun, debug, "p1_mysql_reconnect: Still unable to connect to ~p:~s:~p (~p)",
			       [Id, Host, Port, Reason]),
			   0;
		       _ ->
			   N + 1
		   end,
	    %% sleep between every unsuccessfull attempt
	    timer:sleep(20 * 1000),
	    reconnect_loop(Conn, LogFun, NewN)
    end.
