Refactor to add an interface for view_state of #mrheader

The goal of this refactoring is to make it easier to understand
and maintain view_state data structure of #mrheader.

Unlike record #mrview in #mrst, the data structure that represents
view_states in #mrheader is a tuple (of tuples) and its definition
and application spreads across all couch_mrview_util.erl module.
This makes it hard to follow and understand how it used
or troubleshoot mrview in case of the problems.

This change introduces an interface for generation and accessing
view_state and also separates generation of reduction closures
from mrview's `reduce`, `reduce_to_count` and `fold_reduce` functions
as they rely on view_state's structure.

Note that this refactoring also  removes explicit set of generic
ordering function on collation option "raw" as it is a default
of couch_btree:open on missing option for `less` function.
diff --git a/src/couch_mrview_util.erl b/src/couch_mrview_util.erl
index c30460c..51e51d8 100644
--- a/src/couch_mrview_util.erl
+++ b/src/couch_mrview_util.erl
@@ -200,7 +200,7 @@
         purge_seq=couch_db:get_purge_seq(Db),
         id_btree_state=nil,
         log_btree_state=nil,
-        view_states=[{nil, nil, nil, 0, 0} || _ <- Views]
+        view_states=[make_view_state(#mrview{}) || _ <- Views]
     },
     init_state(Db, Fd, State, Header);
 % read <= 1.2.x header record and transpile it to >=1.3.x
@@ -215,8 +215,8 @@
         purge_seq=PurgeSeq,
         id_btree_state=IdBtreeState,
         log_btree_state=nil,
-        view_states=[{Bt, nil, nil, USeq, PSeq} || {Bt, USeq, PSeq} <- ViewStates]
-        });
+        view_states=[make_view_state(V) || V <- ViewStates]
+    });
 init_state(Db, Fd, State, Header) ->
     #mrst{
         language=Lang,
@@ -232,12 +232,6 @@
         view_states=ViewStates
     } = Header,
 
-    StateUpdate = fun
-        ({_, _, _, _, _}=St) -> St;
-        (St) -> {St, nil, nil, 0, 0}
-    end,
-    ViewStates2 = lists:map(StateUpdate, ViewStates),
-
     IdBtOpts = [{compression, couch_db:compression(Db)}],
     {ok, IdBtree} = couch_btree:open(IdBtreeState, Fd, IdBtOpts),
     {ok, LogBtree} = case SeqIndexed orelse KeySeqIndexed of
@@ -246,7 +240,7 @@
     end,
 
     OpenViewFun = fun(St, View) -> open_view(Db, Fd, Lang, St, View) end,
-    Views2 = lists:zipwith(OpenViewFun, ViewStates2, Views),
+    Views2 = lists:zipwith(OpenViewFun, ViewStates, Views),
 
     State#mrst{
         fd=Fd,
@@ -258,45 +252,33 @@
         views=Views2
     }.
 
-open_view(Db, Fd, Lang, {BTState, SeqBTState, KSeqBTState, USeq, PSeq}, View) ->
-    FunSrcs = [FunSrc || {_Name, FunSrc} <- View#mrview.reduce_funs],
-    ReduceFun =
-        fun(reduce, KVs) ->
-            KVs2 = detuple_kvs(expand_dups(KVs, []), []),
-            {ok, Result} = couch_query_servers:reduce(Lang, FunSrcs, KVs2),
-            {length(KVs2), Result};
-        (rereduce, Reds) ->
-            Count = lists:sum([Count0 || {Count0, _} <- Reds]),
-            UsrReds = [UsrRedsList || {_, UsrRedsList} <- Reds],
-            {ok, Result} = couch_query_servers:rereduce(Lang, FunSrcs, UsrReds),
-            {Count, Result}
-        end,
-
-    Less = case couch_util:get_value(<<"collation">>, View#mrview.options) of
-        <<"raw">> -> fun(A, B) -> A < B end;
-        _ -> fun couch_ejson_compare:less_json_ids/2
-    end,
-
+open_view(Db, Fd, Lang, ViewState, View) ->
+    ReduceFun = make_reduce_fun(Lang, View#mrview.reduce_funs),
+    LessFun = maybe_define_less_fun(View),
+    Compression = couch_db:compression(Db),
+    BTState = get_key_btree_state(ViewState),
     ViewBtOpts = [
-        {less, Less},
+        {less, LessFun},
         {reduce, ReduceFun},
-        {compression, couch_db:compression(Db)}
+        {compression, Compression}
     ],
     {ok, Btree} = couch_btree:open(BTState, Fd, ViewBtOpts),
 
     BySeqReduceFun = fun couch_db_updater:btree_by_seq_reduce/2,
     {ok, SeqBtree} = if View#mrview.seq_indexed ->
+        SeqBTState = get_seq_btree_state(ViewState),
         ViewSeqBtOpts = [{reduce, BySeqReduceFun},
-                         {compression, couch_db:compression(Db)}],
+                         {compression, Compression}],
 
         couch_btree:open(SeqBTState, Fd, ViewSeqBtOpts);
     true ->
         {ok, nil}
     end,
     {ok, KeyBySeqBtree} = if View#mrview.keyseq_indexed ->
-        KeyBySeqBtOpts = [{less, Less},
+        KSeqBTState = get_kseq_btree_state(ViewState),
+        KeyBySeqBtOpts = [{less, LessFun},
                           {reduce, BySeqReduceFun},
-                          {compression, couch_db:compression(Db)}],
+                          {compression, Compression}],
         couch_btree:open(KSeqBTState, Fd, KeyBySeqBtOpts);
     true ->
         {ok, nil}
@@ -305,8 +287,8 @@
     View#mrview{btree=Btree,
                 seq_btree=SeqBtree,
                 key_byseq_btree=KeyBySeqBtree,
-                update_seq=USeq,
-                purge_seq=PSeq}.
+                update_seq=get_update_seq(ViewState),
+                purge_seq=get_purge_seq(ViewState)}.
 
 
 temp_view_to_ddoc({Props}) ->
