from plenum.common.messages.node_messages import InstanceChange, ViewChangeDone
from plenum.test.delayers import icDelay, vcd_delay
from plenum.test.helper import waitForViewChange
from plenum.test.node_catchup.helper import ensure_all_nodes_have_same_data
from plenum.test.test_node import ensureElectionsDone

nodeCount = 7


def check_vcd_msgs(node, expected_count, expected_view_no):
    if expected_view_no not in node.msgsForFutureViews:
        raise AssertionError('no messages for expected view')
    vcd_count = sum(1 for msg in node.msgsForFutureViews[expected_view_no] if isinstance(msg[0], ViewChangeDone))
    assert expected_count == vcd_count


def check_no_ic_msgs(node, expected_view_no):
    if expected_view_no not in node.msgsForFutureViews:
        raise AssertionError('no messages for expected view')
    ic_count = sum(1 for msg in node.msgsForFutureViews[expected_view_no] if isinstance(msg[0], InstanceChange))
    assert ic_count == 0


def test_delayed_instance_changes_after_vcd_for_next_view(looper, txnPoolNodeSet):
    '''
    A node is doing view change to view=1, while the other nodes already finished view change to view=2.
    The node receives a quorum of VCD messages for view=2 before a quorum of InstanceChange messages for view=2.
    Nevertheless, the node should not start a view change to view=2 without a quorum of InstanceChanges,
    that is it should not go to propagate primary mode since it's already in view chanage state.
    The node should eventually finish view change to view=2 once receives all VCD and IS msgs for view=2
    '''
    nodes = txnPoolNodeSet
    slow_node = nodes[-1]
    fast_nodes = [n for n in nodes if n != slow_node]
    slow_stasher = slow_node.nodeIbStasher

    # 1. DO FIRST VIEW CHANGE

    # delay VCD for the first ViewChange
    slow_stasher.delay(vcd_delay())

    # Trigger view change
    for n in nodes:
        n.view_changer.on_master_degradation()
    waitForViewChange(looper, nodes, expectedViewNo=1)

    # make sure view change is finished on all nodes except the slow one
    ensureElectionsDone(looper, fast_nodes, numInstances=3)

    # 2. DO SECOND VIEW CHANGE

    # delay Instance Changes and drop all VCD to view=1
    # so that the slow node receives VCD for view=2 before
    # a quorum of InstanceChanges for that view while still doing view change to view=1
    slow_stasher.drop_delayeds()
    slow_stasher.resetDelays()
    slow_stasher.delay(icDelay())

    # Trigger view change
    for n in nodes:
        n.view_changer.on_master_degradation()
    waitForViewChange(looper, fast_nodes, expectedViewNo=2)

    # make sure view change is finished on all nodes except the slow one
    ensureElectionsDone(looper, fast_nodes, numInstances=3)

    # slow node is still on view=1
    assert slow_node.viewNo == 1
    assert slow_node.view_change_in_progress

    # make sure that the slow node receives VCD msgs for view=2
    # and didn't receive IS msgs for view=2
    check_vcd_msgs(slow_node, expected_view_no=2, expected_count=len(fast_nodes), )
    check_no_ic_msgs(slow_node, expected_view_no=2)

    # 3. RESET DELAYS AND CHECK
    slow_stasher.reset_delays_and_process_delayeds()

    waitForViewChange(looper, nodes, expectedViewNo=2)
    ensureElectionsDone(looper, nodes)
    assert not slow_node.view_change_in_progress
    ensure_all_nodes_have_same_data(looper, nodes=nodes)

@@ -250,16 +250,60 @@
 me_data%0A
+from plenum.test.stasher import delay_rules%0A
 from ple
@@ -1849,24 +1849,41 @@
 wChange%0A    
+with delay_rules(
 slow_stasher
@@ -1882,23 +1882,18 @@
 _stasher
-.delay(
+, 
 vcd_dela
@@ -1892,30 +1892,34 @@
 vcd_delay())
-%0A%0A
+:%0A  
     
+  
 # Trigger vi
@@ -1920,32 +1920,36 @@
 ger view change%0A
+    
     for n in nod
@@ -1944,32 +1944,36 @@
 for n in nodes:%0A
+    
         n.view_c
@@ -1995,32 +1995,36 @@
 r_degradation()%0A
+    
     waitForViewC
@@ -2063,16 +2063,20 @@
 wNo=1)%0A%0A
+    
     # ma
@@ -2136,32 +2136,36 @@
 he slow one%0A    
+  
+  
 ensureElectionsD
@@ -2201,24 +2201,95 @@
 stances=3)%0A%0A
+        # drop all VCD to view=1%0A        slow_stasher.drop_delayeds()%0A%0A
     # 2. DO 
@@ -2340,39 +2340,16 @@
 nges and
- drop all VCD to view=1
 %0A    # s
@@ -2496,72 +2496,25 @@
     
-slow_stasher.drop_delayeds()%0A    slow_stasher.resetDelays()%0A    
+with delay_rules(
 slow
@@ -2525,15 +2525,10 @@
 sher
-.delay(
+, 
 icDe
@@ -2533,18 +2533,23 @@
 Delay())
+:
 %0A%0A
+    
     # Tr
@@ -2566,24 +2566,28 @@
  change%0A    
+   
+ 
 for n in nod
@@ -2582,32 +2582,36 @@
 for n in nodes:%0A
+    
         n.view_c
@@ -2637,24 +2637,28 @@
 gradation()%0A
+    
     waitForV
@@ -2706,24 +2706,28 @@
 wNo=2)%0A%0A    
+ 
+   
 # make sure 
@@ -2779,24 +2779,28 @@
 he slow one%0A
+    
     ensureEl
@@ -2852,16 +2852,20 @@
 3)%0A%0A    
+  
+  
 # slow n
@@ -2887,24 +2887,28 @@
  view=1%0A    
+   
+ 
 assert slow_
@@ -2924,16 +2924,20 @@
 No == 1%0A
+    
     asse
@@ -2974,16 +2974,20 @@
 ogress%0A%0A
+    
     # ma
@@ -3038,24 +3038,28 @@
  for view=2%0A
+    
     # and di
@@ -3090,24 +3090,28 @@
  view=2%0A    
+  
+  
 check_vcd_ms
@@ -3178,24 +3178,28 @@
 des), )%0A    
+   
+ 
 check_no_ic_
@@ -3270,61 +3270,8 @@
 HECK
-%0A    slow_stasher.reset_delays_and_process_delayeds()
 %0A%0A  

use context manager in tests