<commit_before># -*- coding: utf-8 -*-
##
##
## This file is part of Indico.
## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
##
## Indico is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 3 of the
## License, or (at your option) any later version.
##
## Indico is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Indico; if not, see <http://www.gnu.org/licenses/>.

from collections import defaultdict, OrderedDict
from datetime import datetime

from sqlalchemy import Date, Time
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import cast
from werkzeug.datastructures import OrderedMultiDict

from indico.core.db import db
from indico.core.db.sqlalchemy.custom import static_array
from indico.core.db.sqlalchemy.custom.utcdatetime import UTCDateTime
from indico.core.db.sqlalchemy.util.queries import limit_groups
from indico.core.errors import NoReportError
from indico.modules.rb.models.reservation_edit_logs import ReservationEditLog
from indico.modules.rb.models.reservation_occurrences import ReservationOccurrence
from indico.modules.rb.models.room_nonbookable_periods import NonBookablePeriod
from indico.modules.rb.models.equipment import (ReservationEquipmentAssociation, EquipmentType,
                                                RoomEquipmentAssociation)
from indico.modules.rb.models.utils import unimplemented
from indico.modules.rb.notifications.reservations import (notify_confirmation, notify_cancellation,
                                                          notify_creation, notify_modification,
                                                          notify_rejection)
from indico.util.date_time import now_utc, format_date, format_time
from indico.util.i18n import _, N_
from indico.util.serializer import Serializer
from indico.util.string import return_ascii
from indico.util.struct.enum import IndicoEnum
from indico.web.flask.util import url_for
from MaKaC.common.Locators import Locator
from MaKaC.user import AvatarHolder


class ConflictingOccurrences(Exception):
    pass


class RepeatFrequency(int, IndicoEnum):
    NEVER = 0
    DAY = 1
    WEEK = 2
    MONTH = 3
    YEAR = 4


class RepeatMapping(object):
    _mapping = {
        (RepeatFrequency.NEVER, 0): (N_('Single reservation'), None, 'none'),
        (RepeatFrequency.DAY, 1): (N_('Repeat daily'), 0, 'daily'),
        (RepeatFrequency.WEEK, 1): (N_('Repeat once a week'), 1, 'weekly'),
        (RepeatFrequency.WEEK, 2): (N_('Repeat once every two week'), 2, 'everyTwoWeeks'),
        (RepeatFrequency.WEEK, 3): (N_('Repeat once every three week'), 3, 'everyThreeWeeks'),
        (RepeatFrequency.MONTH, 1): (N_('Repeat every month'), 4, 'monthly')
    }

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def getMessage(cls, repeat_frequency, repeat_interval):
        return cls._mapping[(repeat_frequency, repeat_interval)][0]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def getOldMapping(cls, repeat_frequency, repeat_interval):
        return cls._mapping[(repeat_frequency, repeat_interval)][1]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def get_short_name(cls, repeat_frequency, repeat_interval):
        # for the API
        return cls._mapping[(repeat_frequency, repeat_interval)][2]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unknown old repeatability'))
    def getNewMapping(cls, repeat):
        if repeat is None or repeat < 5:
            for k, (_, v, _) in cls._mapping.iteritems():
                if v == repeat:
                    return k
        else:
            raise KeyError('Undefined old repeat: {}'.format(repeat))

    @classmethod
    def getMapping(cls):
        return cls._mapping