@@ -341,18 +323,9 @@
 reduce_to_count(nil) ->
     0;
 reduce_to_count(Reductions) ->
-    Reduce = fun
-        (reduce, KVs) ->
-            Counts = [
-                case V of {dups, Vals} -> length(Vals); _ -> 1 end
-                || {_,V} <- KVs
-            ],
-            {lists:sum(Counts), []};
-        (rereduce, Reds) ->
-            {lists:sum([Count0 || {Count0, _} <- Reds]), []}
-    end,
-    {Count, _} = couch_btree:final_reduce(Reduce, Reductions),
-    Count.
+    CountReduceFun = fun count_reduce/2,
+    FinalReduction = couch_btree:final_reduce(CountReduceFun, Reductions),
+    get_count(FinalReduction).
 
 %% @doc get all changes for a view
 get_view_changes_count(View) ->
@@ -414,25 +387,13 @@
         btree=Bt,
         reduce_funs=RedFuns
     } = View,
-    LPad = lists:duplicate(NthRed - 1, []),
-    RPad = lists:duplicate(length(RedFuns) - NthRed, []),
-    {_Name, FunSrc} = lists:nth(NthRed,RedFuns),
 
-    ReduceFun = fun
-        (reduce, KVs0) ->
-            KVs1 = detuple_kvs(expand_dups(KVs0, []), []),
-            {ok, Red} = couch_query_servers:reduce(Lang, [FunSrc], KVs1),
-            {0, LPad ++ Red ++ RPad};
-        (rereduce, Reds) ->
-            ExtractRed = fun({_, UReds0}) -> [lists:nth(NthRed, UReds0)] end,
-            UReds = lists:map(ExtractRed, Reds),
-            {ok, Red} = couch_query_servers:rereduce(Lang, [FunSrc], UReds),
-            {0, LPad ++ Red ++ RPad}
-    end,
+    ReduceFun = make_user_reds_reduce_fun(Lang, RedFuns, NthRed),
 
     WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
-        {_, Reds} = couch_btree:final_reduce(ReduceFun, PartialReds),
-        Fun(GroupedKey, lists:nth(NthRed, Reds), Acc0)
+        FinalReduction = couch_btree:final_reduce(ReduceFun, PartialReds),
+        UserReductions = get_user_reds(FinalReduction),
+        Fun(GroupedKey, lists:nth(NthRed, UserReductions), Acc0)
     end,
 
     couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options).
@@ -610,37 +571,12 @@
         views=Views
     } = State,
 
