-module(detonate_deliver_site_graph).
-author("ChenLiang").
-behaviour(gen_server).
-include("detonate_server.hrl").
-export([start_link/0, lookup_deliver_node/2]).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
china_graph = digraph:new() :: digraph:graph(),
china_tid = ets:new(undefined, []) :: ets:tid(),
world_graph = digraph:new() :: digraph:graph(),
world_tid = ets:new(undefined, []) :: ets:tid()
}
).
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
lookup_deliver_node(lookup_province, Province) ->
lager:debug("lookup_province: ~p ~n", [Province]),
gen_server:call(?MODULE, {lookup_province, Province});
lookup_deliver_node(lookup_country, CountryCode) ->
lager:debug("lookup_country: ~p ~n", [CountryCode]),
gen_server:call(?MODULE, {lookup_country, CountryCode}).
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([]) ->
gen_server:cast(?MODULE, init_china),
gen_server:cast(?MODULE, init_world),
{ok, #state{}}.
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call({lookup_province, Province}, _From, State=#state{china_graph = ChinaGraph, china_tid = ChinaTid}) ->
Reply = lookup_deliver_node(lookup_province, ChinaGraph, ChinaTid, Province),
{reply, Reply, State};
handle_call({lookup_country, CountryCode}, _From, State=#state{world_graph = WorldGraph, world_tid = WorldTid}) ->
Reply = lookup_deliver_node(lookup_country, WorldGraph, WorldTid, CountryCode),
{reply, Reply, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(init_china, State=#state{china_graph = ChinaGraph, china_tid = ChinaTid}) ->
ChinaVertexFileName = filename:join(code:lib_dir(detonate_server, priv), "china_province_code.csv"),
ChinaEdgeFileName = filename:join(code:lib_dir(detonate_server, priv), "china_province_border.csv"),
initialize_graph(china, ChinaGraph, ChinaTid, ChinaVertexFileName, ChinaEdgeFileName),
{noreply, State};
handle_cast(init_world, State=#state{world_graph = WorldGraph, world_tid = WorldTid}) ->
WorldVertexFileName = filename:join(code:lib_dir(detonate_server, priv), "world_country_code.csv"),
WorldEdgeFileName = filename:join(code:lib_dir(detonate_server, priv), "world_country_border.csv"),
initialize_graph(world, WorldGraph, WorldTid, WorldVertexFileName, WorldEdgeFileName),
{noreply, State};
handle_cast(_Request, State) ->
{noreply, State}.
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State) ->
{noreply, State}.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State) ->
ok.
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
initialize_graph(china, Graph, Tid, VertexFileName, EdgeFileName)->
{ok, VertexIoDevice} = file:open(VertexFileName, [read, binary]),
add_vertex(china, Graph, Tid, VertexIoDevice),
file:close(VertexIoDevice),
{ok, EdgeIoDevice} = file:open(EdgeFileName, [read, binary]),
add_edge(china, Graph, Tid, EdgeIoDevice),
file:close(EdgeIoDevice);
initialize_graph(world, Graph, Tid, VertexFileName, EdgeFileName)->
{ok, VertexIoDevice} = file:open(VertexFileName, [read, binary]),
add_vertex(world, Graph, Tid, VertexIoDevice),
file:close(VertexIoDevice),
{ok, EdgeIoDevice} = file:open(EdgeFileName, [read, binary]),
add_edge(world, Graph, Tid, EdgeIoDevice),
file:close(EdgeIoDevice).
add_vertex(china, Graph, Tid, VertexIoDevice) ->
case file:read_line(VertexIoDevice) of
{ok, Line} ->
case binary:split(Line, [<<",">>, <<"\n">>], [global]) of
[Province, DeliverSite | _] ->
ets:insert(Tid, {Province, DeliverSite}),
digraph:add_vertex(Graph, DeliverSite),
add_vertex(china, Graph, Tid, VertexIoDevice);
_ ->
ok
end;
eof ->
ok
end;
add_vertex(world, Graph, Tid, VertexIoDevice) ->
case file:read_line(VertexIoDevice) of
{ok, Line} ->
case binary:split(Line, [<<",">>, <<"\n">>], [global]) of
[_Continent,<<>>] ->
add_vertex(world, Graph, Tid, VertexIoDevice);
[CountryCode, CountryChineseName, CountryEnglishName | _] ->
ets:insert(Tid, {CountryCode, CountryChineseName, CountryEnglishName}),
digraph:add_vertex(Graph, CountryCode),
add_vertex(world, Graph, Tid, VertexIoDevice);
_ ->
ok
end;
eof ->
ok
end.
add_edge(china, Graph, Tid, EdgeIoDevice) ->
case file:read_line(EdgeIoDevice) of
{ok, Line} ->
case binary:split(Line, [<<",">>, <<"\n">>], [global]) of
[Province1, Province2 | _] ->
[{_, DeliverSite1}] = ets:lookup(Tid, Province1),
[{_, DeliverSite2}] = ets:lookup(Tid, Province2),
digraph:add_edge(Graph, DeliverSite1, DeliverSite2),
digraph:add_edge(Graph, DeliverSite2, DeliverSite1),
add_edge(china, Graph, Tid, EdgeIoDevice);
_ ->
ok
end;
eof ->
ok
end;
add_edge(world, Graph, Tid, EdgeIoDevice) ->
case file:read_line(EdgeIoDevice) of
{ok, Line} ->
case binary:split(Line, [<<",">>, <<"\n">>], [global]) of
[_Continent,<<>>] ->
add_edge(world, Graph, Tid, EdgeIoDevice);
[_CountryCode1, CountryCode2 | _] when byte_size(CountryCode2) =/= 2 ->
add_edge(world, Graph, Tid, EdgeIoDevice);
[CountryCode1, CountryCode2 | _] ->
digraph:add_edge(Graph, CountryCode1, CountryCode2),
digraph:add_edge(Graph, CountryCode2, CountryCode1),
add_edge(world, Graph, Tid, EdgeIoDevice);
_ ->
ok
end;
eof ->
ok
end.
lookup_deliver_node(lookup_province, ChinaGraph, Tid, Province) ->
[{_, DeliverSite}] = ets:lookup(Tid, Province),
case bfs(lookup_province, ChinaGraph, DeliverSite) of
not_found ->
{ok, ?HANGZHOU};
{ok, RecentDeliverSite} ->
{ok, RecentDeliverSite}
end;
lookup_deliver_node(lookup_country, WorldGraph, _Tid, CountryCode) ->
case bfs(lookup_country, WorldGraph, CountryCode) of
not_found ->
{ok, ?TOKYO};
{ok, RecentDeliverSite} ->
{ok, RecentDeliverSite}
end.
bfs(Graph, Queue, NodeList, VisitedList) ->
case queue:is_empty(Queue) of
true ->
not_found;
_ ->
{{value, Vertex} ,NewQueue} = queue:out(Queue),
NewVisitedList = lists:append(VisitedList, [Vertex]),
case find(Vertex, NodeList) of
not_found ->
VertexList = find_vertex_list(Graph, Vertex),
NotVisitedVertexList = filter(VertexList, NewVisitedList),
NewQueue1 = in_queue(NotVisitedVertexList, NewQueue),
bfs(Graph, NewQueue1, NodeList, NewVisitedList);
{ok, DeliverSite} ->
{ok, DeliverSite}
end
end.
bfs(lookup_province, Graph, Vertex) ->
Queue = queue:new(),
{ok, SupportedNodeList} = application:get_env(china_deliver_sites),
NewQueue = in_queue(Vertex, Queue),
bfs(Graph, NewQueue, SupportedNodeList, []);
bfs(lookup_country, Graph, Vertex) ->
Queue = queue:new(),
{ok, SupportedNodeList} = application:get_env(world_deliver_sites),
NewQueue = in_queue(Vertex, Queue),
bfs(Graph, NewQueue, SupportedNodeList, []).
find(_Element, []) ->
not_found;
find(Element, [{CountryCode, DeliverSite}|Tail]) ->
case string:equal(Element, CountryCode) of
true ->
{ok, DeliverSite};
_ ->
find(Element, Tail)
end;
find(Element, [DeliverSite|Tail]) ->
case string:equal(Element, DeliverSite) of
true ->
{ok, DeliverSite};
_ ->
find(Element, Tail)
end.
is_visited(Element, List) ->
case find(Element, List) of
not_found ->
false;
_ ->
true
end.
filter(VertexList, VisitedList) ->
filter(VertexList, VisitedList, []).
filter([Head|Tail], VisitedList, ResultList) ->
case is_visited(Head, VisitedList) of
false ->
filter(Tail, VisitedList, ResultList ++ [Head]);
_ ->
filter(Tail, VisitedList, ResultList)
end;
filter([], _VisitedList, ResultList) ->
ResultList.
find_vertex_list(Graph, Vertex) ->
Edges = digraph:edges(Graph, Vertex),
find_vertex_list(Graph, Vertex, Edges, []).
find_vertex_list(Graph, Vertex, [Head|Tail], VertexList) ->
{_, V1, V2, _} = digraph:edge(Graph, Head),
case string:equal(V1, Vertex) of
true ->
find_vertex_list(Graph, Vertex, Tail, VertexList ++ [V2]);
_ ->
find_vertex_list(Graph, Vertex, Tail, VertexList)
end;
find_vertex_list(_Graph, _Vertex, [], VertexList) ->
VertexList.
in_queue([], Queue) ->
Queue;
in_queue([Head|Tail], Queue) ->
NewQueue = queue:in(Head, Queue),
in_queue(Tail, NewQueue);
in_queue(Item, Queue) ->
queue:in(Item, Queue).