class Reservation(Serializer, db.Model):
    __tablename__ = 'reservations'
    __public__ = []
    __calendar_public__ = [
        'id', ('booked_for_name', 'bookedForName'), ('booking_reason', 'reason'), ('details_url', 'bookingUrl')
    ]
    __api_public__ = [
        'id', ('start_dt', 'startDT'), ('end_dt', 'endDT'), 'repeat_frequency', 'repeat_interval',
        ('booked_for_name', 'bookedForName'), ('details_url', 'bookingUrl'), ('booking_reason', 'reason'),
        ('uses_vc', 'usesAVC'), ('needs_vc_assistance', 'needsAVCSupport'),
        'needs_assistance', ('is_accepted', 'isConfirmed'), ('is_valid', 'isValid'), 'is_cancelled',
        'is_rejected', ('location_name', 'location'), 'booked_for_user_email'
    ]

    @declared_attr
    def __table_args__(cls):
        return (db.Index('ix_reservations_start_dt_date', cast(cls.start_dt, Date)),
                db.Index('ix_reservations_end_dt_date', cast(cls.end_dt, Date)),
                db.Index('ix_reservations_start_dt_time', cast(cls.start_dt, Time)),
                db.Index('ix_reservations_end_dt_time', cast(cls.end_dt, Time)),
                {'schema': 'roombooking'})

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    created_dt = db.Column(
        UTCDateTime,
        nullable=False,
        default=now_utc
    )
    start_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    end_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    repeat_frequency = db.Column(
        db.SmallInteger,
        nullable=False,
        default=RepeatFrequency.NEVER
    )  # week, month, year, etc.
    repeat_interval = db.Column(
        db.SmallInteger,
        nullable=False,
        default=0
    )  # 1, 2, 3, etc.
    booked_for_id = db.Column(
        db.String
        # Must be nullable for legacy data :(
    )
    booked_for_name = db.Column(
        db.String,
        nullable=False
    )
    created_by_id = db.Column(
        db.String
        # Must be nullable for legacy data :(
    )
    room_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.rooms.id'),
        nullable=False,
        index=True
    )
    contact_email = db.Column(
        db.String,
        nullable=False
    )
    contact_phone = db.Column(
        db.String,
        nullable=False
    )
    is_accepted = db.Column(
        db.Boolean,
        nullable=False
    )
    is_cancelled = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_rejected = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    booking_reason = db.Column(
        db.Text,
        nullable=False
    )
    rejection_reason = db.Column(
        db.String
    )
    uses_vc = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    needs_vc_assistance = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    needs_assistance = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    event_id = db.Column(
        db.Integer,
        index=True
    )

    edit_logs = db.relationship(
        'ReservationEditLog',
        backref='reservation',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )
    occurrences = db.relationship(
        'ReservationOccurrence',
        backref='reservation',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )
    used_equipment = db.relationship(
        'EquipmentType',
        secondary=ReservationEquipmentAssociation,
        backref='reservations',
        lazy='dynamic'
    )

    @hybrid_property
    def is_archived(self):
        return self.end_dt < datetime.now()

    @hybrid_property
    def is_repeating(self):
        return self.repeat_frequency != RepeatFrequency.NEVER

    @hybrid_property
    def is_valid(self):
        return self.is_accepted and not (self.is_rejected or self.is_cancelled)

    @is_valid.expression
    def is_valid(self):
        return self.is_accepted & ~(self.is_rejected | self.is_cancelled)

    @return_ascii
    def __repr__(self):
        return u'<Reservation({0}, {1}, {2}, {3}, {4})>'.format(
            self.id,
            self.room_id,
            self.booked_for_name,
            self.start_dt,
            self.end_dt
        )

    @hybrid_property
    def is_pending(self):
        return not (self.is_accepted or self.is_rejected or self.is_cancelled)

    @is_pending.expression
    def is_pending(self):
        return ~(Reservation.is_accepted | Reservation.is_rejected | Reservation.is_cancelled)

    @property
    def location_name(self):
        return self.room.location_name

    @property
    def repetition(self):
        return self.repeat_frequency, self.repeat_interval

    @property
    def details_url(self):
        return url_for('rooms.roomBooking-bookingDetails', self, _external=True)

    @property
    def status_string(self):
        parts = []
        if self.is_valid:
            parts.append(_("Valid"))
        else:
            if self.is_cancelled:
                parts.append(_("Cancelled"))
            if self.is_rejected:
                parts.append(_("Rejected"))
            if not self.is_accepted:
                parts.append(_("Not confirmed"))
        if self.is_archived:
            parts.append(_("Archived"))
        else:
            parts.append(_("Live"))
        return ', '.join(parts)

    @property
    def event(self):
        from MaKaC.conference import ConferenceHolder

        return ConferenceHolder().getById(str(self.event_id))

    @event.setter
    def event(self, event):
        self.event_id = int(event.getId())

    def get_vc_equipment(self):
        vc_equipment = self.room.available_equipment \
                           .correlate(ReservationOccurrence) \
                           .with_entities(EquipmentType.id) \
                           .filter_by(name='Video conference') \
                           .as_scalar()
        return self.used_equipment.filter(EquipmentType.parent_id == vc_equipment)

    @staticmethod
    def get_with_data(*args, **kwargs):
        filters = kwargs.pop('filters', None)
        limit = kwargs.pop('limit', None)
        offset = kwargs.pop('offset', 0)
        order = kwargs.pop('order', Reservation.start_dt)
        limit_per_room = kwargs.pop('limit_per_room', False)
        occurs_on = kwargs.pop('occurs_on')
        if kwargs:
            raise ValueError('Unexpected kwargs: {}'.format(kwargs))

        query = Reservation.query.options(joinedload(Reservation.room))
        if filters:
            query = query.filter(*filters)
        if occurs_on:
            query = query.filter(
                Reservation.id.in_(db.session.query(ReservationOccurrence.reservation_id)
                                   .filter(ReservationOccurrence.date.in_(occurs_on),
                                           ReservationOccurrence.is_valid))
            )
        if limit_per_room and (limit or offset):
            query = limit_groups(query, Reservation, Reservation.room_id, order, limit, offset)

        query = query.order_by(order, Reservation.created_dt)

        if not limit_per_room:
            if limit:
                query = query.limit(limit)
            if offset:
                query = query.offset(offset)

        result = OrderedDict((r.id, {'reservation': r}) for r in query)

        if 'vc_equipment' in args:
            vc_id_subquery = db.session.query(EquipmentType.id) \
                .correlate(Reservation) \
                .filter_by(name='Video conference') \
                .join(RoomEquipmentAssociation) \
                .filter(RoomEquipmentAssociation.c.room_id == Reservation.room_id) \
                .as_scalar()

            # noinspection PyTypeChecker
            vc_equipment_data = dict(db.session.query(Reservation.id, static_array.array_agg(EquipmentType.name))
                                     .join(ReservationEquipmentAssociation, EquipmentType)
                                     .filter(Reservation.id.in_(result.iterkeys()))
                                     .filter(EquipmentType.parent_id == vc_id_subquery)
                                     .group_by(Reservation.id))

            for id_, data in result.iteritems():
                data['vc_equipment'] = vc_equipment_data.get(id_, ())

        if 'occurrences' in args:
            occurrence_data = OrderedMultiDict(db.session.query(ReservationOccurrence.reservation_id,
                                                                ReservationOccurrence)
                                               .filter(ReservationOccurrence.reservation_id.in_(result.iterkeys()))
                                               .order_by(ReservationOccurrence.start_dt))
            for id_, data in result.iteritems():
                data['occurrences'] = occurrence_data.getlist(id_)

        return result.values()

    @property
    def created_by_user(self):
        return AvatarHolder().getById(self.created_by_id) if self.created_by_id else None

    @created_by_user.setter
    def created_by_user(self, user):
        self.created_by_id = user.getId() if user else None

    @property
    def booked_for_user(self):
        return AvatarHolder().getById(self.booked_for_id) if self.booked_for_id else None

    @booked_for_user.setter
    def booked_for_user(self, user):
        self.booked_for_id = user.getId() if user else None

    @property
    def booked_for_user_email(self):
        user = self.booked_for_user
        return self.booked_for_user.getEmail() if user else None

    @property
    def contact_emails(self):
        return set(filter(None, map(unicode.strip, self.contact_email.split(u','))))

    def accept(self, user):
        self.is_accepted = True
        self.add_edit_log(ReservationEditLog(user_name=user.getFullName(), info=['Reservation accepted']))
        notify_confirmation(self)

        valid_occurrences = self.occurrences.filter(ReservationOccurrence.is_valid).all()
        pre_occurrences = ReservationOccurrence.find_overlapping_with(self.room, valid_occurrences, self.id).all()
        for occurrence in pre_occurrences:
            if not occurrence.is_valid:
                continue
            occurrence.reject(user, u'Rejected due to collision with a confirmed reservation')

    def cancel(self, user, reason=None, silent=False):
        self.is_cancelled = True
        self.rejection_reason = reason
        self.occurrences.filter_by(is_valid=True).update({'is_cancelled': True}, synchronize_session='fetch')
        if not silent:
            notify_cancellation(self)
            log_msg = u'Reservation cancelled: {}'.format(reason) if reason else 'Reservation cancelled'
            self.add_edit_log(ReservationEditLog(user_name=user.getFullName(), info=[log_msg]))

    def reject(self, user, reason, silent=False):
        self.is_rejected = True
        self.rejection_reason = reason
        self.occurrences.filter_by(is_valid=True).update({'is_rejected': True, 'rejection_reason': reason},
                                                         synchronize_session='fetch')
        if not silent:
            notify_rejection(self)
            log_msg = u'Reservation rejected: {}'.format(reason)<commit_msg>Refactor Reservation<commit_after># -*- coding: utf-8 -*-
