import uuid
import itertools
import operator

import arrow

from collections import namedtuple

HEADERS = ('start', 'stop', 'project', 'id', 'updated_at')


class Frame(namedtuple('Frame', HEADERS)):
    def __new__(cls, start, stop, project, id, updated_at=None):
        if not isinstance(start, arrow.Arrow):
            start = arrow.get(start)

        if not isinstance(stop, arrow.Arrow):
            stop = arrow.get(stop)

        if updated_at is None:
            updated_at = arrow.utcnow()
        elif not isinstance(updated_at, arrow.Arrow):
            updated_at = arrow.get(updated_at)

        return super(Frame, cls).__new__(
            cls, start, stop, project, id, updated_at
        )

    def dump(self):
        start = self.start.timestamp
        stop = self.stop.timestamp
        updated_at = self.updated_at.timestamp

        return (start, stop, self.project, self.id, updated_at)

    @property
    def day(self):
        return self.start.floor('day')

    def __lt__(self, other):
        return self.start < other.start

    def __lte__(self, other):
        return self.start <= other.start

    def __gt__(self, other):
        return self.start > other.start

    def __gte__(self, other):
        return self.start >= other.start


class Span(object):
    def __init__(self, start, stop, timeframe='day'):
        self.timeframe = timeframe
        self.start = start.floor(self.timeframe)
        self.stop = stop.ceil(self.timeframe)

    def __contains__(self, frame):
        return frame.start >= self.start and frame.stop <= self.stop


class Frames(object):
    def __init__(self, frames=None):
        if not frames:
            frames = []

        rows = [Frame(*frame) for frame in frames]
        self._rows = rows

        self.changed = False

    def __len__(self):
        return len(self._rows)

    def __getitem__(self, key):
        if key in HEADERS:
            return tuple(self._get_col(key))
        elif isinstance(key, int):
            return self._rows[key]
        else:
            try:
                return self._rows[self['id'].index(key)]
            except ValueError:
                raise KeyError("Frame with id {} not found".format(key))

    def __setitem__(self, key, value):
        self.changed = True

        if isinstance(value, Frame):
            frame = value
        else:
            frame = self.new_frame(*value)

        if isinstance(key, int):
            self._rows[key] = frame
        else:
            try:
                self._rows[self['id'].index(key)] = frame
            except ValueError:
                frame._replace(id=key)
                self._rows.append(frame)

    def __delitem__(self, key):
        self.changed = True
        del self._rows[key]

    def _get_col(self, col):
        index = HEADERS.index(col)
        for row in self._rows:
            yield row[index]

    def add(self, *args, **kwargs):
        self.changed = True
        frame = self.new_frame(*args, **kwargs)
        self._rows.append(frame)
        return frame

    def new_frame(self, project, start, stop, id=None):
        if not id:
            id = uuid.uuid1().hex
        return Frame(start, stop, project, id)

    def dump(self):
        return tuple(frame.dump() for frame in self._rows)

    def for_project(self, name, subprojects=True):
        return (
            frame for frame in self._rows
            if frame.project == name or
               (subprojects and frame.project.startswith(name + '/'))
        )

    def span(self, start, stop):
        return Span(start, stop)

    def by_day(self, span):
        frames = (f for f in self._rows if f in span)
        return itertools.groupby(frames, operator.attrgetter('day'))

@@ -2037,37 +2037,16 @@
   else:%0A
-            try:%0A    
         
@@ -2075,130 +2075,31 @@
 self
-%5B'id'%5D.index(key)%5D%0A            except ValueError:%0A                raise KeyError(%22Frame with id %7B%7D not found%22.format
+._get_index_by_id
 (key)
-)
+%5D
 %0A%0A  
@@ -2360,32 +2360,75 @@
 e%0A        else:%0A
+            frame = frame._replace(id=key)%0A
             try:
@@ -2463,20 +2463,25 @@
 self
-%5B'id'%5D.index
+._get_index_by_id
 (key
@@ -2514,59 +2514,18 @@
 ept 
-Value
+Key
 Error:%0A
-                frame._replace(id=key)%0A
     
@@ -2649,16 +2649,275 @@
 ows%5Bkey%5D
+%0A    def _get_index_by_id(self, id):%0A        try:%0A            return next(%0A                i for i, v in enumerate(self%5B'id'%5D) if v.startswith(id)%0A            )%0A        except StopIteration:%0A            raise KeyError(%22Frame with id %7B%7D not found.%22.format(id))
 %0A%0A    de

Fix setting and saving frames by id