-    ViewStates = lists:foldr(fun(V, Acc) ->
-                    SeqBtState = case V#mrview.seq_indexed of
-                        true ->
-                            couch_btree:get_state(V#mrview.seq_btree);
-                        _ ->
-                            nil
-                    end,
-                    KSeqBtState = case V#mrview.keyseq_indexed of
-                        true ->
-                            couch_btree:get_state(V#mrview.key_byseq_btree);
-                        _ ->
-                            nil
-                    end,
-                    [{couch_btree:get_state(V#mrview.btree),
-                      SeqBtState,
-                      KSeqBtState,
-                      V#mrview.update_seq,
-                      V#mrview.purge_seq} | Acc]
-            end, [], Views),
-
-    LogBtreeState = case LogBtree of
-        nil -> nil;
-        _ -> couch_btree:get_state(LogBtree)
-    end,
-
     #mrheader{
         seq=Seq,
         purge_seq=PurgeSeq,
-        id_btree_state=couch_btree:get_state(IdBtree),
-        log_btree_state= LogBtreeState,
-        view_states=ViewStates
+        id_btree_state=get_btree_state(IdBtree),
+        log_btree_state=get_btree_state(LogBtree),
+        view_states=[make_view_state(V) || V <- Views]
     }.
 
 
@@ -1004,6 +940,121 @@
 
 %% End of <= 1.2.x upgrade code.
 
+make_view_state(#mrview{} = View) ->
+    BTState = get_btree_state(View#mrview.btree),
+    SeqBTState = case View#mrview.seq_indexed of
+        true ->
+            get_btree_state(View#mrview.seq_btree);
+        _ ->
+            nil
+    end,
+    KSeqBTState = case View#mrview.keyseq_indexed of
+        true ->
+            get_btree_state(View#mrview.key_byseq_btree);
+        _ ->
+            nil
+    end,
+    {
+        BTState,
+        SeqBTState,
+        KSeqBTState,
+        View#mrview.update_seq,
+        View#mrview.purge_seq
+    };
+make_view_state({BTState, UpdateSeq, PurgeSeq}) ->
+    {BTState, nil, nil, UpdateSeq, PurgeSeq};
+make_view_state(nil) ->
+    {nil, nil, nil, 0, 0}.
+
+
+get_key_btree_state(ViewState) ->
+    element(1, ViewState).
+
+get_seq_btree_state(ViewState) ->
+    element(2, ViewState).
+
+get_kseq_btree_state(ViewState) ->
+    element(3, ViewState).
+
+get_update_seq(ViewState) ->
+    element(4, ViewState).
+
+get_purge_seq(ViewState) ->
+    element(5, ViewState).
+
+get_count(Reduction) ->
+    element(1, Reduction).
+
+get_user_reds(Reduction) ->
+    element(2, Reduction).
+
+
+make_reduce_fun(Lang, ReduceFuns) ->
+    FunSrcs = [FunSrc || {_, FunSrc} <- ReduceFuns],
+    fun
+        (reduce, KVs0) ->
+            KVs = detuple_kvs(expand_dups(KVs0, []), []),
+            {ok, Result} = couch_query_servers:reduce(Lang, FunSrcs, KVs),
+            {length(KVs), Result};
+        (rereduce, Reds) ->
+            ExtractFun = fun(Red, {CountsAcc0, URedsAcc0}) ->
+                CountsAcc = CountsAcc0 + get_count(Red),
+                URedsAcc = lists:append(URedsAcc0, [get_user_reds(Red)]),
+                {CountsAcc, URedsAcc}
+            end,
+            {Counts, UReds} = lists:foldl(ExtractFun, {0, []}, Reds),
+            {ok, Result} = couch_query_servers:rereduce(Lang, FunSrcs, UReds),
+            {Counts, Result}
+    end.
+
+
+maybe_define_less_fun(#mrview{options = Options}) ->
+    case couch_util:get_value(<<"collation">>, Options) of
+        <<"raw">> -> undefined;
+        _ -> fun couch_ejson_compare:less_json_ids/2
+    end.
+
+
+count_reduce(reduce, KVs) ->
+    CountFun = fun
+        ({_, {dups, Vals}}, Acc) -> Acc + length(Vals);
+        (_, Acc) -> Acc + 1
+    end,
+    Count = lists:foldl(CountFun, 0, KVs),
+    {Count, []};
+count_reduce(rereduce, Reds) ->
+    CountFun = fun(Red, Acc) ->
+        Acc + get_count(Red)
+    end,
+    Count = lists:foldl(CountFun, 0, Reds),
+    {Count, []}.
+
+
+make_user_reds_reduce_fun(Lang, ReduceFuns, NthRed) ->
+    LPad = lists:duplicate(NthRed - 1, []),
+    RPad = lists:duplicate(length(ReduceFuns) - NthRed, []),
+    {_, FunSrc} = lists:nth(NthRed, ReduceFuns),
+    fun
+        (reduce, KVs0) ->
+            KVs = detuple_kvs(expand_dups(KVs0, []), []),
+            {ok, Result} = couch_query_servers:reduce(Lang, [FunSrc], KVs),
+            {0, LPad ++ Result ++ RPad};
+        (rereduce, Reds) ->
+            ExtractFun = fun(Reds0) ->
+                [lists:nth(NthRed, get_user_reds(Reds0))]
+            end,
+            UReds = lists:map(ExtractFun, Reds),
+            {ok, Result} = couch_query_servers:rereduce(Lang, [FunSrc], UReds),
+            {0, LPad ++ Result ++ RPad}
+    end.
+
+
+get_btree_state(nil) ->
+    nil;
+get_btree_state(#btree{} = Btree) ->
+    couch_btree:get_state(Btree).
+
+
 extract_view_reduce({red, {N, _Lang, #mrview{reduce_funs=Reds}}, _Ref}) ->
     {_Name, FunSrc} = lists:nth(N, Reds),
     FunSrc.