##
##
## This file is part of Indico.
## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
##
## Indico is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 3 of the
## License, or (at your option) any later version.
##
## Indico is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Indico; if not, see <http://www.gnu.org/licenses/>.

from collections import defaultdict, OrderedDict
from datetime import datetime

from sqlalchemy import Date, Time
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import cast
from werkzeug.datastructures import OrderedMultiDict

from indico.core.db import db
from indico.core.db.sqlalchemy.custom import static_array
from indico.core.db.sqlalchemy.custom.utcdatetime import UTCDateTime
from indico.core.db.sqlalchemy.util.queries import limit_groups
from indico.core.errors import NoReportError
from indico.modules.rb.models.reservation_edit_logs import ReservationEditLog
from indico.modules.rb.models.reservation_occurrences import ReservationOccurrence
from indico.modules.rb.models.room_nonbookable_periods import NonBookablePeriod
from indico.modules.rb.models.equipment import (ReservationEquipmentAssociation, EquipmentType,
                                                RoomEquipmentAssociation)
from indico.modules.rb.models.utils import unimplemented
from indico.modules.rb.notifications.reservations import (notify_confirmation, notify_cancellation,
                                                          notify_creation, notify_modification,
                                                          notify_rejection)
from indico.util.date_time import now_utc, format_date, format_time
from indico.util.i18n import _, N_
from indico.util.serializer import Serializer
from indico.util.string import return_ascii
from indico.util.struct.enum import IndicoEnum
from indico.web.flask.util import url_for
from MaKaC.common.Locators import Locator
from MaKaC.user import AvatarHolder


