# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This module contains unit tests for the classes in the mtb module

import glob
import os
import sys
import unittest

import common_unittest_utils
import fuzzy
import mtb
import test_conf as conf

from common_unittest_utils import create_mocked_devices
from firmware_constants import AXIS, GV, MTB, PLATFORM, UNIT, VAL
from mtb import FingerPath, TidPacket
from geometry.elements import Point, about_eq


unittest_path_lumpy = os.path.join(os.getcwd(), 'tests/logs/lumpy')
mocked_device = create_mocked_devices()


def get_mtb_packets(gesture_filename):
    """Get mtb_packets object by reading the gesture file."""
    parser = mtb.MtbParser()
    packets = parser.parse_file(gesture_filename)
    mtb_packets = mtb.Mtb(packets=packets)
    return mtb_packets


class FakeMtb(mtb.Mtb):
    """A fake MTB class to set up x and y positions directly."""
    def __init__(self, list_x, list_y):
        self.list_x = list_x
        self.list_y = list_y

    def get_x_y(self, target_slot):
        """Return list_x, list_y directly."""
        return (self.list_x, self.list_y)


class MtbTest(unittest.TestCase):
    """Unit tests for mtb.Mtb class."""

    def setUp(self):
        self.test_dir = os.path.join(os.getcwd(), 'tests')
        self.data_dir = os.path.join(self.test_dir, 'data')

    def _get_filepath(self, filename, gesture_dir=''):
        return os.path.join(self.data_dir, gesture_dir, filename)

    def _get_range_middle(self, criteria):
        """Get the middle range of the criteria."""
        fc = fuzzy.FuzzyCriteria(criteria)
        range_min , range_max = fc.get_criteria_value_range()
        range_middle = (range_min + range_max) / 2.0
        return range_middle

    def _call_get_reversed_motions(self, list_x, list_y, expected_x,
                                   expected_y, direction):
        mtb = FakeMtb(list_x, list_y)
        displacement = mtb.get_reversed_motions(0, direction, ratio=0.1)
        self.assertEqual(displacement[AXIS.X], expected_x)
        self.assertEqual(displacement[AXIS.Y], expected_y)

    def test_get_reversed_motions_no_reversed(self):
        list_x = (10, 22 ,36, 54, 100)
        list_y = (1, 2 ,6, 10, 22)
        self._call_get_reversed_motions(list_x, list_y, 0, 0, GV.TLBR)

    def test_get_reversed_motions_reversed_x_y(self):
        list_x = (10, 22 ,36, 154, 100)
        list_y = (1, 2 ,6, 30, 22)
        self._call_get_reversed_motions(list_x, list_y, -54, -8, GV.TLBR)

    def _test_get_x_y(self, filename, slot, expected_value):
        gesture_filename = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(gesture_filename)
        list_x, list_y = mtb_packets.get_x_y(slot)
        points = zip(list_x, list_y)
        self.assertEqual(len(points), expected_value)

    def test_get_x_y(self):
        self._test_get_x_y('one_finger_with_slot_0.dat', 0, 12)
        self._test_get_x_y('one_finger_without_slot_0.dat', 0, 9)
        self._test_get_x_y('two_finger_with_slot_0.dat', 0, 121)
        self._test_get_x_y('two_finger_with_slot_0.dat', 1, 59)
        self._test_get_x_y('two_finger_without_slot_0.dat', 0, 104)
        self._test_get_x_y('two_finger_without_slot_0.dat', 1, 10)

    def test_get_pressure(self):
        """Test get pressure"""
        filename = 'one_finger_with_slot_0.dat'
        gesture_filename = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(gesture_filename)
        finger_paths = mtb_packets.get_ordered_finger_paths()

        # There is only one tracking ID in the file.
        self.assertEqual(len(finger_paths), 1)

        # Verify some of the pressure values
        finger_path = finger_paths.values()[0]
        list_z = finger_path.get('pressure')
        self.assertEqual(list_z[0:5], [59, 57, 56, 58, 60])

    def test_get_x_y_multiple_slots(self):
        filename = 'x_y_multiple_slots.dat'
        filepath = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(filepath)
        slots = (0, 1)
        list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
        expected_list_x = {}
        expected_list_y = {}
        expected_list_x[0] = [1066, 1068, 1082, 1183, 1214, 1285, 1322, 1351,
                              1377, 1391]
        expected_list_y[0] = [561, 559, 542, 426, 405, 358, 328, 313, 304, 297]
        expected_list_x[1] = [770, 769, 768, 758, 697, 620, 585, 565, 538, 538]
        expected_list_y[1] = [894, 894, 895, 898, 927, 968, 996, 1003, 1013,
                              1013]
        for slot in slots:
            self.assertEqual(list_x[slot], expected_list_x[slot])
            self.assertEqual(list_y[slot], expected_list_y[slot])

    def test_get_x_y_multiple_slots2(self):
        """Test slot state machine.

        When the last slot in the previous packet is slot 0, and the first
        slot in the current packet is also slot 0, the slot 0 will not be
        displayed explicitly. This test ensures that the slot stat machine
        is tracked properly.
        """
        filename = 'pinch_to_zoom.zoom_in.dat'
        filepath = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(filepath)
        slots = (0, 1)
        list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
        expected_final_x = {}
        expected_final_y = {}
        expected_final_x[0] = 1318
        expected_final_y[0] = 255
        expected_final_x[1] = 522
        expected_final_y[1] = 1232
        for slot in slots:
            self.assertEqual(list_x[slot][-1], expected_final_x[slot])
            self.assertEqual(list_y[slot][-1], expected_final_y[slot])

    def _test_get_all_finger_paths_about_numbers_of_packets(
            self, filename, expected_numbers):
        mtb_packets = get_mtb_packets(self._get_filepath(filename))
        finger_paths = mtb_packets.get_ordered_finger_paths()
        for tid, expected_len in expected_numbers.items():
            self.assertEqual(len(finger_paths[tid].tid_packets), expected_len)

    def test_get_ordered_finger_paths_about_number_of_packets(self):
        self._test_get_all_finger_paths_about_numbers_of_packets(
                'two_finger_with_slot_0.dat', {2101: 122, 2102: 60})
        self._test_get_all_finger_paths_about_numbers_of_packets(
                'two_finger_without_slot_0.dat', {2097: 105, 2098: 11})

    def test_data_ready(self):
        """Test data_ready flag when point.x could be 0."""
        filename = ('20130506_030025-fw_11.27-robot_sim/'
                    'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.27-'
                    'robot_sim-20130506_031554.dat')
        filepath = os.path.join(unittest_path_lumpy, filename)
        mtb_packets = get_mtb_packets(filepath)
        points = mtb_packets.get_ordered_finger_path(0, 'point')
        # Note:
        # 1. In the first packet, there exists the event ABS_PRESSURE
        #    but no ABS_MT_PRESSURE.
        # 2. The last packet with ABS_MT_TRACKING_ID = -1 is also counted.
        self.assertEqual(len(points), 78)

    def _test_drumroll(self, filename, expected_max_distance):
        """expected_max_distance: unit in pixel"""
        gesture_filename = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(gesture_filename)
        actual_max_distance = mtb_packets.get_max_distance_of_all_tracking_ids()
        self.assertTrue(about_eq(actual_max_distance, expected_max_distance))

    def test_drumroll(self):
        expected_max_distance = 52.0216301167
        self._test_drumroll('drumroll_lumpy.dat', expected_max_distance)

    def test_drumroll1(self):
        expected_max_distance = 43.5660418216
        self._test_drumroll('drumroll_lumpy_1.dat', expected_max_distance)

    def test_drumroll_link(self):
        expected_max_distance = 25.6124969497
        self._test_drumroll('drumroll_link.dat', expected_max_distance)

    def test_no_drumroll_link(self):
        expected_max_distance = 2.91547594742
        self._test_drumroll('no_drumroll_link.dat', expected_max_distance)

    def test_no_drumroll_link(self):
        expected_max_distance = 24.8243831746
        self._test_drumroll('drumroll_link_2.dat', expected_max_distance)

    def _test_finger_path(self, filename, tid, expected_slot, expected_data,
                          request_data_ready=True):
        """Test the data in a finger path"""
        # Instantiate the expected finger_path
        expected_finger_path = FingerPath(expected_slot,
                                          [TidPacket(time, Point(*xy), z)
                                          for time, xy, z in expected_data])

        # Derive the actual finger_path for the specified tid
        mtb_packets = get_mtb_packets(self._get_filepath(filename))
        finger_paths = mtb_packets.get_ordered_finger_paths(request_data_ready)
        actual_finger_path = finger_paths[tid]

        # Assert that the packet lengths are the same.
        self.assertEqual(len(expected_finger_path.tid_packets),
                         len(actual_finger_path.tid_packets))

        # Assert that all tid data (including syn_time, point, pressure, etc.)
        # in the tid packets are the same.
        for i in range(len(actual_finger_path.tid_packets)):
            expected_packet = expected_finger_path.tid_packets[i]
            actual_packet = actual_finger_path.tid_packets[i]
            self.assertEqual(expected_packet.syn_time, actual_packet.syn_time)
            self.assertTrue(expected_packet.point == actual_packet.point)
            self.assertEqual(expected_packet.pressure, actual_packet.pressure)

    def test_get_ordered_finger_paths(self):
        """Test get_ordered_finger_paths

        Tracking ID 95: slot 0 (no explicit slot 0 assigned).
                        This is the only slot in the packet.
        """
        filename = 'drumroll_link_2.dat'
        tid = 95
        expected_slot = 0
        expected_data = [# (syn_time,    (x,   y),   z)
                         (238154.686034, (789, 358), 59),
                         (238154.691606, (789, 358), 60),
                         (238154.697058, (789, 358), 57),
                         (238154.702576, (789, 358), 59),
                         (238154.713731, (789, 358), 57),
                         (238154.719160, (789, 359), 57),
                         (238154.724791, (789, 359), 56),
                         (238154.730111, (789, 359), 58),
                         (238154.735588, (788, 359), 53),
                         (238154.741068, (788, 360), 53),
                         (238154.746569, (788, 360), 49),
                         (238154.752108, (787, 360), 40),
                         (238154.757705, (787, 361), 27),
                         (238154.763075, (490, 903), 46),
                         (238154.768532, (486, 892), 61),
                         (238154.774695, (484, 895), 57),
                         (238154.780192, (493, 890), 56),
                         (238154.785651, (488, 893), 55),
                         (238154.791140, (488, 893), 56),
                         (238154.802080, (489, 893), 55),
                         (238154.807578, (490, 893), 50),
                         (238154.818573, (490, 893), 46),
                         (238154.824066, (491, 893), 36),
                         (238154.829525, (492, 893), 22),
                         (238154.849958, (492, 893), 22),
        ]
        self._test_finger_path(filename, tid, expected_slot, expected_data)

    def test_get_ordered_finger_paths2(self):
        """Test get_ordered_finger_paths

        Tracking ID 104: slot 0 (explicit slot 0 assigned).
                         This is the 2nd slot in the packet.
                         A slot 1 has already existed.
        """

        filename = 'drumroll_link_2.dat'
        tid = 104
        expected_slot = 0
        expected_data = [# (syn_time,    (x,   y),   z)
                         (238157.994296, (780, 373), 75),
                         (238158.001110, (780, 372), 75),
                         (238158.007128, (780, 372), 76),
                         (238158.012617, (780, 372), 73),
                         (238158.018112, (780, 373), 69),
                         (238158.023600, (780, 373), 68),
                         (238158.029542, (781, 373), 51),
                         (238158.049605, (781, 373), 51),
        ]
        self._test_finger_path(filename, tid, expected_slot, expected_data)

    def test_get_ordered_finger_paths2b(self):
        """Test get_ordered_finger_paths

        Tracking ID 103: slot 1 (explicit slot 1 assigned).
                         This tracking ID overlaps with two distinct
                         tracking IDs of which the slot is the same slot 0.
                         This is a good test as a multiple-finger case.

                         tid 102, slot 0 arrived
                         tid 103, slot 1 arrived
                         tid 102, slot 0 left
                         tid 104, slot 0 arrived
                         tid 103, slot 1 left
                         tid 104, slot 0 left
        """
        filename = 'drumroll_link_2.dat'
        tid = 103
        expected_slot = 1
        expected_data = [# (syn_time,    (x,   y),   z)
                         (238157.906405, (527, 901), 71),
                         (238157.911749, (527, 901), 74),
                         (238157.917247, (527, 901), 73),
                         (238157.923152, (527, 902), 71),
                         (238157.928317, (527, 902), 72),
                         (238157.934492, (527, 902), 71),
                         (238157.939984, (527, 902), 69),
                         (238157.945485, (527, 902), 65),
                         (238157.950984, (527, 902), 66),
                         (238157.956482, (527, 902), 70),
                         (238157.961976, (527, 902), 65),
                         (238157.973768, (527, 902), 64),
                         (238157.980491, (528, 901), 61),
                         (238157.987140, (529, 899), 60),
                         (238157.994296, (531, 896), 52),
                         (238158.001110, (534, 892), 34),
                         (238158.007128, (534, 892), 34),
                         (238158.012617, (534, 892), 34),
                         (238158.018112, (534, 892), 34),
                         (238158.023600, (534, 892), 34),
                         (238158.029542, (534, 892), 34),
        ]
        self._test_finger_path(filename, tid, expected_slot, expected_data)

    def test_get_ordered_finger_paths3(self):
        """Test get_ordered_finger_paths

        This is a good test sample.
        - An unusual slot 9
        - This is the 2nd slot in the packet. A slot 8 has already existed.
        - Its ABS_MT_PRESSURE is missing in the first packet.
        - Slot 8 terminates a few packets earlier than this slot.
        - Some of the ABS_MT_POSITION_X/Y and ABS_MT_PRESSURE are not shown.
        """
        filename = 'drumroll_3.dat'
        tid = 582
        expected_slot = 9
        expected_data = [# (syn_time,  (x,   y),   z)
                         (6411.371613, (682, 173), None),
                         (6411.382541, (667, 186), 35),
                         (6411.393355, (664, 189), 37),
                         (6411.404310, (664, 190), 38),
                         (6411.413015, (664, 189), 38),
                         (6411.422118, (665, 189), 38),
                         (6411.430792, (665, 189), 37),
                         (6411.439764, (667, 188), 36),
                         (6411.448484, (675, 185), 29),
                         (6411.457212, (683, 181), 17),
                         (6411.465843, (693, 172), 5),
                         (6411.474749, (469, 381), 6),
                         (6411.483702, (471, 395), 26),
                         (6411.492369, (471, 396), 13),
                         (6411.499916, (471, 396), 13),
        ]
        self._test_finger_path(filename, tid, expected_slot, expected_data,
                               request_data_ready=False)

    def test_get_ordered_finger_paths4(self):
        """Test get_ordered_finger_paths

        This test is to verify if it could handle the case when a finger-off
        event is followed immediately by a finger-on event in the same packet.
        This situation may occur occasionally in two_close_fingers_tracking
        gestures. Basically, this could be considered as a firmware bug.
        However, our test should be able to handle the situation gracefully.

        A problematic packet may look like:

        Event: time .., type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value -1
        Event: time .., type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 202
        Event: time .., type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 1577
        Event: time .., type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 1018
        Event: time .., type 3 (EV_ABS), code 58 (ABS_MT_PRESSURE), value 99
        Event: time .., type 3 (EV_ABS), code 48 (ABS_MT_TOUCH_MAJOR), value 19
        Event: time .., type 3 (EV_ABS), code 49 (ABS_MT_TOUCH_MINOR), value 19
        Event: time .., type 3 (EV_ABS), code 0 (ABS_X), value 1577
        Event: time .., type 3 (EV_ABS), code 1 (ABS_Y), value 1018
        Event: time .., type 3 (EV_ABS), code 24 (ABS_PRESSURE), value 99
        Event: time .., -------------- SYN_REPORT ------------
        """
        # Get the actual finger_paths from the gesture data file.
        filename = 'two_close_fingers_tracking.dat'
        mtb_packets = get_mtb_packets(self._get_filepath(filename))
        finger_paths = mtb_packets.get_ordered_finger_paths(
                request_data_ready=False)

        data_list = [
                # (tid, packet_idx, syn_time, (x, y), z, number_packets)
                (197, -1, 1395784288.323233, (1619, 1019), 98, 435),
                (202, 0, 1395784288.323233, (1577, 1018), 99, 261),
        ]

        for tid, packet_idx, syn_time, xy, z, number_packets in data_list:
            expected_packet = TidPacket(syn_time, Point(*xy), z)

            # Derive the actual finger path and the actual packet.
            actual_finger_path = finger_paths[tid]
            actual_packet = actual_finger_path.tid_packets[packet_idx]

            # Assert that the number of packets in the actual finger path
            # is equal to the specified number.
            self.assertEqual(number_packets,
                             len(actual_finger_path.tid_packets))

            # Assert that the expected packet is equal to the actual packet.
            self.assertEqual(expected_packet.syn_time, actual_packet.syn_time)
            self.assertTrue(expected_packet.point == actual_packet.point)
            self.assertEqual(expected_packet.pressure, actual_packet.pressure)


    def test_get_slot_data(self):
        """Test if it can get the data from the correct slot.

        slot 0 and slot 1 start at the same packet. This test verifies if the
        method uses the correct corresponding slot numbers.
        """
        filename = 'two_finger_tracking.diagonal.slow.dat'
        gesture_filename = self._get_filepath(filename)
        mtb_packets = get_mtb_packets(gesture_filename)

        # There are more packets. Use just a few of them to verify.
        xy_pairs = {
            # Slot 0
            0: [(1142, 191), (1144, 201), (1144, 200)],
            # Slot 1
            1: [(957, 105), (966, 106), (960, 104)],
        }

        number_packets = {
            # Slot 0
            0: 190,
            # Slot 1
            1: 189,
        }

        slots = [0, 1]
        for slot in slots:
            points = mtb_packets.get_slot_data(slot, 'point')
            # Verify the number of packets in each slot
            self.assertEqual(len(points), number_packets[slot])
            # Verify a few packets in each slot
            for i, xy_pair in enumerate(xy_pairs[slot]):
                self.assertTrue(Point(*xy_pair) == points[i])

    def test_convert_to_evemu_format(self):
        evemu_filename = self._get_filepath('one_finger_swipe.evemu.dat')
        mtplot_filename = self._get_filepath('one_finger_swipe.dat')
        packets = mtb.MtbParser().parse_file(mtplot_filename)
        evemu_converted_iter = iter(mtb.convert_to_evemu_format(packets))
        with open(evemu_filename) as evemuf:
            for line_evemu_original in evemuf:
                evemu_original = line_evemu_original.split()
                evemu_converted_str = next(evemu_converted_iter, None)
                self.assertNotEqual(evemu_converted_str, None)
                if evemu_converted_str:
                    evemu_converted = evemu_converted_str.split()
                self.assertEqual(len(evemu_original), 5)
                self.assertEqual(len(evemu_converted), 5)
                # Skip the timestamps for they are different in both formats.
                # Prefix, type, code, and value should be the same.
                for i in [0, 2, 3, 4]:
                    self.assertEqual(evemu_original[i], evemu_converted[i])

    def test_get_largest_gap_ratio(self):
        """Test get_largest_gap_ratio for one-finger and two-finger gestures."""
        # The following files come with noticeable large gaps.
        list_large_ratio = [
            'one_finger_tracking.left_to_right.slow_1.dat',
            'two_finger_gaps.vertical.dat',
            'two_finger_gaps.horizontal.dat',
            'resting_finger_2nd_finger_moving_segment_gaps.dat',
            'gap_new_finger_arriving_or_departing.dat',
            'one_stationary_finger_2nd_finger_moving_gaps.dat',
            'resting_finger_2nd_finger_moving_gaps.dat',
        ]
        gesture_slots = {
            'one_finger': [0,],
            'two_finger': [0, 1],
            'resting_finger': [1,],
            'gap_new_finger': [0,],
            'one_stationary_finger': [1,],
        }

        range_middle = self._get_range_middle(conf.no_gap_criteria)
        gap_data_dir = self._get_filepath('gaps')
        gap_data_filenames = glob.glob(os.path.join(gap_data_dir, '*.dat'))
        for filename in gap_data_filenames:
            mtb_packets = get_mtb_packets(filename)
            base_filename = os.path.basename(filename)

            # What slots to check are based on the gesture name.
            slots = []
            for gesture in gesture_slots:
                if base_filename.startswith(gesture):
                    slots = gesture_slots[gesture]
                    break

            for slot in slots:
                largest_gap_ratio = mtb_packets.get_largest_gap_ratio(slot)
                if base_filename in list_large_ratio:
                    self.assertTrue(largest_gap_ratio >= range_middle)
                else:
                    self.assertTrue(largest_gap_ratio < range_middle)

    def test_get_largest_accumulated_level_jumps(self):
        """Test get_largest_accumulated_level_jumps."""
        dir_level_jumps = 'drag_edge_thumb'

        filenames = [
            # filenames with level jumps
            # ----------------------------------
            'drag_edge_thumb.horizontal.dat',
            'drag_edge_thumb.horizontal_2.dat',
            # test no points in some tracking ID
            'drag_edge_thumb.horizontal_3.no_points.dat',
            'drag_edge_thumb.vertical.dat',
            'drag_edge_thumb.vertical_2.dat',
            'drag_edge_thumb.diagonal.dat',
            # Change tracking IDs quickly.
            'drag_edge_thumb.horizontal_4.change_ids_quickly.dat',

            # filenames without level jumps
            # ----------------------------------
            'drag_edge_thumb.horizontal.curvy.dat',
            'drag_edge_thumb.horizontal_2.curvy.dat',
            'drag_edge_thumb.vertical.curvy.dat',
            'drag_edge_thumb.vertical_2.curvy.dat',
            # Rather small level jumps
            'drag_edge_thumb.horizontal_5.small_level_jumps.curvy.dat',
        ]

        largest_level_jumps = {
            # Large jumps
            'drag_edge_thumb.horizontal.dat': {AXIS.X: 0, AXIS.Y: 97},
            # Smaller jumps
            'drag_edge_thumb.horizontal_2.dat': {AXIS.X: 0, AXIS.Y: 24},
            # test no points in some tracking ID
            'drag_edge_thumb.horizontal_3.no_points.dat':
                    {AXIS.X: 97, AXIS.Y: 88},
            # Change tracking IDs quickly.
            'drag_edge_thumb.horizontal_4.change_ids_quickly.dat':
                    {AXIS.X: 0, AXIS.Y: 14},
            # Large jumps
            'drag_edge_thumb.vertical.dat': {AXIS.X: 54, AXIS.Y: 0},
            # The first slot 0 comes with smaller jumps only.
            'drag_edge_thumb.vertical_2.dat': {AXIS.X: 20, AXIS.Y: 0},
            # Large jumps
            'drag_edge_thumb.diagonal.dat': {AXIS.X: 84, AXIS.Y: 58},
        }

        target_slot = 0
        for filename in filenames:
            filepath = self._get_filepath(filename, gesture_dir=dir_level_jumps)
            packets = get_mtb_packets(filepath)
            displacements = packets.get_displacements_for_slots(target_slot)

            # There are no level jumps in a curvy line.
            file_with_level_jump = 'curvy' not in filename

            # Check the first slot only
            tids = displacements.keys()
            tids.sort()
            tid = tids[0]
            # Check both axis X and axis Y
            for axis in AXIS.LIST:
                disp = displacements[tid][axis]
                jump = packets.get_largest_accumulated_level_jumps(disp)
                # Verify that there are no jumps in curvy files, and
                #        that there are jumps in the other files.
                expected_jump = (0 if not file_with_level_jump
                                   else largest_level_jumps[filename][axis])
                self.assertTrue(jump == expected_jump)

    def test_get_max_distance_from_points(self):
        """Test get_max_distance_from_points"""
        # Two farthest points: (15, 16) and (46, 70)
        list_coordinates_pairs = [
            (20, 25), (21, 35), (15, 16), (25, 22), (30, 32), (46, 70),
            (35, 68), (42, 53), (50, 30), (43, 69), (16, 17), (14, 30),
        ]
        points = [Point(*pairs) for pairs in list_coordinates_pairs]
        mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])

        # Verify the max distance in pixels
        max_distance_px = mtb_packets.get_max_distance_from_points(points,
                                                                   UNIT.PIXEL)
        expected_max_distance_px = ((46 - 15) ** 2 + (70 - 16) ** 2) ** 0.5
        self.assertAlmostEqual(max_distance_px, expected_max_distance_px)

        # Verify the max distance in mms
        max_distance_mm = mtb_packets.get_max_distance_from_points(points,
                                                                   UNIT.MM)
        expected_max_distance_mm = (((46 - 15) / 12.0) ** 2 +
                                    ((70 - 16) / 10.0) ** 2) ** 0.5
        self.assertAlmostEqual(max_distance_mm, expected_max_distance_mm)

    def _test_get_segments(self, list_t, list_coord, expected_segments, ratio):
        """Test get_segments

        @param expected_segments: a dictionary of
                {segment_flag: expected_segment_indexes}
        """
        mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])
        for segment_flag, (expected_segment_t, expected_segment_coord) in \
                expected_segments.items():
            segment_t, segment_coord = mtb_packets.get_segments(
                    list_t, list_coord, segment_flag, ratio)
            self.assertEqual(segment_t, expected_segment_t)
            self.assertEqual(segment_coord, expected_segment_coord)

    def test_get_segments_by_distance(self):
        """Test get_segments_by_distance

        In the test case below,
            min_coord = 100
            max_coord = 220
            max_distance = max_coord - min_coord = 220 - 100 = 120
            ratio = 0.1
            120 * 0.1 = 12
            begin segment: 100 ~ 112
            end segment: 208 ~ 220
        """
        list_coord = [102, 101, 101, 100, 100, 103, 104, 110, 118, 120,
                      122, 124, 131, 140, 150, 160, 190, 210, 217, 220]
        list_t = [1000 + 0.012 * i for i in range(len(list_coord))]
        ratio = 0.1
        expected_segments= {
                VAL.WHOLE: (list_t, list_coord),
                VAL.MIDDLE: (list_t[8:17], list_coord[8:17]),
                VAL.BEGIN: (list_t[:8], list_coord[:8]),
                VAL.END: (list_t[17:], list_coord[17:]),
        }
        self._test_get_segments(list_t, list_coord, expected_segments, ratio)

    def test_get_segments_by_length(self):
        """Test get_segments_by_length"""
        list_coords = [
                [105, 105, 105, 105, 105, 105, 105, 105, 105, 105],
                [104, 105, 105, 105, 105, 105, 105, 105, 105, 105],
                [105, 105, 105, 105, 105, 105, 105, 105, 105, 106],
        ]
        ratio = 0.1
        for list_c in list_coords:
            list_t = [1000 + 0.012 * i for i in range(len(list_c))]
            expected_segments= {
                    VAL.WHOLE: (list_t, list_c),
                    VAL.MIDDLE: (list_t[1:9], list_c[1:9]),
                    VAL.BEGIN: (list_t[:1], list_c[:1]),
                    VAL.END: (list_t[9:], list_c[9:]),
            }
            self._test_get_segments(list_t, list_c, expected_segments, ratio)


if __name__ == '__main__':
  unittest.main()