anchor.diff:          _ -> invalid_ip()
       end.
   
  +%% mt_ips value format:
  +%%     2 |  10 |       8 |       12 |   16 |     16 |    256
  +%% ------+-----+---------+----------+------+--------+--------
  +%%  flag | ISP | country | province | city | county | bitmap
   lookup_ets(IP) ->
  -    ok.
  +    {A, B, C, D} = inet:parse_ipv4_address(IP),
  +    Key = (A bsl 16) + (B bsl 8) + C,
  +    case ets:lookup(?IPS_TABLE, Key) of
  +        [{Key, Value}] ->
  +            L = 255 - D,
  +            <<Flag:2, ISP:10, Country:8, Province:12, City:16,
  +              County:16, _:L, X:1, _:D>> = Value,
  +            case X of
  +                1 ->
  +                    Geo = id_to_name(ISP, Country, Province, City, County),
  +                    {200, format([IP | Geo])};
  +                0 -> ok
  +            end;
  +        [] ->
  +            case taobao_api(IP) of
  +                ok -> lookup_ets(IP);
  +                error -> invalid_ip()
  +            end
  +    end.
  +
  +id_to_name(ISP_id, Country_id, Province_id, City_id, County_id) ->
  +    [{_, ISP}]      = ets:lookup(?GEO_TABLE, {isp,      ISP_id}),
  +    [{_, Country}]  = ets:lookup(?GEO_TABLE, {country,  Country_id}),
  +    [{_, Province}] = ets:lookup(?GEO_TABLE, {province, Province_id}),
  +    [{_, City}]     = ets:lookup(?GEO_TABLE, {city,     City_id}),
  +    [{_, County}]   = ets:lookup(?GEO_TABLE, {county,   County_id}),
  +    [ISP, Country, Province, City, County].
  +
  +format(Args) when is_list(Args) ->
  +    % [IP, ISP, Country, Province, City, County] = Args
  +    io_lib:format(normal_ip(), Args).
  +
  +normal_ip() ->
  +    "{\"ip\":\"~s\", \"isp\":\"~s\", \"country\":\"~s\"," ++
  +    "\"province\":\"~s\", \"city\":\"~s\", \"county\":\"~s\"}".
   
   special_ip(IP) ->
       Json = "{\"province\":\"https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml\"," ++
  
positive.code:     {200, Json}.
  
  invalid_ip() ->
      {400, "{\"message\":\"invalid ip.\"}"}.
  
  months() ->
      ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
  
negative.code: %% Geographic IP Informatin Server
  %%================================
  
  -module(geoip).
  -author('wuhualiang@meituan.com').
  
  -mode(compile).
  -export([init/1, start/2, worker/2]).
  
  -define(DEV, _).
  -ifdef(DEV).
  -export([http_response/2]).
  -export([special_ip/1]).
  -export([multicast_ip/1]).
  -export([invalid_ip/0]).
  -export([normal_ip/0]).
  -export([format/1]).
  -endif.
  
  -define(LOG_FILE, "logs").
  -define(IPS_FILE, "ips.dump").
  -define(GEO_FILE, "geo.meta").
  -define(IPS_TABLE, mt_ips).
  -define(GEO_TABLE, mt_geo).
  -define(GEO_MANAGER, geoman).
  -define(SNAPSHOT_INTV, 86400000).
  -define(WRITEBACK_THR, 100000).
  -define(HIGH_WATER_MARK, 1000).
  
  init(careful) ->
      ets:new(?IPS_TABLE, [set, public, named_table]),
      ets:new(?GEO_TABLE, [set, public, named_table]),
      ets:insert(?IPS_TABLE, {count, 0}),
      ets:insert(?GEO_TABLE, {{ips,      count}, 0}),
      ets:insert(?GEO_TABLE, {{country,  count}, 0}),
      ets:insert(?GEO_TABLE, {{province, count}, 0}),
      ets:insert(?GEO_TABLE, {{city,     count}, 0}),
      ets:insert(?GEO_TABLE, {{county,   count}, 0}),
      ets:tab2file(?IPS_TABLE, ?IPS_FILE),
      ets:tab2file(?GEO_TABLE, ?GEO_FILE),
      ets:delete(?IPS_TABLE),
      ets:delete(?GEO_TABLE),
      ok.
  
  start(Port, Procs) ->
      inets:start(),
      error_logger:logfile({open, ?LOG_FILE}),
      {ok, ?IPS_TABLE} = ets:file2tab(?IPS_FILE),
      {ok, ?GEO_TABLE} = ets:file2tab(?GEO_FILE),
      GeoMan = spawn(geoman),
      register(?GEO_MANAGER, GeoMan),
      lists:map(fun worker_init/1, [0, Procs-1]),
      {ok, LSock} = gen_tcp:listen(Port, [{active, false}, {packet, http}]),
      loop(LSock, Procs).
  
  %% ======== Internal Functions ======== %%
  
  worker_init(X) when is_integer(X) ->
      Tid = ets:new(taskqueue, [set, public]),
      put({worker, X}, Tid),
      put({latest_task, X}, 0),
      spawn(?MODULE, worker, [Tid, 1]).
  
  worker(Tid, Todo) ->
      case ets:lookup(Tid, Todo) of
          [{_, Sock}] ->
              case gen_tcp:recv(Sock, 0, 4000) of
                  {ok, {http_request, 'GET', {abs_path, Path}, _}} ->
                      case Path of
                          "/api/ip/get/" ++ IP ->
                              {Code, Json} = lookup(IP),
                              Response = http_response(Code, Json),
                              inet:setopts(Sock, {send_timeout, 4000}),
                              gen_tcp:send(Sock, Response);
                          _ -> pass
                      end;
                  _ -> pass
              end,
              gen_tcp:close(Sock),
              worker(Tid, Todo + 1);
          [] ->
              timer:sleep(1),
              worker(Tid, Todo)
      end.
  
  loop(LSock, Procs) ->
      {ok, Sock} = gen_tcp:accept(LSock),
      {X, Tid} = free_proc(Procs),
      Task = get({latest_task, X}) + 1,
      put({latest_task, X}, Task),
      ets:insert(Tid, {Task, Sock}),
      loop(LSock, Procs).
  
  free_proc(Procs) -> free_proc(Procs, 1).
  free_proc(Procs, Tried) ->
      {_, _, Y} = erlang:now(),
      X = Y rem Procs,
      Tid = get({worker, X}),
      Size = ets:info(Tid, size),
      if Size < ?HIGH_WATER_MARK ->
          {X, Tid};
      true ->
          if Procs == Tried ->
              {X, Tid};
          true ->
              free_proc(Procs, Tried + 1)
          end
      end.
  
  geoman() ->
      receive
          {Type, Name, Pid} when is_pid(Pid) ->
              case ets:lookup(?GEO_TABLE, {Type, Name}) of
                  [{_, Id}] ->
                      Pid ! Id;
                  [] ->
                      [{_, Count}] = ets:lookup(?GEO_TABLE, {Type, count}),
                      Id = Count + 1,
                      ets:insert({{Type, Id}, Name}),
                      ets:insert({{Type, Name}, Id}),
                      ets:insert({{Type, count}, Id}),
                      Pid ! Id
              end;
          _ -> pass
      end,
      geoman().
  
  snapshot() ->
      timer:sleep(?SNAPSHOT_INTV),
  