class ConflictingOccurrences(Exception):
    pass


class RepeatFrequency(int, IndicoEnum):
    NEVER = 0
    DAY = 1
    WEEK = 2
    MONTH = 3
    YEAR = 4


class RepeatMapping(object):
    _mapping = {
        (RepeatFrequency.NEVER, 0): (N_('Single reservation'), None, 'none'),
        (RepeatFrequency.DAY, 1): (N_('Repeat daily'), 0, 'daily'),
        (RepeatFrequency.WEEK, 1): (N_('Repeat once a week'), 1, 'weekly'),
        (RepeatFrequency.WEEK, 2): (N_('Repeat once every two week'), 2, 'everyTwoWeeks'),
        (RepeatFrequency.WEEK, 3): (N_('Repeat once every three week'), 3, 'everyThreeWeeks'),
        (RepeatFrequency.MONTH, 1): (N_('Repeat every month'), 4, 'monthly')
    }

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def getMessage(cls, repeat_frequency, repeat_interval):
        return cls._mapping[(repeat_frequency, repeat_interval)][0]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def getOldMapping(cls, repeat_frequency, repeat_interval):
        return cls._mapping[(repeat_frequency, repeat_interval)][1]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unimplemented repetition pair'))
    def get_short_name(cls, repeat_frequency, repeat_interval):
        # for the API
        return cls._mapping[(repeat_frequency, repeat_interval)][2]

    @classmethod
    @unimplemented(exceptions=(KeyError,), message=_('Unknown old repeatability'))
    def getNewMapping(cls, repeat):
        if repeat is None or repeat < 5:
            for k, (_, v, _) in cls._mapping.iteritems():
                if v == repeat:
                    return k
        else:
            raise KeyError('Undefined old repeat: {}'.format(repeat))

    @classmethod
    def getMapping(cls):
        return cls._mapping


