The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
% -----------------------------------------------------------------------------
% unijp.erl
% -----------------------------------------------------------------------------
% Mastering programmed by YAMASHINA Hio
%
% Copyright 2007 YAMASHINA Hio
% -----------------------------------------------------------------------------

% -----------------------------------------------------------------------------
%% @author    YAMASHINA Hio <hio@hio.jp>
%% @copyright 2008 YAMASHINA Hio
%% @version   0.01
%% @doc       Unicode::Japanese binding
%% == SUPPORTED ENCODINGS ==
%% These options are available for `Icode' argument of {@link new/3} and {@link set/2}.
%% <ul>
%% <li>`utf8'</li>
%% <li>`sjis'</li>
%% <li>`eucjp'</li>
%% <li>`jis'</li>
%% </ul>
%% @end
% -----------------------------------------------------------------------------

-module(unijp).
-export([start/0, stop/0]).
-export([version_str/0]).
-export([version_tuple/0]).
-export([conv/3]).
-export([test/0]).

-define(SHAREDLIB, "unijp_driver").
-define(PKGNAME, unijp).
-define(REGNAME, unijp).

-define(PORT_VERSION_STR,   1).
-define(PORT_VERSION_TUPLE, 2).
-define(PORT_CONV_3,        3).

test(Name,Fun) ->
  io:format("~s ...~n", [Name]),
  Ret = Fun(),
  io:format("~s: ~p~n", [Name, Ret]),
  Ret.
test() ->
  test(start, fun()-> start() end),
  test(version_str,   fun()-> version_str()                end),
  test(version_tuple, fun()-> version_tuple()              end),
  test(conv,          fun()-> conv("utf8", "utf8", "text") end),
  test(conv,          fun()-> conv("utf8", "ucs4", "ts") end),
  test(conv,          fun()-> conv("utf8", "ucs4", "text") end),
  io:format("ok.~n"),
  ok.

% -----------------------------------------------------------------------------
% version_str().
%% @spec version_str() -> string()
%% @doc get version number as string.
version_str() ->
	Result = erlang:port_call(whereis(?REGNAME), ?PORT_VERSION_STR, []),
	{ok, VersionStr} = Result,
	VersionStr.

% -----------------------------------------------------------------------------
% version_tuple().
%% @spec version_tuple() -> {int(),int(),int()}
%% @doc get version number as tuple of integers.
version_tuple() ->
	Result = erlang:port_call(whereis(?REGNAME), ?PORT_VERSION_TUPLE, []),
	{ok, {Major,Minor,Patch}} = Result,
	{Major,Minor,Patch}.

% -----------------------------------------------------------------------------
% conv(From, To, Source).
%% @spec conv(From, To, Source) -> string()
%%   From   = atom()
%%   To     = atom()
%%   Source = iolist()
%% @doc convert string Source from From to To.
conv(From, To, Source) ->
	Bin = iolist_to_binary(Source),
	Result = erlang:port_call(whereis(?REGNAME), ?PORT_CONV_3, {From,To,Bin}),
	{ok, Converted} = Result,
	Converted.

% -----------------------------------------------------------------------------
% start.
%
%% @spec start()->term()
%% @doc  start port driver
start() ->
	my_start(whereis(?REGNAME)).

%% @spec my_start(Port)->pod()
%%   Port = undefined | port()
%% @private
my_start(undefined) ->
	% io:format("start: begin trans..~n"),
	global:trans({unijp_start, self()}, fun()->
		case whereis(?REGNAME) of
		undefined ->
			% io:format("start: real start~n"),
			Pid = my_spawn_server(),
			% io:format("start: register: ~p (registered:~p)~n", [Pid, whereis(?REGNAME)]),
			Pid;
		Pid ->
			% io:format("start: found in tran ~p~n", [Pid]),
			Pid
		end
	end);
my_start(Pid) ->
	% io:format("start: exists: ~p~n", [Pid]),
	Pid.

%% @spec my_spawn_server()->pid()
%% @private
%% @doc  spawn server process.
my_spawn_server() ->
	% io:format("spawn server ...~n"),
	Parent = self(),
	Daemon = spawn(fun()->my_server(Parent) end),
	Port = receive
		{Daemon, Recv} -> Recv
		% after 3000     -> exit(timeout)
		after 3000*60     -> exit(timeout)
	end,
	Port.

%% @spec my_server(Parent)->void()
%%   Parent = pid()
%% @private
%% @doc  unijp daemon, main.
my_server(Parent) ->
	% io:format("server proc ...~n"),
	Port = my_open_port(),
	register(?REGNAME, Port),
	register(unijp_daemon, self()),
	Parent ! {self(), Port},
	my_server_loop([]).

%% @spec my_open_port() -> port()
%% @private
%% @doc  open port procedure.
my_open_port()->
	% io:format("load_driver ...~n"),
	case erl_ddll:load_driver(code:priv_dir(?PKGNAME), ?SHAREDLIB) of
		ok -> ok;
		{error, already_loaded} -> ok;
		Any -> exit({error, {could_not_load_driver, Any}})
	end,
	% io:format("open_port ...~n"),
	Port = open_port({spawn, ?SHAREDLIB}, [binary]),
	% io:format("open_port ~p on ~p~n", [Port, self()]),
	Port.

%% @spec my_server_loop(PortList)->void()
%%   PortList = [port()]
%% @private
%% @doc  loop forever to keep driver/port instances.
my_server_loop(PortList)->
	% io:format("my_server_loop ~p(~p)~n", [self(), PortList]),
	receive
	{ Caller, close } when is_pid(Caller) ->
		Caller ! ok;
	{ Caller, find_port } when is_pid(Caller) ->
		my_server_find_port(Caller, PortList);
	{ Caller, release_port, Port } when is_pid(Caller) ->
		my_server_loop([Port|PortList]);
	_Any ->
		my_server_loop(PortList)
	end.

my_server_find_port(Caller, []) ->
	my_server_find_port(Caller, [my_open_port()]);
my_server_find_port(Caller, [Port|PortList]) ->
	Caller ! { find_port, Port },
	my_server_loop(PortList).

% -----------------------------------------------------------------------------
% stop.
%
%% @spec stop() -> ok
%% @doc  stop unijp port service.
stop() ->
	case whereis(unijp_daemon) of
	undefined ->
		ok;
	Port ->
		Port ! { self(), close },
		receive
			ok -> ok
		after 10000 ->
			exit(noreply)
		end
	end.

% -----------------------------------------------------------------------------
% End of Module.
% -----------------------------------------------------------------------------