# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos.  If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================

import datetime

from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Float, String, Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql import and_

from eos.db import saveddata_meta, saveddata_session
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.fighter import fighters_table
from eos.db.saveddata.implant import fitImplants_table
from eos.db.saveddata.module import modules_table
from eos.effectHandlerHelpers import HandledDroneCargoList, HandledImplantList, HandledBoosterList, HandledModuleList, HandledProjectedDroneList, HandledProjectedModList
from eos.saveddata.booster import Booster
from eos.saveddata.cargo import Cargo
from eos.saveddata.character import Character
from eos.saveddata.damagePattern import DamagePattern
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit as es_Fit
from eos.saveddata.implant import Implant
from eos.saveddata.module import Module
from eos.saveddata.targetProfile import TargetProfile
from eos.saveddata.user import User


fits_table = Table("fits", saveddata_meta,
                   Column("ID", Integer, primary_key=True),
                   Column("ownerID", ForeignKey("users.ID"), nullable=True, index=True),
                   Column("shipID", Integer, nullable=False, index=True),
                   Column("name", String, nullable=False),
                   Column("timestamp", Integer, nullable=False),
                   Column("characterID", ForeignKey("characters.ID"), nullable=True),
                   Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
                   Column("builtinDamagePatternID", Integer, nullable=True),
                   Column("booster", Boolean, nullable=False, index=True, default=0),
                   Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
                   Column("builtinTargetResistsID", Integer, nullable=True),
                   Column("modeID", Integer, nullable=True),
                   Column("implantLocation", Integer, nullable=False),
                   Column("notes", String, nullable=True),
                   Column("ignoreRestrictions", Boolean, default=0),
                   Column("created", DateTime, nullable=True, default=datetime.datetime.now),
                   Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now),
                   Column("systemSecurity", Integer, nullable=True)
                   )

projectedFits_table = Table("projectedFits", saveddata_meta,
                            Column("sourceID", ForeignKey("fits.ID"), primary_key=True),
                            Column("victimID", ForeignKey("fits.ID"), primary_key=True),
                            Column("amount", Integer, nullable=False, default=1),
                            Column("active", Boolean, nullable=False, default=1),
                            Column("created", DateTime, nullable=True, default=datetime.datetime.now),
                            Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
                            Column("projectionRange", Float, nullable=True),
                            )

commandFits_table = Table("commandFits", saveddata_meta,
                          Column("boosterID", ForeignKey("fits.ID"), primary_key=True),
                          Column("boostedID", ForeignKey("fits.ID"), primary_key=True),
                          Column("active", Boolean, nullable=False, default=1),
                          Column("created", DateTime, nullable=True, default=datetime.datetime.now),
                          Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
                          )


class ProjectedFit:

    def __init__(self, sourceID, source_fit, amount=1, active=True):
        self.sourceID = sourceID
        self.source_fit = source_fit
        self.active = active
        self.__amount = amount

    @reconstructor
    def init(self):
        if self.source_fit.isInvalid:
            # Very rare for this to happen, but be prepared for it
            saveddata_session.delete(self.source_fit)
            saveddata_session.flush()
            saveddata_session.refresh(self.victim_fit)

    # We have a series of setters and getters here just in case someone
    # downgrades and screws up the table with NULL values
    @property
    def amount(self):
        return self.__amount or 1

    @amount.setter
    def amount(self, amount):
        self.__amount = amount

    def __repr__(self):
        return "ProjectedFit(sourceID={}, victimID={}, amount={}, active={}) at {}".format(
                self.sourceID, self.victimID, self.amount, self.active, hex(id(self))
        )


class CommandFit:
    def __init__(self, boosterID, booster_fit, active=True):
        self.boosterID = boosterID
        self.booster_fit = booster_fit
        self.active = active

    @reconstructor
    def init(self):
        if self.booster_fit.isInvalid:
            # Very rare for this to happen, but be prepared for it
            saveddata_session.delete(self.booster_fit)
            saveddata_session.flush()
            saveddata_session.refresh(self.boosted_fit)

    def __repr__(self):
        return "CommandFit(boosterID={}, boostedID={}, active={}) at {}".format(
                self.boosterID, self.boostedID, self.active, hex(id(self))
        )