class Reservation(Serializer, db.Model):
    __tablename__ = 'reservations'
    __public__ = []
    __calendar_public__ = [
        'id', ('booked_for_name', 'bookedForName'), ('booking_reason', 'reason'), ('details_url', 'bookingUrl')
    ]
    __api_public__ = [
        'id', ('start_dt', 'startDT'), ('end_dt', 'endDT'), 'repeat_frequency', 'repeat_interval',
        ('booked_for_name', 'bookedForName'), ('details_url', 'bookingUrl'), ('booking_reason', 'reason'),
        ('uses_vc', 'usesAVC'), ('needs_vc_assistance', 'needsAVCSupport'),
        'needs_assistance', ('is_accepted', 'isConfirmed'), ('is_valid', 'isValid'), 'is_cancelled',
        'is_rejected', ('location_name', 'location'), 'booked_for_user_email'
    ]

    @declared_attr
    def __table_args__(cls):
        return (db.Index('ix_reservations_start_dt_date', cast(cls.start_dt, Date)),
                db.Index('ix_reservations_end_dt_date', cast(cls.end_dt, Date)),
                db.Index('ix_reservations_start_dt_time', cast(cls.start_dt, Time)),
                db.Index('ix_reservations_end_dt_time', cast(cls.end_dt, Time)),
                {'schema': 'roombooking'})

    id = db.Column(
        db.Integer,
        primary_key=True
    )
    created_dt = db.Column(
        UTCDateTime,
        nullable=False,
        default=now_utc
    )
    start_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    end_dt = db.Column(
        db.DateTime,
        nullable=False,
        index=True
    )
    repeat_frequency = db.Column(
        db.SmallInteger,
        nullable=False,
        default=RepeatFrequency.NEVER
    )  # week, month, year, etc.
    repeat_interval = db.Column(
        db.SmallInteger,
        nullable=False,
        default=0
    )  # 1, 2, 3, etc.
    booked_for_id = db.Column(
        db.String
        # Must be nullable for legacy data :(
    )
    booked_for_name = db.Column(
        db.String,
        nullable=False
    )
    created_by_id = db.Column(
        db.String
        # Must be nullable for legacy data :(
    )
    room_id = db.Column(
        db.Integer,
        db.ForeignKey('roombooking.rooms.id'),
        nullable=False,
        index=True
    )
    contact_email = db.Column(
        db.String,
        nullable=False
    )
    contact_phone = db.Column(
        db.String,
        nullable=False
    )
    is_accepted = db.Column(
        db.Boolean,
        nullable=False
    )
    is_cancelled = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    is_rejected = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    booking_reason = db.Column(
        db.Text,
        nullable=False
    )
    rejection_reason = db.Column(
        db.String
    )
    uses_vc = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    needs_vc_assistance = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    needs_assistance = db.Column(
        db.Boolean,
        nullable=False,
        default=False
    )
    event_id = db.Column(
        db.Integer,
        index=True
    )

    edit_logs = db.relationship(
        'ReservationEditLog',
        backref='reservation',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )
    occurrences = db.relationship(
        'ReservationOccurrence',
        backref='reservation',
        cascade='all, delete-orphan',
        lazy='dynamic'
    )
    used_equipment = db.relationship(
        'EquipmentType',
        secondary=ReservationEquipmentAssociation,
        backref='reservations',
        lazy='dynamic'
    )

    @hybrid_property
    def is_archived(self):
        return self.end_dt < datetime.now()

    @hybrid_property
    def is_pending(self):
        return not (self.is_accepted or self.is_rejected or self.is_cancelled)

    @is_pending.expression
    def is_pending(self):
        return ~(Reservation.is_accepted | Reservation.is_rejected | Reservation.is_cancelled)

    @hybrid_property
    def is_repeating(self):
        return self.repeat_frequency != RepeatFrequency.NEVER

    @hybrid_property
    def is_valid(self):
        return self.is_accepted and not (self.is_rejected or self.is_cancelled)

    @is_valid.expression
    def is_valid(self):
        return self.is_accepted & ~(self.is_rejected | self.is_cancelled)

    @property
    def booked_for_user(self):
        return AvatarHolder().getById(self.booked_for_id) if self.booked_for_id else None

    @booked_for_user.setter
    def booked_for_user(self, user):
        self.booked_for_id = user.getId()
        self.booked_for_name = user.getFullName()

    @property
    def booked_for_user_email(self):
        user = self.booked_for_user
        return self.booked_for_user.getEmail() if user else None

    @property
    def contact_emails(self):
        return set(filter(None, map(unicode.strip, self.contact_email.split(u','))))

    @property
    def details_url(self):
        return url_for('rooms.roomBooking-bookingDetails', self, _external=True)

    @property
    def location_name(self):
        return self.room.location_name

    @property
    def repetition(self):
        return self.repeat_frequency, self.repeat_interval

    @property
    def status_string(self):
        parts = []
        if self.is_valid:
            parts.append(_(u"Valid"))
        else:
            if self.is_cancelled:
                parts.append(_(u"Cancelled"))
            if self.is_rejected:
                parts.append(_(u"Rejected"))
            if not self.is_accepted:
                parts.append(_(u"Not confirmed"))
        if self.is_archived:
            parts.append(_(u"Archived"))
        else:
            parts.append(_(u"Live"))
        return u', '.join(map(unicode, parts))

    @return_ascii
    def __repr__(self):
        return u'<Reservation({0}, {1}, {2}, {3}, {4})>'.format(
            self.id,
            self.room_id,
            self.booked_for_name,
            self.start_dt,
            self.end_dt
        )

    @property
    def event(self):
        from MaKaC.conference import ConferenceHolder
        return ConferenceHolder().getById(str(self.event_id))

    @event.setter
    def event(self, event):
        self.event_id = int(event.getId())

    def get_vc_equipment(self):
        vc_equipment = self.room.available_equipment \
                           .correlate(ReservationOccurrence) \
                           .with_entities(EquipmentType.id) \
                           .filter_by(name='Video conference') \
                           .as_scalar()
        return self.used_equipment.filter(EquipmentType.parent_id == vc_equipment)

    @staticmethod
    def get_with_data(*args, **kwargs):
        filters = kwargs.pop('filters', None)
        limit = kwargs.pop('limit', None)
        offset = kwargs.pop('offset', 0)
        order = kwargs.pop('order', Reservation.start_dt)
        limit_per_room = kwargs.pop('limit_per_room', False)
        occurs_on = kwargs.pop('occurs_on')
        if kwargs:
            raise ValueError('Unexpected kwargs: {}'.format(kwargs))

        query = Reservation.query.options(joinedload(Reservation.room))
        if filters:
            query = query.filter(*filters)
        if occurs_on:
            query = query.filter(
                Reservation.id.in_(db.session.query(ReservationOccurrence.reservation_id)
                                   .filter(ReservationOccurrence.date.in_(occurs_on),
                                           ReservationOccurrence.is_valid))
            )
        if limit_per_room and (limit or offset):
            query = limit_groups(query, Reservation, Reservation.room_id, order, limit, offset)

        query = query.order_by(order, Reservation.created_dt)

        if not limit_per_room:
            if limit:
                query = query.limit(limit)
            if offset:
                query = query.offset(offset)

        result = OrderedDict((r.id, {'reservation': r}) for r in query)

        if 'vc_equipment' in args:
            vc_id_subquery = db.session.query(EquipmentType.id) \
                .correlate(Reservation) \
                .filter_by(name='Video conference') \
                .join(RoomEquipmentAssociation) \
                .filter(RoomEquipmentAssociation.c.room_id == Reservation.room_id) \
                .as_scalar()

            # noinspection PyTypeChecker
            vc_equipment_data = dict(db.session.query(Reservation.id, static_array.array_agg(EquipmentType.name))
                                     .join(ReservationEquipmentAssociation, EquipmentType)
                                     .filter(Reservation.id.in_(result.iterkeys()))
                                     .filter(EquipmentType.parent_id == vc_id_subquery)
                                     .group_by(Reservation.id))

            for id_, data in result.iteritems():
                data['vc_equipment'] = vc_equipment_data.get(id_, ())

        if 'occurrences' in args:
            occurrence_data = OrderedMultiDict(db.session.query(ReservationOccurrence.reservation_id,
                                                                ReservationOccurrence)
                                               .filter(ReservationOccurrence.reservation_id.in_(result.iterkeys()))
                                               .order_by(ReservationOccurrence.start_dt))
            for id_, data in result.iteritems():
                data['occurrences'] = occurrence_data.getlist(id_)

        return result.values()

    @property
    def created_by_user(self):
        return AvatarHolder().getById(self.created_by_id) if self.created_by_id else None

    @created_by_user.setter
    def created_by_user(self, user):
        self.created_by_id = user.getId() if user else None

    def accept(self, user):
        self.is_accepted = True
        self.add_edit_log(ReservationEditLog(user_name=user.getFullName(), info=['Reservation accepted']))
        notify_confirmation(self)

        valid_occurrences = self.occurrences.filter(ReservationOccurrence.is_valid).all()
        pre_occurrences = ReservationOccurrence.find_overlapping_with(self.room, valid_occurrences, self.id).all()
        for occurrence in pre_occurrences:
            if not occurrence.is_valid:
                continue
            occurrence.reject(user, u'Rejected due to collision with a confirmed reservation')

    def cancel(self, user, reason=None, silent=False):
        self.is_cancelled = True
        self.rejection_reason = reason
        self.occurrences.filter_by(is_valid=True).update({'is_cancelled': True}, synchronize_session='fetch')
        if not silent:
            notify_cancellation(self)
            log_msg = u'Reservation cancelled: {}'.format(reason) if reason else 'Reservation cancelled'
            self.add_edit_log(ReservationEditLog(user_name=user.getFullName(), info=[log_msg]))

    def reject(self, user, reason, silent=False):
        self.is_rejected = True
        self.rejection_reason = reason
        self.occurrences.filter_by(is_valid=True).update({'is_rejected': True, 'rejection_reason': reason},
                                                         synchronize_session='fetch')
        if not silent:
            notify_rejection(self)
            log_msg = u'Reservation rejected: {}'.format(reason)
{'fraction_non_alphanumeric': 0.10090785436371114, 'fraction_numerical': 0.0026571347230569704, 'mean_word_length': 2.4419161676646706, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1676359', 'n_tokens_mistral': 9508, 'n_tokens_neox': 9126, 'n_words': 1914}