#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime

import distill
from distill.segmentation.segmentation_error import SegmentationError

class Segments():
    """
    A list of Segment objects.
    """

    ##############################################
    # IMPLEMENTATION OF PYTHON DEFINED FUNCTIONS #
    ##############################################

    def __init__(self, segments=[]):
        """
        Segments initialization function.

        :param segments ([]): An optional List of Segment objects to be included in the Segments abstraction.
        """
        self.segments = segments

    def __iter__(self):
        """
        Allows Segments to be iterable.
        """
        return iter(self.segments)

    def __len__(self):
        """
        Allows Segments to return a length.
        """
        return len(self.segments)

    def __getitem__(self, item):
        """
        Allows Segments to be subscriptable by segment name or numeric index.
        """
        if isinstance(item, str):
            segment_names = [segment.get_segment_name() for segment in self.segments]
            if item in segment_names:
                index = segment_names.index(item)
                return self.segments[index]
        else:
            return self.segments[item]

    def __setitem__(self, key, value):
        """
        Allows subscripts to be used to set items.
        """
        if not isinstance(value, distill.Segment):
            raise TypeError("Segments objects can only hold Segment objects.")

        if isinstance(key, str):
            if not value.segment_name == key:
                raise SegmentationError("Segment name in subscript must match the segment name of the Segment object.")

            segment_names = [segment.get_segment_name() for segment in self.segments]
            if key in segment_names:
                index = segment_names.index(key)
                self.segments[index] = value
            else:
                self.segments.append(value)
        elif isinstance(key, int):
            if key < len(self.segments):
                self.segments[key] = value
            else:
                raise SegmentationError("Index provided goes beyond the length of the underlying list of Segment objects.")


    def __str__(self):
        """
        Creates a readable string for Segments.
        """
        result = "Segments: [\n"
        for segment in self.segments:
            result += str(segment) + "\n"
        result += "]"
        return result

    ############################
    # DATA STRUCTURE FUNCTIONS #
    ############################

    def get_segment_list(self):
        """
        Returns a list of Segment objects in Segments.

        :return: A list of segment objects.
        """
        return self.segments

    def get_segment_name_dict(self):
        """
        Returns a dictionary of segment_name to Segment objects based on the key parameter.  Note that segment names
        must be unique.
        """
        result = {}
        for segment in self.segments:
            if segment.get_segment_name() in result:
                raise SegmentationError("Segment names must be unique")
            else:
                result[segment.get_segment_name()] = segment
        return result

    ######################
    # SEGMENTS FILTERING #
    ######################

    def get_num_logs(self, num_logs):
        """
        Returns a new Segments object only including segments with the specified number of logs.
        :param num_logs: The minimum number of logs (inclusive) necessary to be included in the new Segments object.
        :return: A new Segments object that contains Segment objects with at least the specified number of logs.
        """
        segments = [segment for segment in self.segments if segment.num_logs >= num_logs]
        return Segments(segments)

    def get_segments_before(self, time):
        """
        Returns a new Segments object only including segments that have end times before the indicated time.

        :param time: An integer or datetime object that represents the time for which Segment end times should be before.
        :return: A new Segments object that contains Segment objects that have end times prior to the time indicated.
        """
        if not isinstance(time, int) and not isinstance(time, datetime.datetime):
            raise TypeError('Time must be an integer or datetime object.')

        segments = [segment for segment in self.segments if segment.start_end_val[1] < time]
        return Segments(segments)

    def get_segments_after(self, time):
        """
        Returns a new Segments object only including segments that have start times after the indicated time.

        :param time: An integer or datetime object that represents the time for which Segment start times should be after.
        :return: A new Segments object that contains Segment objects that have start times after the time indicated.
        """
        if not isinstance(time, int) and not isinstance(time, datetime.datetime):
            raise TypeError('Time must be an integer or datetime object.')

        segments = [segment for segment in self.segments if segment.start_end_val[0] > time]

        return Segments(segments)

    def get_segments_of_type(self, segment_type):
        """
        Returns a new Segments object that includes Segment objects of a specified type.

        :param segment_type: The type of Segment objects that should be included.
        :return: A new Segments object that contains Segment objects of the specified type.
        """
        if not isinstance(segment_type, distill.Segment_Type):
            raise TypeError("Given segment_type: " + str(segment_type) + " is not a valid segment type.")

        segments = [segment for segment in self.segments if segment.segment_type == segment_type]
        return Segments(segments)

    #################################
    # SEGMENT ADDITION AND DELETION #
    #################################
    def append(self, item):
        """
        Adds a Segment object to the Segments object.
        :param item: The Segment object to add.
        """
        if not isinstance(item, distill.Segment):
            raise TypeError("Only Segment objects can be added to a Segments object.")

        self.segments.append(item)

    def append_segments(self, segments):
        """
        Adds all Segment objects in the given Segments object to the current Segments object.
        :param segments: A Segments object to append to the calling Segments object.
        """
        if not isinstance(segments, distill.Segments):
            raise TypeError("Only Segments objects can be appended with append_segments.")

        self.segments.extend(segments.segments)

    def delete(self, segment_name):
        """
        Deletes the Segment object with the given segment_name.
        :param segment_name: The name of the Segment to delete.
        """
        segment_names = [segment.get_segment_name() for segment in self.segments]
        if segment_name in segment_names:
            index = segment_names.index(segment_name)
            segment = self.segments[index]
            self.segments.remove(segment)
        else:
            raise SegmentationError("No Segment objects with given segment name: " + segment_name)