es_Fit.projectedFitDict = association_proxy(
        "victimOf",  # look at the victimOf association...
        "source_fit",  # .. and return the source fits
        creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit)
)

es_Fit.commandFitDict = association_proxy(
        "boostedOf",  # look at the boostedOf association...
        "booster_fit",  # .. and return the booster fit
        creator=lambda boosterID, booster_fit: CommandFit(boosterID, booster_fit)
)


# These relationships are broken out so that we can easily access it in the events stuff
# We sometimes don't want particular relationships to cause a fit modified update (eg: projecting
# a fit onto another would 'modify' both fits unless the following relationship is ignored)
projectedFitSourceRel = relationship(
   ProjectedFit,
   primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
   backref='source_fit',
   collection_class=attribute_mapped_collection('victimID'),
   cascade='all, delete, delete-orphan')


boostedOntoRel = relationship(
   CommandFit,
   primaryjoin=commandFits_table.c.boosterID == fits_table.c.ID,
   backref='booster_fit',
   collection_class=attribute_mapped_collection('boostedID'),
   cascade='all, delete, delete-orphan')

mapper(es_Fit, fits_table,
       properties={
           "_Fit__modules": relation(
                   Module,
                   collection_class=HandledModuleList,
                   primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),  # noqa
                   order_by=modules_table.c.position,
                   cascade='all, delete, delete-orphan'),
           "_Fit__projectedModules": relation(
                   Module,
                   collection_class=HandledProjectedModList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),  # noqa
           "owner": relation(
                   User,
                   backref="fits"),
           "itemID": fits_table.c.shipID,
           "shipID": fits_table.c.shipID,
           "_Fit__boosters": relation(
                   Booster,
                   collection_class=HandledBoosterList,
                   cascade='all, delete, delete-orphan',
                   backref='owner',
                   single_parent=True),
           "_Fit__drones": relation(
                   Drone,
                   collection_class=HandledDroneCargoList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),  # noqa
           "_Fit__fighters": relation(
                   Fighter,
                   collection_class=HandledDroneCargoList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == False)),  # noqa
           "_Fit__cargo": relation(
                   Cargo,
                   collection_class=HandledDroneCargoList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)),
           "_Fit__projectedDrones": relation(
                   Drone,
                   collection_class=HandledProjectedDroneList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),  # noqa
           "_Fit__projectedFighters": relation(
                   Fighter,
                   collection_class=HandledProjectedDroneList,
                   cascade='all, delete, delete-orphan',
                   single_parent=True,
                   primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == True)),  # noqa
           "_Fit__implants": relation(
                   Implant,
                   collection_class=HandledImplantList,
                   cascade='all, delete, delete-orphan',
                   backref='owner',
                   single_parent=True,
                   primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID,
                   secondaryjoin=fitImplants_table.c.implantID == Implant.ID,
                   secondary=fitImplants_table),
           "_Fit__character": relation(
                   Character,
                   backref="fits"),
           "_Fit__userDamagePattern": relation(DamagePattern),
           "_Fit__builtinDamagePatternID": fits_table.c.builtinDamagePatternID,
           "_Fit__userTargetProfile": relation(TargetProfile),
           "_Fit__builtinTargetProfileID": fits_table.c.builtinTargetResistsID,
           "projectedOnto": projectedFitSourceRel,
           "victimOf": relationship(
                   ProjectedFit,
                   primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID,
                   backref='victim_fit',
                   collection_class=attribute_mapped_collection('sourceID'),
                   cascade='all, delete, delete-orphan'),
           "boostedOnto": boostedOntoRel,
           "boostedOf": relationship(
                   CommandFit,
                   primaryjoin=fits_table.c.ID == commandFits_table.c.boostedID,
                   backref='boosted_fit',
                   collection_class=attribute_mapped_collection('boosterID'),
                   cascade='all, delete, delete-orphan'),
       }
)

mapper(ProjectedFit, projectedFits_table,
   properties={
       "_ProjectedFit__amount": projectedFits_table.c.amount,
   }
)

mapper(CommandFit, commandFits_table)
