"""WSGI interface (see PEP 333 and 3333).

Note that WSGI environ keys and values are 'native strings'; that is,
whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
it's a unicode string. But PEP 3333 says: "even if Python's str type is
actually Unicode "under the hood", the content of native strings must
still be translatable to bytes via the Latin-1 encoding!"
"""

import sys as _sys

import cherrypy as _cherrypy
from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
from cherrypy import _cperror
from cherrypy.lib import httputil
from cherrypy.lib import is_closable_iterator

def downgrade_wsgi_ux_to_1x(environ):
    """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ.
    """
    env1x = {}

    url_encoding = environ[ntou('wsgi.url_encoding')]
    for k, v in list(environ.items()):
        if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
            v = v.encode(url_encoding)
        elif isinstance(v, unicodestr):
            v = v.encode('ISO-8859-1')
        env1x[k.encode('ISO-8859-1')] = v

    return env1x

class VirtualHost(object):

    """Select a different WSGI application based on the Host header.

    This can be useful when running multiple sites within one CP server.
    It allows several domains to point to different applications. For example::

        root = Root()
        RootApp = cherrypy.Application(root)
        Domain2App = cherrypy.Application(root)
        SecureApp = cherrypy.Application(Secure())

        vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
            domains={'www.domain2.example': Domain2App,
                     'www.domain2.example:443': SecureApp,
                     })

        cherrypy.tree.graft(vhost)
    """
    default = None
    """Required. The default WSGI application."""

    use_x_forwarded_host = True
    """If True (the default), any "X-Forwarded-Host"
    request header will be used instead of the "Host" header. This
    is commonly added by HTTP servers (such as Apache) when proxying."""

    domains = {}
    """A dict of {host header value: application} pairs.
    The incoming "Host" request header is looked up in this dict,
    and, if a match is found, the corresponding WSGI application
    will be called instead of the default. Note that you often need
    separate entries for "example.com" and "www.example.com".
    In addition, "Host" headers may contain the port number.
    """

    def __init__(self, default, domains=None, use_x_forwarded_host=True):
        self.default = default
        self.domains = domains or {}
        self.use_x_forwarded_host = use_x_forwarded_host

    def __call__(self, environ, start_response):
        domain = environ.get('HTTP_HOST', '')
        if self.use_x_forwarded_host:
            domain = environ.get("HTTP_X_FORWARDED_HOST", domain)

        nextapp = self.domains.get(domain)
        if nextapp is None:
            nextapp = self.default
        return nextapp(environ, start_response)

class InternalRedirector(object):

    """WSGI middleware that handles raised cherrypy.InternalRedirect."""

    def __init__(self, nextapp, recursive=False):
        self.nextapp = nextapp
        self.recursive = recursive

    def __call__(self, environ, start_response):
        redirections = []
        while True:
            environ = environ.copy()
            try:
                return self.nextapp(environ, start_response)
            except _cherrypy.InternalRedirect:
                ir = _sys.exc_info()[1]
                sn = environ.get('SCRIPT_NAME', '')
                path = environ.get('PATH_INFO', '')
                qs = environ.get('QUERY_STRING', '')

                # Add the *previous* path_info + qs to redirections.
                old_uri = sn + path
                if qs:
                    old_uri += "?" + qs
                redirections.append(old_uri)

                if not self.recursive:
                    # Check to see if the new URI has been redirected to
                    # already
                    new_uri = sn + ir.path
                    if ir.query_string:
                        new_uri += "?" + ir.query_string
                    if new_uri in redirections:
                        ir.request.close()
                        raise RuntimeError("InternalRedirector visited the "
                                           "same URL twice: %r" % new_uri)

                # Munge the environment and try again.
                environ['REQUEST_METHOD'] = "GET"
                environ['PATH_INFO'] = ir.path
                environ['QUERY_STRING'] = ir.query_string
                environ['wsgi.input'] = BytesIO()
                environ['CONTENT_LENGTH'] = "0"
                environ['cherrypy.previous_request'] = ir.request

class ExceptionTrapper(object):

    """WSGI middleware that traps exceptions."""

    def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
        self.nextapp = nextapp
        self.throws = throws

    def __call__(self, environ, start_response):
        return _TrappedResponse(
            self.nextapp,
            environ,
            start_response,
            self.throws
        )

class _TrappedResponse(object):

    response = iter([])

    def __init__(self, nextapp, environ, start_response, throws):
        self.nextapp = nextapp
        self.environ = environ
        self.start_response = start_response
        self.throws = throws
        self.started_response = False
        self.response = self.trap(
            self.nextapp, self.environ, self.start_response)
        self.iter_response = iter(self.response)

    def __iter__(self):
        self.started_response = True
        return self

    def __next__(self):
        return self.trap(next, self.iter_response)

    # todo: https://pythonhosted.org/six/#six.Iterator
    if not py3k:
        next = __next__

    def close(self):
        if hasattr(self.response, 'close'):
            self.response.close()

    def trap(self, func, *args, **kwargs):
        try:
            return func(*args, **kwargs)
        except self.throws:
            raise
        except StopIteration:
            raise
        except:
            tb = _cperror.format_exc()
            #print('trapped (started %s):' % self.started_response, tb)
            _cherrypy.log(tb, severity=40)
            if not _cherrypy.request.show_tracebacks:
                tb = ""
            s, h, b = _cperror.bare_error(tb)
            if py3k:
                # What fun.
                s = s.decode('ISO-8859-1')
                h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
                     for k, v in h]
            if self.started_response:
                # Empty our iterable (so future calls raise StopIteration)
                self.iter_response = iter([])
            else:
                self.iter_response = iter(b)

            try:
                self.start_response(s, h, _sys.exc_info())
            except:
                # "The application must not trap any exceptions raised by
                # start_response, if it called start_response with exc_info.
                # Instead, it should allow such exceptions to propagate
                # back to the server or gateway."
                # But we still log and call close() to clean up ourselves.
                _cherrypy.log(traceback=True, severity=40)
                raise

            if self.started_response:
                return ntob("").join(b)
            else:
                return b

#                           WSGI-to-CP Adapter                           #

class AppResponse(object):

    """WSGI response iterable for CherryPy applications."""

    def __init__(self, environ, start_response, cpapp):
        self.cpapp = cpapp
        try:
            if not py3k:
                if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
                    environ = downgrade_wsgi_ux_to_1x(environ)
            self.environ = environ
            self.run()

            r = _cherrypy.serving.response

            outstatus = r.output_status
            if not isinstance(outstatus, bytestr):
                raise TypeError("response.output_status is not a byte string.")

            outheaders = []
            for k, v in r.header_list:
                if not isinstance(k, bytestr):
                    raise TypeError(
                        "response.header_list key %r is not a byte string." %
                        k)
                if not isinstance(v, bytestr):
                    raise TypeError(
                        "response.header_list value %r is not a byte string." %
                        v)
                outheaders.append((k, v))

            if py3k:
                # According to PEP 3333, when using Python 3, the response
                # status and headers must be bytes masquerading as unicode;
                # that is, they must be of type "str" but are restricted to
                # code points in the "latin-1" set.
                outstatus = outstatus.decode('ISO-8859-1')
                outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
                              for k, v in outheaders]

            self.iter_response = iter(r.body)
            self.write = start_response(outstatus, outheaders)
        except:
            self.close()
            raise

    def __iter__(self):
        return self

    def __next__(self):
        return next(self.iter_response)

    # todo: https://pythonhosted.org/six/#six.Iterator
    if not py3k:
        next = __next__

    def close(self):
        """Close and de-reference the current request and response. (Core)"""
        streaming = _cherrypy.serving.response.stream
        self.cpapp.release_serving()

        # We avoid the expense of examining the iterator to see if it's
        # closable unless we are streaming the response, as that's the
        # only situation where we are going to have an iterator which
        # may not have been exhausted yet.


import json
from math import log
from numbers import Number

from logbook import Logger

import eos.db
from config import getVersion
from service.fit import Fit
from service.market import Market
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
from service.const import PortEftRigSize
from eos.saveddata.module import Module
from eos.saveddata.drone import Drone
from eos.effectHandlerHelpers import HandledList
from eos.db import gamedata_session, getCategory, getAttributeInfo, getGroup
from eos.gamedata import Attribute, Effect, Group, Item, ItemEffect
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from gui.fitCommands.calc.module.localAdd import CalcAddLocalModuleCommand
from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
from gui.fitCommands.helpers import ModuleInfo

pyfalog = Logger(__name__)
pyfaVersion = getVersion()

class EfsPort:
    wepTestSet = {}
    version = 0.04

    @staticmethod
    def attrDirectMap(values, target, source):
        for val in values:
            target[val] = source.getModifiedItemAttr(val)

    @staticmethod
    def getT2MwdSpeed(fit, sFit):
        fitID = fit.ID
        propID = None
        shipHasMedSlots = fit.ship.getModifiedItemAttr("medSlots") > 0
        shipPower = fit.ship.getModifiedItemAttr("powerOutput")
        # Monitors have a 99% reduction to prop mod power requirements
        if fit.ship.name == "Monitor":
            shipPower *= 100
        rigSize = fit.ship.getModifiedItemAttr("rigSize")
        if not shipHasMedSlots:
            return None

        filterVal = Item.groupID == getGroup("Propulsion Module").ID
        propMods = gamedata_session.query(Item).options().filter(filterVal).all()
        mapPropData = lambda propName: \
                      next(map(lambda propMod: {"id": propMod.typeID, "powerReq": propMod.attributes["power"].value},
                               (filter(lambda mod: mod.name == propName, propMods))))
        mwd5mn = mapPropData("5MN Microwarpdrive II")
        mwd50mn = mapPropData("50MN Microwarpdrive II")
        mwd500mn = mapPropData("500MN Microwarpdrive II")
        mwd50000mn = mapPropData("50000MN Microwarpdrive II")
        if rigSize == PortEftRigSize.SMALL or rigSize is None:
            propID = mwd5mn["id"] if shipPower > mwd5mn["powerReq"] else None
        elif rigSize == PortEftRigSize.MEDIUM:
            propID = mwd50mn["id"] if shipPower > mwd50mn["powerReq"] else mwd5mn["id"]
        elif rigSize == PortEftRigSize.LARGE:
            propID = mwd500mn["id"] if shipPower > mwd500mn["powerReq"] else mwd50mn["id"]
        elif rigSize == PortEftRigSize.CAPITAL:
            propID = mwd50000mn["id"] if shipPower > mwd50000mn["powerReq"] else mwd500mn["id"]

        if propID is None:
            return None
        cmd = CalcAddLocalModuleCommand(fitID, ModuleInfo(itemID=propID))
        cmd.Do()
        if cmd.needsGuiRecalc:
            sFit.recalc(fit)
        fit = eos.db.getFit(fitID)
        mwdPropSpeed = fit.maxSpeed
        mwdPosition = list(filter(lambda mod: mod.item and mod.item.ID == propID, fit.modules))[0].position
        cmd = CalcRemoveLocalModulesCommand(fitID, [mwdPosition])
        cmd.Do()
        if cmd.needsGuiRecalc:
            sFit.recalc(fit)
        fit = eos.db.getFit(fitID)
        return mwdPropSpeed

    @staticmethod
    def getPropData(fit, sFit):
        propMods = filter(lambda mod: mod.item and mod.item.group.name == "Propulsion Module", fit.modules)
        activePropWBloomFilter = lambda mod: mod.state > 0 and "signatureRadiusBonus" in mod.item.attributes
        propWithBloom = next(filter(activePropWBloomFilter, propMods), None)
        if propWithBloom is not None:
            oldPropState = propWithBloom.state
            propWithBloom.state = FittingModuleState.ONLINE
            sFit.recalc(fit)
            sp = fit.maxSpeed
            sig = fit.ship.getModifiedItemAttr("signatureRadius")
            propWithBloom.state = oldPropState
            sFit.recalc(fit)
            return {"usingMWD": True, "unpropedSpeed": sp, "unpropedSig": sig}
        return {
            "usingMWD": False,
            "unpropedSpeed": fit.maxSpeed,
            "unpropedSig": fit.ship.getModifiedItemAttr("signatureRadius")
        }

    @staticmethod
    def getModsInGroups(fit, modGroupNames):
        matchingMods = list(filter(lambda mod: mod.item and mod.item.group.name in modGroupNames, fit.modules))
        # Sort mods to prevent the order needlessly changing as pyfa updates.
        matchingMods.sort(key=lambda mod: mod.item.ID)
        matchingMods.sort(key=lambda mod: mod.item.group.ID)
        return matchingMods

    # Note this also includes data for any cap boosters as they "repair" cap.
    @staticmethod
    def getRepairData(fit, sFit):
        modGroupNames = [
            "Shield Booster", "Armor Repair Unit",
            "Ancillary Shield Booster", "Ancillary Armor Repairer",
            "Hull Repair Unit", "Capacitor Booster",
        ]
        repairMods = EfsPort.getModsInGroups(fit, modGroupNames)
        repairs = [];
        for mod in repairMods:
            stats = {}
            EfsPort.attrDirectMap(["duration", "capacitorNeed"], stats, mod)
            if mod.item.group.name in ["Armor Repair Unit", "Ancillary Armor Repairer"]:
                stats["type"] = "Armor Repairer"
                EfsPort.attrDirectMap(["armorDamageAmount"], stats, mod)
                if mod.item.group.name == "Ancillary Armor Repairer":
                    stats["numShots"] = mod.numShots
                    EfsPort.attrDirectMap(["reloadTime", "chargedArmorDamageMultiplier"], stats, mod)
            elif mod.item.group.name in ["Shield Booster", "Ancillary Shield Booster"]:
                stats["type"] = "Shield Booster"
                EfsPort.attrDirectMap(["shieldBonus"], stats, mod)
                if mod.item.group.name == "Ancillary Shield Booster":
                    stats["numShots"] = mod.numShots
                    EfsPort.attrDirectMap(["reloadTime"], stats, mod)
                    c = mod.charge
                    if c:
                        sFit.recalc(fit)
                        CalcChangeModuleChargesCommand(
                            fit.ID,
                            projected=False,
                            chargeMap={mod.position: None},
                            recalc=False).Do()
                        sFit.recalc(fit)
                        stats["unloadedCapacitorNeed"] = mod.getModifiedItemAttr("capacitorNeed")
                        CalcChangeModuleChargesCommand(
                            fit.ID,
                            projected=False,
                            chargeMap={mod.position: c.typeID},
                            recalc=False).Do()
                        sFit.recalc(fit)
            elif mod.item.group.name == "Capacitor Booster":
                # The capacitorNeed is negative, which provides the boost.
                stats["type"] = "Capacitor Booster"
                stats["numShots"] = mod.numShots
                EfsPort.attrDirectMap(["reloadTime"], stats, mod)
            repairs.append(stats)
        return repairs

    @staticmethod
    def getOutgoingProjectionData(fit):
        # This is a subset of module groups capable of projection and a superset of those currently used by efs
        modGroupNames = [
            "Remote Shield Booster", "Warp Scrambler", "Stasis Web", "Remote Capacitor Transmitter",
            "Energy Nosferatu", "Energy Neutralizer", "Burst Jammer", "ECM", "Sensor Dampener",
            "Weapon Disruptor", "Remote Armor Repairer", "Target Painter", "Remote Hull Repairer",
            "Burst Projectors", "Warp Disrupt Field Generator", "Armor Resistance Shift Hardener",
            "Target Breaker", "Micro Jump Drive", "Ship Modifiers", "Stasis Grappler",
            "Ancillary Remote Shield Booster", "Ancillary Remote Armor Repairer",
            "Titan Phenomena Generator", "Non-Repeating Hardeners", "Mutadaptive Remote Armor Repairer"
        ]
        projectedMods = EfsPort.getModsInGroups(fit, modGroupNames)
        projections = []
        for mod in projectedMods:
            maxRangeDefault = 0
            falloffDefault = 0
            stats = {}
            if mod.item.group.name in ["Stasis Web", "Stasis Grappler"]:
                stats["type"] = "Stasis Web"
                stats["optimal"] = mod.getModifiedItemAttr("maxRange")
                EfsPort.attrDirectMap(["duration", "speedFactor"], stats, mod)
            elif mod.item.group.name == "Weapon Disruptor":
                stats["type"] = "Weapon Disruptor"
                stats["optimal"] = mod.getModifiedItemAttr("maxRange")
                stats["falloff"] = mod.getModifiedItemAttr("falloffEffectiveness")
                EfsPort.attrDirectMap([
                    "trackingSpeedBonus", "maxRangeBonus", "falloffBonus", "aoeCloudSizeBonus",
                    "aoeVelocityBonus", "missileVelocityBonus", "explosionDelayBonus"
                ], stats, mod)
            elif mod.item.group.name == "Energy Nosferatu":
                stats["type"] = "Energy Nosferatu"
                EfsPort.attrDirectMap(["powerTransferAmount", "energyNeutralizerSignatureResolution"], stats, mod)
            elif mod.item.group.name == "Energy Neutralizer":
                stats["type"] = "Energy Neutralizer"
                EfsPort.attrDirectMap([
                    "energyNeutralizerSignatureResolution", "entityCapacitorLevelModifierSmall",
                    "entityCapacitorLevelModifierMedium", "entityCapacitorLevelModifierLarge",
                    "energyNeutralizerAmount"
                ], stats, mod)
            elif mod.item.group.name in ["Remote Shield Booster", "Ancillary Remote Shield Booster"]:
                stats["type"] = "Remote Shield Booster"
                EfsPort.attrDirectMap(["shieldBonus"], stats, mod)
            elif mod.item.group.name in [
                    "Remote Armor Repairer", "Ancillary Remote Armor Repairer", "Mutadaptive Remote Armor Repairer"
            ]:
                stats["type"] = "Remote Armor Repairer"
                EfsPort.attrDirectMap(["armorDamageAmount"], stats, mod)
            elif mod.item.group.name == "Warp Scrambler":
                stats["type"] = "Warp Scrambler"
                EfsPort.attrDirectMap(["activationBlockedStrenght", "warpScrambleStrength"], stats, mod)
            elif mod.item.group.name == "Warp Disrupt Field Generator":
                maxRangeDefault = mod.getModifiedItemAttr("warpScrambleRange")
                stats["type"] = "Warp Scrambler"
                EfsPort.attrDirectMap(["activationBlockedStrenght", "warpScrambleStrength"], stats, mod)
                if maxRangeDefault >= 30000:
                    # We want this to be 0 for disruption scripts as we have no other way to tell scrams from points.
                    stats["activationBlockedStrenght"] = 0
            elif mod.item.group.name == "Target Painter":
                stats["type"] = "Target Painter"
                EfsPort.attrDirectMap(["signatureRadiusBonus"], stats, mod)
            elif mod.item.group.name == "Sensor Dampener":
                stats["type"] = "Sensor Dampener"
                EfsPort.attrDirectMap(["maxTargetRangeBonus", "scanResolutionBonus"], stats, mod)
            elif mod.item.group.name == "ECM":
                stats["type"] = "ECM"
                EfsPort.attrDirectMap([
                    "scanGravimetricStrengthBonus", "scanMagnetometricStrengthBonus",
                    "scanRadarStrengthBonus", "scanLadarStrengthBonus",
                ], stats, mod)
            elif mod.item.group.name == "Burst Jammer":
                stats["type"] = "Burst Jammer"
                maxRangeDefault = mod.getModifiedItemAttr("ecmBurstRange")
                EfsPort.attrDirectMap([
                    "scanGravimetricStrengthBonus", "scanMagnetometricStrengthBonus",
                    "scanRadarStrengthBonus", "scanLadarStrengthBonus",
                ], stats, mod)
            elif mod.item.group.name == "Micro Jump Drive":
                stats["type"] = "Micro Jump Drive"
                EfsPort.attrDirectMap(["moduleReactivationDelay"], stats, mod)
            else:
                pyfalog.error("Projected module {0} lacks efs export implementation".format(mod.item.name))
            if mod.getModifiedItemAttr("maxRange", None) is None:
                pyfalog.error("Projected module {0} has no maxRange".format(mod.item.name))
            stats["optimal"] = mod.getModifiedItemAttr("maxRange", maxRangeDefault)
            stats["falloff"] = mod.getModifiedItemAttr("falloffEffectiveness", falloffDefault)
            EfsPort.attrDirectMap(["duration", "capacitorNeed"], stats, mod)
            projections.append(stats)
        return projections

    # Note that unless padTypeIDs is True all 0s will be removed from modTypeIDs in the return.
    # They always are added initially for the sake of brevity, as this option may not be retained long term.
    @staticmethod
    def getModuleInfo(fit, padTypeIDs=False):
        moduleNames = []
        modTypeIDs = []
        moduleNameSets = {FittingSlot.LOW: [], FittingSlot.MED: [], FittingSlot.HIGH: [], FittingSlot.RIG: [], FittingSlot.SUBSYSTEM: []}
        modTypeIDSets = {FittingSlot.LOW: [], FittingSlot.MED: [], FittingSlot.HIGH: [], FittingSlot.RIG: [], FittingSlot.SUBSYSTEM: []}
        for mod in fit.modules:
            try:
                if mod.item is not None:
                    if mod.charge is not None:
                        modTypeIDSets[mod.slot].append([mod.item.typeID, mod.charge.typeID])
                        moduleNameSets[mod.slot].append(mod.item.name + ":  " + mod.charge.name)
                    else:
                        modTypeIDSets[mod.slot].append(mod.item.typeID)
                        moduleNameSets[mod.slot].append(mod.item.name)
                else:
                    modTypeIDSets[mod.slot].append(0)
                    moduleNameSets[mod.slot].append("Empty Slot")
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                pyfalog.error("Could not find name for module {0}".format(vars(mod)))

        for modInfo in [
            ["High Slots:"], moduleNameSets[FittingSlot.HIGH], ["", "Med Slots:"], moduleNameSets[FittingSlot.MED],
            ["", "Low Slots:"], moduleNameSets[FittingSlot.LOW], ["", "Rig Slots:"], moduleNameSets[FittingSlot.RIG]
        ]:
            moduleNames.extend(modInfo)
        if len(moduleNameSets[FittingSlot.SUBSYSTEM]) > 0:
            moduleNames.extend(["", "Subsystems:"])
            moduleNames.extend(moduleNameSets[FittingSlot.SUBSYSTEM])

        for slotType in [FittingSlot.HIGH, FittingSlot.MED, FittingSlot.LOW, FittingSlot.RIG, FittingSlot.SUBSYSTEM]:
            if slotType is not FittingSlot.SUBSYSTEM or len(modTypeIDSets[slotType]) > 0:
                modTypeIDs.extend([0, 0] if slotType is not FittingSlot.HIGH else [0])
                modTypeIDs.extend(modTypeIDSets[slotType])

        droneNames = []
        droneIDs = []
        fighterNames = []
        fighterIDs = []
        for drone in fit.drones:
            if drone.amountActive > 0:
                droneIDs.append(drone.item.typeID)
                droneNames.append("%s x%s" % (drone.item.name, drone.amount))
        for fighter in fit.fighters:
            if fighter.amount > 0:
                fighterIDs.append(fighter.item.typeID)
                fighterNames.append("%s x%s" % (fighter.item.name, fighter.amount))
        if len(droneNames) > 0:
            modTypeIDs.extend([0, 0])
            modTypeIDs.extend(droneIDs)
            moduleNames.extend(["", "Drones:"])
            moduleNames.extend(droneNames)
        if len(fighterNames) > 0:
            modTypeIDs.extend([0, 0])
            modTypeIDs.extend(fighterIDs)
            moduleNames.extend(["", "Fighters:"])
            moduleNames.extend(fighterNames)
        if len(fit.implants) > 0:
            modTypeIDs.extend([0, 0])
            moduleNames.extend(["", "Implants:"])
            for implant in fit.implants:
                modTypeIDs.append(implant.item.typeID)
                moduleNames.append(implant.item.name)
        if len(fit.boosters) > 0:
            modTypeIDs.extend([0, 0])
            moduleNames.extend(["", "Boosters:"])
            for booster in fit.boosters:
                modTypeIDs.append(booster.item.typeID)
                moduleNames.append(booster.item.name)
        if len(fit.commandFits) > 0:
            modTypeIDs.extend([0, 0])
            moduleNames.extend(["", "Command Fits:"])
            for commandFit in fit.commandFits:
                modTypeIDs.append(commandFit.ship.item.typeID)
                moduleNames.append(commandFit.name)
        if len(fit.projectedModules) > 0:
            modTypeIDs.extend([0, 0])
            moduleNames.extend(["", "Projected Modules:"])
            for mod in fit.projectedModules:
                modTypeIDs.append(mod.item.typeID)
                moduleNames.append(mod.item.name)

        if fit.character.name != "All 5":
            modTypeIDs.extend([0, 0, 0])
            moduleNames.extend(["", "Character:"])
            moduleNames.append(fit.character.name)
        if padTypeIDs is not True:
            modTypeIDsUnpadded = [mod for mod in modTypeIDs if mod != 0]
            modTypeIDs = modTypeIDsUnpadded
        return {"moduleNames": moduleNames, "modTypeIDs": modTypeIDs}

    @staticmethod
    def getFighterAbilityData(fighterAttr, fighter, baseRef):
        baseRefDam = baseRef + "Damage"
        abilityName = "RegularAttack" if baseRef == "fighterAbilityAttackMissile" else "MissileAttack"
        rangeSuffix = "RangeOptimal" if baseRef == "fighterAbilityAttackMissile" else "Range"
        reductionRef = baseRef if baseRef == "fighterAbilityAttackMissile" else baseRefDam
        damageReductionFactor = log(fighterAttr(reductionRef + "ReductionFactor")) / log(fighterAttr(reductionRef + "ReductionSensitivity"))
        damTypes = ["EM", "Therm", "Exp", "Kin"]
        abBaseDamage = sum(map(lambda damType: fighterAttr(baseRefDam + damType), damTypes))
        abDamage = abBaseDamage * fighterAttr(baseRefDam + "Multiplier")
        return {
            "name": abilityName, "volley": abDamage * fighter.amount, "explosionRadius": fighterAttr(baseRef + "ExplosionRadius"),
            "explosionVelocity": fighterAttr(baseRef + "ExplosionVelocity"), "optimal": fighterAttr(baseRef + rangeSuffix),
            "damageReductionFactor": damageReductionFactor, "rof": fighterAttr(baseRef + "Duration"),
        }

    @staticmethod
    def getWeaponSystemData(fit):
        weaponSystems = []
        groups = {}
        # Export at maximum spool for consistency, spoolup data is exported anyway.
        defaultSpoolValue = 1
        spoolOptions = SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, True)
        for mod in fit.modules:
            if mod.getDps(spoolOptions=spoolOptions).total > 0:
                # Group weapon + ammo combinations that occur more than once
                keystr = str(mod.itemID) + "-" + str(mod.chargeID)
                if keystr in groups:
                    groups[keystr][1] += 1
                else:
                    groups[keystr] = [mod, 1]
        for wepGroup in groups.values():
            stats = wepGroup[0]
            n = wepGroup[1]
            tracking = 0
            maxVelocity = 0
            explosionDelay = 0
            damageReductionFactor = 0
            explosionRadius = 0
            explosionVelocity = 0
            aoeFieldRange = 0
            typeing = 'None'
            if stats.charge:
                name = stats.item.name + ", " + stats.charge.name
            else:
                name = stats.item.name
            if stats.hardpoint == FittingHardpoint.TURRET:
                tracking = stats.getModifiedItemAttr("trackingSpeed")
                typeing = "Turret"
            # Bombs share most attributes with missiles despite not needing the hardpoint
            elif stats.hardpoint == FittingHardpoint.MISSILE or "Bomb Launcher" in stats.item.name:
                maxVelocity = stats.getModifiedChargeAttr("maxVelocity")
                explosionDelay = stats.getModifiedChargeAttr("explosionDelay")
                damageReductionFactor = stats.getModifiedChargeAttr("aoeDamageReductionFactor")
                explosionRadius = stats.getModifiedChargeAttr("aoeCloudSize")
                explosionVelocity = stats.getModifiedChargeAttr("aoeVelocity")
                typeing = "Missile"
            # AoE DDs can be treated like missiles with a damageReductionFactor of 0
            elif stats.item.group.name == 'Super Weapon' and stats.maxRange:
                explosionRadius = stats.getModifiedItemAttr("signatureRadius")
                typeing = "Missile"
            elif stats.hardpoint == FittingHardpoint.NONE:
                aoeFieldRange = stats.getModifiedItemAttr("empFieldRange")
                # This also covers non-bomb weapons with dps values and no hardpoints, most notably targeted doomsdays.
                typeing = "SmartBomb"
            # Targeted DDs are the only non drone/fighter weapon without an explicit max range
            if stats.item.group.name == 'Super Weapon' and stats.maxRange is None:
                maxRange = 300000
            else:
                maxRange = stats.maxRange
            statDict = {
                "dps": stats.getDps(spoolOptions=spoolOptions).total * n, "capUse": stats.capUse * n, "falloff": stats.falloff,
                "type": typeing, "name": name, "optimal": maxRange,
                "numCharges": stats.numCharges, "numShots": stats.numShots, "reloadTime": stats.reloadTime,
                "cycleTime": stats.getCycleParameters().averageTime, "volley": stats.getVolley(spoolOptions=spoolOptions).total * n, "tracking": tracking,
                "maxVelocity": maxVelocity, "explosionDelay": explosionDelay, "damageReductionFactor": damageReductionFactor,
                "explosionRadius": explosionRadius, "explosionVelocity": explosionVelocity, "aoeFieldRange": aoeFieldRange,
                "damageMultiplierBonusMax": stats.getModifiedItemAttr("damageMultiplierBonusMax"),
                "damageMultiplierBonusPerCycle": stats.getModifiedItemAttr("damageMultiplierBonusPerCycle")
            }
            weaponSystems.append(statDict)
        for drone in fit.drones:
            if drone.getDps().total > 0 and drone.amountActive > 0:
                droneAttr = drone.getModifiedItemAttr
                # Drones are using the old tracking formula for trackingSpeed. This updates it to match turrets.
                newTracking = droneAttr("trackingSpeed") / (droneAttr("optimalSigRadius") / 40000)
                statDict = {
                    "dps": drone.getDps().total, "cycleTime": drone.getCycleParameters().averageTime, "type": "Drone",
                    "optimal": drone.maxRange, "name": drone.item.name, "falloff": drone.falloff,
                    "maxSpeed": droneAttr("maxVelocity"), "tracking": newTracking,
                    "volley": drone.getVolley().total
                }
                weaponSystems.append(statDict)
        for fighter in fit.fighters:
            if fighter.getDps().total > 0 and fighter.amount > 0:
                fighterAttr = fighter.getModifiedItemAttr
                abilities = []
                if "fighterAbilityAttackMissileDamageEM" in fighter.item.attributes.keys():
                    baseRef = "fighterAbilityAttackMissile"
                    ability = EfsPort.getFighterAbilityData(fighterAttr, fighter, baseRef)
                    abilities.append(ability)
                if "fighterAbilityMissilesDamageEM" in fighter.item.attributes.keys():
                    baseRef = "fighterAbilityMissiles"
                    ability = EfsPort.getFighterAbilityData(fighterAttr, fighter, baseRef)
                    abilities.append(ability)
                statDict = {
                    "dps": fighter.getDps().total, "type": "Fighter", "name": fighter.item.name,
                    "maxSpeed": fighterAttr("maxVelocity"), "abilities": abilities,
                    "ehp": fighterAttr("shieldCapacity") / 0.8875 * fighter.amount,
                    "volley": fighter.getVolley().total, "signatureRadius": fighterAttr("signatureRadius")
                }
                weaponSystems.append(statDict)
        return weaponSystems

    @staticmethod
    def getTestSet(setType):
        def getT2ItemsWhere(additionalFilter, mustBeOffensive=False, category="Module"):
            # Used to obtain a smaller subset of items while still containing examples of each group.
            T2_META_LEVEL = 5
            categoryID = getCategory(category).categoryID
            result = gamedata_session.query(Item).join(ItemEffect, Group, Attribute).\
                      filter(
                          additionalFilter,
                          Item.metaLevel == T2_META_LEVEL,
                          Group.categoryID == categoryID,
                      ).all()
            if mustBeOffensive:
                result = filter(lambda t: t.offensive is True, result)
            return list(result)

        def getChargeType(item, setType):
            if setType == "turret":
                return str(item.attributes["chargeGroup1"].value) + "-" + str(item.attributes["chargeSize"].value)
            return str(item.attributes["chargeGroup1"].value)

        if setType in EfsPort.wepTestSet.keys():
            return EfsPort.wepTestSet[setType]
        else:
            EfsPort.wepTestSet[setType] = []
        modSet = EfsPort.wepTestSet[setType]

        if setType == "drone":
            ilist = getT2ItemsWhere(True, True, "Drone")
            for item in ilist:
                drone = Drone(item)
                drone.amount = 1
                drone.amountActive = 1
                drone.itemModifiedAttributes.parent = drone
                modSet.append(drone)
            return modSet

        turretFittedEffectID = gamedata_session.query(Effect).filter(Effect.name == "turretFitted").first().effectID
        launcherFittedEffectID = gamedata_session.query(Effect).filter(Effect.name == "launcherFitted").first().effectID
        if setType == "launcher":
            effectFilter = ItemEffect.effectID == launcherFittedEffectID
            reqOff = False
        else:
            effectFilter = ItemEffect.effectID == turretFittedEffectID
            reqOff = True
        ilist = getT2ItemsWhere(effectFilter, reqOff)
        previousChargeTypes = []
        # Get modules from item list
        for item in ilist:
            chargeType = getChargeType(item, setType)
            # Only add turrets if we don"t already have one with the same size and ammo type.
            if setType == "launcher" or chargeType not in previousChargeTypes:
                previousChargeTypes.append(chargeType)
                mod = Module(item)
                modSet.append(mod)

        sMkt = Market.getInstance()
        # Due to typed missile damage bonuses we"ll need to add extra launchers to cover all four types.
        additionalLaunchers = []
        for mod in modSet:
            clist = list(gamedata_session.query(Item).options().
                    filter(Item.groupID == mod.getModifiedItemAttr("chargeGroup1")).all())
            mods = [mod]
            charges = [clist[0]]
            if setType == "launcher":
                # We don"t want variations of missiles we already have
                prevCharges = list(sMkt.getVariationsByItems(charges))
                testCharges = []
                for charge in clist:
                    if charge not in prevCharges:
                        testCharges.append(charge)
                        prevCharges += sMkt.getVariationsByItems([charge])
                for c in testCharges:
                    charges.append(c)
                    additionalLauncher = Module(mod.item)
                    mods.append(additionalLauncher)
            for i in range(len(mods)):
                mods[i].charge = charges[i]
                mods[i].reloadForce = True
                mods[i].state = 2
                if setType == "launcher" and i > 0:
                    additionalLaunchers.append(mods[i])
        modSet += additionalLaunchers
        return modSet

    @staticmethod
    def getWeaponBonusMultipliers(fit):
        def sumDamage(attr):
            totalDamage = 0
            for damageType in ["emDamage", "thermalDamage", "kineticDamage", "explosiveDamage"]:
                if attr(damageType) is not None:
                    totalDamage += attr(damageType)
            return totalDamage

        def getCurrentMultipliers(tf):
            fitMultipliers = {}
            getDroneMulti = lambda d: sumDamage(d.getModifiedItemAttr) * d.getModifiedItemAttr("damageMultiplier")
            fitMultipliers["drones"] = list(map(getDroneMulti, tf.drones))

            getFitTurrets = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.TURRET, f.modules)
            getTurretMulti = lambda mod: mod.getModifiedItemAttr("damageMultiplier") / mod.getCycleParameters().averageTime
            fitMultipliers["turrets"] = list(map(getTurretMulti, getFitTurrets(tf)))

            getFitLaunchers = lambda f: filter(lambda mod: mod.hardpoint == FittingHardpoint.MISSILE, f.modules)
            getLauncherMulti = lambda mod: sumDamage(mod.getModifiedChargeAttr) / mod.getCycleParameters().averageTime
            fitMultipliers["launchers"] = list(map(getLauncherMulti, getFitLaunchers(tf)))
            return fitMultipliers

        multipliers = {"turret": 1, "launcher": 1, "droneBandwidth": 1}
        drones = EfsPort.getTestSet("drone")
        launchers = EfsPort.getTestSet("launcher")
        turrets = EfsPort.getTestSet("turret")
        for weaponTypeSet in [turrets, launchers, drones]:
            for mod in weaponTypeSet:
                mod.owner = fit
        turrets = list(filter(lambda mod: mod.getModifiedItemAttr("damageMultiplier"), turrets))
        launchers = list(filter(lambda mod: sumDamage(mod.getModifiedChargeAttr), launchers))

        # Since the effect modules are fairly opaque a mock test fit is used to test the impact of traits.
        # standin class used to prevent . notation causing issues when used as an arg
        class standin:
            pass
        tf = standin()
        tf.modules = HandledList(turrets + launchers)
        tf.character = fit.character
        tf.ship = fit.ship
        tf.drones = HandledList(drones)
        tf.fighters = HandledList([])
        tf.boosters = HandledList([])
        tf.extraAttributes = fit.extraAttributes
        tf.mode = fit.mode
        preTraitMultipliers = getCurrentMultipliers(tf)
        for effect in fit.ship.item.effects.values():
            if effect.isImplemented:
                effect.handler(tf, tf.ship, [], None, effect=effect)
        # Factor in mode effects for T3 Destroyers
        if fit.mode is not None:
            for effect in fit.mode.item.effects.values():
                if effect.isImplemented:
                    effect.handler(tf, fit.mode, [], None, effect=effect)
        if fit.ship.item.groupID == getGroup("Strategic Cruiser").ID:
            subSystems = list(filter(lambda mod: mod.slot == FittingSlot.SUBSYSTEM and mod.item, fit.modules))
            for sub in subSystems:
                for effect in sub.item.effects.values():
                    if effect.isImplemented:
                        effect.handler(tf, sub, [], None, effect=effect)
        postTraitMultipliers = getCurrentMultipliers(tf)
        getMaxRatio = lambda dictA, dictB, key: max(map(lambda a, b: b / a, dictA[key], dictB[key]))
        multipliers["turret"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "turrets"), 6)
        multipliers["launcher"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "launchers"), 6)
        multipliers["droneBandwidth"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "drones"), 6)
        Fit.getInstance().recalc(fit)
        return multipliers

    @staticmethod
    def getShipSize(groupID):
        # Size groupings are somewhat arbitrary but allow for a more manageable number of top level groupings in a tree structure.
        frigateGroupNames = ["Frigate", "Shuttle", "Corvette", "Assault Frigate", "Covert Ops", "Interceptor",
                             "Stealth Bomber", "Electronic Attack Ship", "Expedition Frigate", "Logistics Frigate"]
        destroyerGroupNames = ["Destroyer", "Interdictor", "Tactical Destroyer", "Command Destroyer"]
        cruiserGroupNames = ["Cruiser", "Heavy Assault Cruiser", "Logistics", "Force Recon Ship",
                             "Heavy Interdiction Cruiser", "Combat Recon Ship", "Strategic Cruiser"]
        bcGroupNames = ["Combat Battlecruiser", "Command Ship", "Attack Battlecruiser"]
        bsGroupNames = ["Battleship", "Elite Battleship", "Black Ops", "Marauder"]
        capitalGroupNames = ["Titan", "Dreadnought", "Freighter", "Carrier", "Supercarrier",
                             "Capital Industrial Ship", "Jump Freighter", "Force Auxiliary"]
        indyGroupNames = ["Industrial", "Deep Space Transport", "Blockade Runner",
                          "Mining Barge", "Exhumer", "Industrial Command Ship"]
        miscGroupNames = ["Capsule", "Prototype Exploration Ship"]
        shipSizes = [
            {"name": "Frigate", "groupIDs": map(lambda s: getGroup(s).ID, frigateGroupNames)},
            {"name": "Destroyer", "groupIDs": map(lambda s: getGroup(s).ID, destroyerGroupNames)},
            {"name": "Cruiser", "groupIDs": map(lambda s: getGroup(s).ID, cruiserGroupNames)},
            {"name": "Battlecruiser", "groupIDs": map(lambda s: getGroup(s).ID, bcGroupNames)},
            {"name": "Battleship", "groupIDs": map(lambda s: getGroup(s).ID, bsGroupNames)},
            {"name": "Capital", "groupIDs": map(lambda s: getGroup(s).ID, capitalGroupNames)},
            {"name": "Industrial", "groupIDs": map(lambda s: getGroup(s).ID, indyGroupNames)},
            {"name": "Misc", "groupIDs": map(lambda s: getGroup(s).ID, miscGroupNames)}
        ]
        for size in shipSizes:
            if groupID in size["groupIDs"]:
                return size["name"]
        sizeNotFoundMsg = "ShipSize not found for groupID: " + str(groupID)
        return sizeNotFoundMsg

    @staticmethod
    def exportEfs(fit, typeNotFitFlag, callback):
        sFit = Fit.getInstance()
        includeShipTypeData = typeNotFitFlag > 0
        if includeShipTypeData:
            fitName = fit.name
        else:
            fitName = fit.ship.name + ": " + fit.name
        pyfalog.info("Creating Eve Fleet Simulator data for: " + fit.name)
        fitModAttr = fit.ship.getModifiedItemAttr
        propData = EfsPort.getPropData(fit, sFit)
        mwdPropSpeed = fit.maxSpeed
        if includeShipTypeData:
            mwdPropSpeed = EfsPort.getT2MwdSpeed(fit, sFit)
        projections = EfsPort.getOutgoingProjectionData(fit)
        modInfo = EfsPort.getModuleInfo(fit)
        moduleNames = modInfo["moduleNames"]


# pylint: disable=missing-docstring

import itertools

import ddt
import six
from six.moves import range
from django.conf import settings
from django.test.client import RequestFactory
from django.test.utils import override_settings
from opaque_keys.edx.locator import CourseLocator

from lms.djangoapps.discussion.notification_prefs import NOTIFICATION_PREF_KEY
from lms.djangoapps.discussion.notifier_api.views import NotifierUsersViewSet
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from openedx.core.djangoapps.django_comment_common.models import Permission, Role
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.models import UserPreference
from openedx.core.djangoapps.user_api.tests.factories import UserPreferenceFactory
from student.models import CourseEnrollment
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

@ddt.ddt
@override_settings(EDX_API_KEY="test_api_key")
class NotifierUsersViewSetTest(UrlResetMixin, ModuleStoreTestCase):
    def setUp(self):
        super(NotifierUsersViewSetTest, self).setUp()
        self.courses = []
        self.cohorts = []
        self.user = UserFactory()
        self.notification_pref = UserPreferenceFactory(
            user=self.user,
            key=NOTIFICATION_PREF_KEY,
            value="notification pref test value"
        )

        self.list_view = NotifierUsersViewSet.as_view({"get": "list"})
        self.detail_view = NotifierUsersViewSet.as_view({"get": "retrieve"})

    def _set_up_course(self, is_course_cohorted, is_user_cohorted, is_moderator):
        cohort_config = {"cohorted": True} if is_course_cohorted else {}
        course = CourseFactory(
            number=("TestCourse{}".format(len(self.courses))),
            cohort_config=cohort_config
        )
        self.courses.append(course)
        CourseEnrollmentFactory(user=self.user, course_id=course.id)
        if is_user_cohorted:
            cohort = CohortFactory.create(
                name="Test Cohort",
                course_id=course.id,
                users=[self.user]
            )
            self.cohorts.append(cohort)
        if is_moderator:
            moderator_perm, _ = Permission.objects.get_or_create(name="see_all_cohorts")
            moderator_role = Role.objects.create(name="Moderator", course_id=course.id)
            moderator_role.permissions.add(moderator_perm)
            self.user.roles.add(moderator_role)

    def _assert_basic_user_info_correct(self, user, result_user):
        self.assertEqual(result_user["id"], user.id)
        self.assertEqual(result_user["email"], user.email)
        self.assertEqual(result_user["name"], user.profile.name)

    def test_without_api_key(self):
        request = RequestFactory().get("dummy")
        for view in [self.list_view, self.detail_view]:
            response = view(request)
            self.assertEqual(response.status_code, 403)

    # Detail view tests

    def _make_detail_request(self):
        request = RequestFactory().get("dummy", HTTP_X_EDX_API_KEY=settings.EDX_API_KEY)
        return self.detail_view(
            request,
            **{NotifierUsersViewSet.lookup_field: str(self.user.id)}
        )

    def _get_detail(self):
        response = self._make_detail_request()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            set(response.data.keys()),
            {"id", "email", "name", "preferences", "course_info"}
        )
        return response.data

    def test_detail_invalid_user(self):
        UserPreference.objects.all().delete()
        response = self._make_detail_request()
        self.assertEqual(response.status_code, 404)

    def test_basic_user_info(self):
        result = self._get_detail()
        self._assert_basic_user_info_correct(self.user, result)

    def test_course_info(self):
        expected_course_info = {}
        for is_course_cohorted, is_user_cohorted, is_moderator in (
                itertools.product([True, False], [True, False], [True, False])
        ):
            self._set_up_course(is_course_cohorted, is_user_cohorted, is_moderator)
            expected_course_info[six.text_type(self.courses[-1].id)] = {
                "cohort_id": self.cohorts[-1].id if is_user_cohorted else None,
                "see_all_cohorts": is_moderator or not is_course_cohorted
            }
        result = self._get_detail()
        self.assertEqual(result["course_info"], expected_course_info)

    def test_course_info_unenrolled(self):
        self._set_up_course(False, False, False)
        course_id = self.courses[0].id
        CourseEnrollment.unenroll(self.user, course_id)
        result = self._get_detail()
        self.assertNotIn(six.text_type(course_id), result["course_info"])

    def test_course_info_no_enrollments(self):
        result = self._get_detail()
        self.assertEqual(result["course_info"], {})

    def test_course_info_non_existent_course_enrollment(self):
        CourseEnrollmentFactory(
            user=self.user,
            course_id=CourseLocator(org="dummy", course="dummy", run="non_existent")
        )
        result = self._get_detail()
        self.assertEqual(result["course_info"], {})

    def test_preferences(self):
        lang_pref = UserPreferenceFactory(
            user=self.user,
            key=LANGUAGE_KEY,
            value="language pref test value"
        )
        UserPreferenceFactory(user=self.user, key="non_included_key")
        result = self._get_detail()
        self.assertEqual(
            result["preferences"],
            {
                NOTIFICATION_PREF_KEY: self.notification_pref.value,
                LANGUAGE_KEY: lang_pref.value,
            }
        )

    # List view tests

    def _make_list_request(self, page, page_size):
        request = RequestFactory().get(
            "dummy",
            {"page": page, "page_size": page_size},
            HTTP_X_EDX_API_KEY=settings.EDX_API_KEY
        )
        return self.list_view(request)

    def _get_list(self, page=1, page_size=''):
        response = self._make_list_request(page, page_size)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            set(response.data.keys()),
            {"count", "next", "previous", "results"}
        )
        return response.data["results"]

    def test_no_users(self):
        UserPreference.objects.all().delete()
        results = self._get_list()
        self.assertEqual(len(results), 0)

    def test_multiple_users(self):
        other_user = UserFactory()
        other_notification_pref = UserPreferenceFactory(
            user=other_user,
            key=NOTIFICATION_PREF_KEY,
            value="other value"
        )
        self._set_up_course(is_course_cohorted=True, is_user_cohorted=True, is_moderator=False)
        self._set_up_course(is_course_cohorted=False, is_user_cohorted=False, is_moderator=False)
        # Users have different sets of enrollments
        CourseEnrollmentFactory(user=other_user, course_id=self.courses[0].id)

        result_map = {result["id"]: result for result in self._get_list()}
        self.assertEqual(set(result_map.keys()), {self.user.id, other_user.id})
        for user in [self.user, other_user]:
            self._assert_basic_user_info_correct(user, result_map[user.id])
        self.assertEqual(
            result_map[self.user.id]["preferences"],
            {NOTIFICATION_PREF_KEY: self.notification_pref.value}
        )
        self.assertEqual(
            result_map[other_user.id]["preferences"],
            {NOTIFICATION_PREF_KEY: other_notification_pref.value}
        )
        self.assertEqual(
            result_map[self.user.id]["course_info"],
            {
                six.text_type(self.courses[0].id): {
                    "cohort_id": self.cohorts[0].id,
                    "see_all_cohorts": False,
                },
                six.text_type(self.courses[1].id): {
                    "cohort_id": None,
                    "see_all_cohorts": True,
                },
            }
        )
        self.assertEqual(
            result_map[other_user.id]["course_info"],
            {
                six.text_type(self.courses[0].id): {
                    "cohort_id": None,
                    "see_all_cohorts": False,
                },
            }
        )

    @ddt.data(
        3,  # Factor of num of results
        5,  # Non-factor of num of results
        12,  # Num of results
        15  # More than num of results
    )
    def test_pagination(self, page_size):
        num_users = 12
        users = [self.user]
        while len(users) < num_users:
            new_user = UserFactory()
            users.append(new_user)
            UserPreferenceFactory(user=new_user, key=NOTIFICATION_PREF_KEY)

        num_pages = (num_users - 1) // page_size + 1
        result_list = []
        for i in range(1, num_pages + 1):
            result_list.extend(self._get_list(page=i, page_size=page_size))


import csv
import numpy as np
import copy
import inspyred
from random import Random
from time import time
from inspyred import ec
import scipy
from scipy.optimize import minimize
import math as m

class Solve:

    def __init__(self, x, y):
        self.LnDetPsi = None
        self.SigmaSqr = None
        self.x = copy.deepcopy(x)
        self.y = copy.deepcopy(y)
        self.n = self.x.shape[0]
        self.k = self.x.shape[1]
        self.theta = np.ones(self.k)
        self.pl = np.ones(self.k) * 2.0
        self.max_x = None
        self.min_x = None
        self.max_y = max(y)
        self.min_y = min(y)
        self.normalizeData()
        self.Psi = np.zeros((self.n, self.n), dtype=np.float)
        self.psi = np.zeros((self.n, 1))
        self.one = np.ones(self.n)
        self.mu = None
        self.U = None
        self.distance = None
        self.updateData()
        self.thetamin = 1e-5
        self.thetamax = 100
        self.pmin = 1
        self.pmax = 2

    def normx(self, x):
        normalised = []
        for i in range(self.k):
            normalised.append((x[i] - self.min_x[i]) /
                              (self.max_x[i] - self.min_x[i]))
        return np.array(normalised)

    def normy(self, y):
        return (y - self.min_y) / (self.max_y - self.min_y)

    def inversenormy(self, normy):
        return normy * (self.max_y - self.min_y) + self.min_y

    def normalizeData(self):
        max_x = []
        min_x = []
        for i in range(self.k):
            max_x.append(max(self.x[:, i]))
            min_x.append(min(self.x[:, i]))
        self.max_x = np.array(max_x)
        self.min_x = np.array(min_x)
        for i in range(self.n):
            self.x[i] = self.normx(self.x[i])
            self.y[i] = self.normy(self.y[i])

    def updateData(self):
        self.distance = np.zeros((self.n, self.n, self.k))
        for i in xrange(self.n):
            for j in xrange(i + 1, self.n):
                self.distance[i, j] = np.abs((self.x[i] - self.x[j]))
        newPsi = np.exp(-np.sum(self.theta *
                                np.power(self.distance, self.pl), axis=2))
        self.Psi = np.triu(newPsi, 1)
        self.Psi = self.Psi + self.Psi.T + np.eye(self.n) + np.eye(self.n)
        self.U = np.linalg.cholesky(self.Psi)
        self.U = np.matrix(self.U.T)
        a = np.linalg.solve(self.U.T, self.one)
        b = np.linalg.solve(self.U, a)
        c = self.one.T.dot(b)
        d = np.linalg.solve(self.U.T, self.y)
        e = np.linalg.solve(self.U, d)
        self.mu = (self.one.T.dot(e)) / c

    def generate_population(self, random, args):
        '''
        Generates an initial population for any global optimization that occurs
        in pyKriging
        :param random: A random seed
        :param args: Args from the optimizer, like population size
        :return chromosome: The new generation for our global optimizer to use
        '''
        size = args.get('num_inputs', None)
        bounder = args["_ec"].bounder
        chromosome = []
        for lo, hi in zip(bounder.lower_bound, bounder.upper_bound):
            chromosome.append(random.uniform(lo, hi))
        return chromosome

    def no_improvement_termination(self, population, num_generations,
                                   num_evaluations, args):
        """Return True if the best fitness does not change for a number of
        generations of if the max number
        of evaluations is exceeded.

        .. Arguments:
                population -- the population of Individuals
                num_generations -- the number of elapsed generations
                num_evaluations -- the number of candidate solution evaluations
                args -- a dictionary of keyword arguments

        Optional keyword arguments in args:

        - *max_generations* -- the number of generations allowed for no change
        in fitness (default 10)

        """
        max_generations = args.setdefault('max_generations', 10)
        previous_best = args.setdefault('previous_best', None)
        max_evaluations = args.setdefault('max_evaluations', 30000)
        current_best = np.around(max(population).fitness, decimals=4)
        if previous_best is None or previous_best != current_best:
            args['previous_best'] = current_best
            args['generation_count'] = 0
            return False or (num_evaluations >= max_evaluations)
        else:
            if args['generation_count'] >= max_generations:
                return True
            else:
                args['generation_count'] += 1
                return False or (num_evaluations >= max_evaluations)

    def neglikelihood(self):
        self.LnDetPsi = 2.0 * np.sum(np.log(np.abs(np.diag(self.U))))
        a = np.linalg.solve(self.U.T, self.one.T)
        b = np.linalg.solve(self.U, a)
        c = self.one.T.dot(b)
        d = np.linalg.solve(self.U.T, self.y)
        e = np.linalg.solve(self.U, d)
        self.mu = (self.one.T.dot(e)) / c
        temp = np.linalg.solve(self.U.T, (self.y - self.mu))
        self.SigmaSqr = (
            (self.y - self.mu).T.dot(np.linalg.solve(self.U, temp))) / self.n
        self.NegLnLike = -1. * \
            (-(self.n / 2.) * np.log(self.SigmaSqr) - 0.5 * self.LnDetPsi)

    regneglikelihood = neglikelihood

    def fittingObjective(self, candidates, args):
        '''
        The objective for a series of candidates from the hyperparameter global
        search.
        :param candidates: An array of candidate design vectors from the global
        optimizer
        :param args: args from the optimizer
        :return fitness: An array of evaluated NegLNLike values for the
        candidate population
        '''
        fitness = []
        for entry in candidates:
            f = 10000
            for i in range(self.k):
                self.theta[i] = entry[i]
            for i in range(self.k):
                self.pl[i] = entry[i + self.k]
            self.neglikelihood()
            f = self.NegLnLike
            fitness.append(f)
        return fitness

    def fittingObjective_local(self, entry):
        '''
        :param entry: The same objective function as the global optimizer,but
        formatted for the
         local optimizer
        :return: The fitness of the surface at the hyperparameters specified in
        entry
        '''
        f = 10000
        for i in range(self.k):
            self.theta[i] = entry[i]
        for i in range(self.k):
            self.pl[i] = entry[i + self.k]
        self.neglikelihood()
        f = self.NegLnLike
        return f

    def train(self):
        '''
        The function trains the hyperparameters of the Kriging model.
        :param optimizer: Two optimizers are implemented, a Particle Swarm
        Optimizer or a GA
        '''

        self.updateData()

        lowerBound = [self.thetamin] * self.k + [self.pmin] * self.k
        upperBound = [self.thetamax] * self.k + [self.pmax] * self.k

        # Create a random seed for our optimizer to use
        rand = Random()
        rand.seed(int(time()))

        ea = inspyred.swarm.PSO(Random())
        ea.terminator = self.no_improvement_termination
        ea.topology = inspyred.swarm.topologies.ring_topology
        final_pop = ea.evolve(
            generator=self.generate_population,
            evaluator=self.fittingObjective, pop_size=300, maximize=False,
            bounder=ec.Bounder(lowerBound, upperBound), max_evaluations=30000,
            neighborhood_size=20, num_inputs=self.k)
        final_pop.sort(reverse=True)

        for entry in final_pop:
            newValues = entry.candidate
            preLOP = copy.deepcopy(newValues)
            locOP_bounds = []
            for i in range(self.k):
                locOP_bounds.append([self.thetamin, self.thetamax])

            for i in range(self.k):
                locOP_bounds.append([self.pmin, self.pmax])

            lopResults = minimize(
                self.fittingObjective_local, newValues, method='SLSQP',
                bounds=locOP_bounds, options={'disp': False}
                )

            newValues = lopResults['x']

            for i in range(self.k):
                self.theta[i] = newValues[i]
            for i in range(self.k):
                self.pl[i] = newValues[i + self.k]

    def predict_normalized(self, x):
        for i in range(self.n):
            self.psi[i] = np.exp(-np.sum(self.theta *
                                         np.power((np.abs(self.x[i] - x)),
                                                  self.pl)))
        z = self.y - self.mu
        a = np.linalg.solve(self.U.T, z)
        b = np.linalg.solve(self.U, a)
        c = self.psi.T.dot(b)
        f = self.mu + c
        return f[0]

    def predict(self, x):
        self.updateData()
        normx = self.normx(x)
        normy = self.predict_normalized(normx)
        return self.inversenormy(normy)

def str_to_float(row):
    result = []
    for char in row:
        result.append(float(char))
    return np.array(result)

def read_data(file_name):
    f = open(file_name, 'rb')
    data = csv.reader(f, delimiter=',')
    x = []
    y = []
    for row in data:
        x.append(str_to_float(row[:-1]))
        rowy = [float(row[-1])]
        y.append(np.array(rowy))
    f.close()
    return np.array(x), np.array(y)

def divide_data(x, y):
    length = x.shape[0]
    x1 = []
    y1 = []
    x2 = []
    y2 = []
    for i in range(1, length + 1):
        if i % 5 == 0:
            x2.append(x[i - 1])
            y2.append(y[i - 1])
        else:
            x1.append(x[i - 1])
            y1.append(y[i - 1])
    return np.array(x1), np.array(y1), np.array(x2), np.array(y2)

def estimate_error(model, x, y):
    estimate_y = []
    for row in x:
        estimate_y.append(model.predict(row))
    estimate_y = np.array(estimate_y)
    error = np.sqrt(sum(np.ravel(estimate_y - y) *
                        np.ravel(estimate_y - y))) / len(y)
    return error

def train_model(model_name, training_data_file):
    start = time()
    x, y = read_data(training_data_file)
    x1, y1, x2, y2 = divide_data(x, y)
    print 'Preliminary model initialising...'
    prelim_model = Solve(x1, y1)
    print 'Preliminary model initialised'
    print 'Preliminary model training started'
    prelim_model.train()
    print 'Preliminary model trained'

    print 'Estimating error using preliminary model'
    error = estimate_error(prelim_model, x2, y2)
    print 'Error estimation done'

    print 'Final model initialising...'
    final_model = Solve(x, y)
    print 'Final model initialised'
    print 'Final model training started'
    print 'Esimated time remaining is approximately ' + str(int(1.45 * (time() - start))) + ' seconds'
    final_model.train()
    print 'Final model trained'

    end = time()
    print 'Total time taken = ' + str(int(end - start)) + ' seconds'
    print 'L2 error for the given data = ' + str(round(error, 2))

    f = open(model_name, 'wb')
    writer = csv.writer(f, delimiter=',')
    writer.writerow(np.concatenate([final_model.max_x, final_model.max_y]))
    writer.writerow(np.concatenate([final_model.min_x, final_model.min_y]))
    for i in range(final_model.n):
        writer.writerow(x[i])
    f.close()

def find_values(model_name, find_y, outname):
    model_file = open(model_name, 'rb')
    model_data = csv.reader(model_file, delimiter=',')
    n = 0
    normalised_x = []
    for row in model_data:
        if n == 0:


# Copyright Daniel Dunn 2013. 2015
# This file is part of Kaithem Automation.

# Kaithem Automation 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, version 3.

# Kaithem Automation 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 Kaithem Automation.  If not, see <http://www.gnu.org/licenses/>.

from typing import Optional
import stat
import os
import json
from pwd import getpwuid, getpwnam
from scullery.persist import *
from scullery.messagebus import subscribe, postMessage

registry=None

import weakref
import threading
import logging
import traceback
import urllib.parse

dirty = weakref.WeakValueDictionary()

dirty_state_files = dirty

stateFileLock = threading.RLock()

import os
import pwd

selected_user = pwd.getpwuid( os.geteuid() ).pw_name

recoveryDir : str = os.path.join("/dev/shm/SculleryRFRecovery", selected_user)
if os.path.exists("/dev/shm"):
    if not os.path.exists("/dev/shm/SculleryRFRecovery"):
        os.mkdir("/dev/shm/SculleryRFRecovery")
    if not os.path.exists(recoveryDir):
        os.mkdir(recoveryDir)
        # Nobody else van put stuff in there!!!
        os.chmod(recoveryDir, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
        p = getpwnam(selected_user)
        os.chown(recoveryDir, p.pw_uid, p.pw_gid)
    else:
        if not getpwuid(os.stat(recoveryDir).st_uid).pw_name == selected_user:
            postMessage("/system/notifications/errors",
                        "Hacking Detected? "+recoveryDir+" not owned by this user")
            recoveryDir = ''
else:
    recoveryDir = ''

def recoveryPath(f):
    if f.startswith("/"):
        f = f[1:]
    return os.path.join(recoveryDir, urllib.parse.quote(f, safe="/"))

class SharedStateFile():
    """
        This is a dict that is savable when the system state gets saved.
        But it will aso use /dev/shm based recovery if the program crashes
    """

    def __init__(self, filename, save_topic="/system/save"):
        # Save all changes immediately to /dev/shm, for crash recovery.
        if not os.path.exists("/dev/shm") or not recoveryDir:
            self.recoveryFile = None
        else:
            self.recoveryFile = recoveryPath(filename)

        if os.path.exists(filename):
            try:
                self.data = load(filename)
            except Exception:
                self.data = {}
                postMessage("/system/notifications/errors",
                            filename+"\n"+traceback.format_exc())
        else:
            self.data = {}
        try:
            if os.path.exists(self.recoveryFile):
                self.data = load(self.recoveryFile)
                dirty[filename] = self
        except:
            print(traceback.format_exc())
        self.filename = filename
        self.legacy_registry_key_mappings = {}
        self.lock = threading.RLock()
        self.noFileForEmpty = False
        self.private=True
        allFiles[filename] = self
        if save_topic:
            subscribe(save_topic, self.save)

    def setupDefaults(self, defaults={}, legacy_registry_key_mappings={}):
        self.legacy_registry_key_mappings.update(legacy_registry_key_mappings)

        # Legacy Kaithem feature
        if registry:
            for i in legacy_registry_key_mappings:
                km = legacy_registry_key_mappings[i]
                if not i in self.data:
                    self.set(i,registry.get(km, defaults[i]))

        for i in defaults:
            if not i in self.data:
                self.set(i, defaults[i])

    def get(self, key, default=None):
        with self.lock:
            return self.data.get(key, default)

    def __contains__(self, key):
        if key in self.data:
            return True

    def getAllData(self):
        with self.lock:
            return self.data.copy()

    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, value):
        self.set(key, value)

    def __delitem__(self, key):
        self.pop(key)

    def set(self, key: str, value):
        with self.lock:
            json.dumps(value)
            if not isinstance(key, str):
                raise RuntimeError("Key must be str")

            if  key in self.data and self.data[key]==value:
                return
            self.data[key] = value

            if registry:
                if key in self.legacy_registry_key_mappings:
                    try:
                        registry.delete(self.legacy_registry_key_mappings[key])
                    except:
                        pass

            dirty[self.filename] = self

            if self.recoveryFile:
                save(self.data, self.recoveryFile, nolog=True,private=True)

    def clear(self):
        with self.lock:
            self.data.clear()
            dirty[self.filename] = self
            if self.recoveryFile:
                save(self.data, self.recoveryFile, nolog=True,private=True)

    def pop(self, key, default=None):
        with self.lock:
            self.data.pop(key, default)
            dirty[self.filename] = self
            if self.recoveryFile:
                save(self.data, self.recoveryFile, nolog=True,private=True)

    def delete(self, key):
        with self.lock:
            try:
                del self.data[key]
            except KeyError:
                pass

            if registry:
                if key in self.legacy_registry_key_mappings:
                    try:
                        registry.delete(self.legacy_registry_key_mappings[key])
                    except:
                        pass
            dirty[self.filename] = self
            if self.recoveryFile:
                save(self.data, self.recoveryFile,private=True)

    def save(self):
        with self.lock:
            if not self.filename in dirty:
                return
            # NoFileForEmpty mode deleted
            if self.noFileForEmpty and (not self.data):
                self.tryDeleteFile()
            else:
                save(self.data, self.filename,private=self.private)
            if self.recoveryFile and os.path.exists(self.recoveryFile):
                try:
                    os.remove(self.recoveryFile)
                except:
                    pass
            try:
                del dirty[self.filename]
            except:
                pass

    def isDirty(self):
        "Return true if no unsaved data"
        return self.filename in dirty

    def tryDeleteFile(self):
        if self.recoveryFile and os.path.exists(self.recoveryFile):
            try:
                os.remove(self.recoveryFile)
            except:
                logging.exception("wat")

        if os.path.exists(self.filename):
            try:
                os.remove(self.filename)
            except:
                logging.exception("wat")

#Py3.8 doesn't like this line.  Use the better typing once 3.9 is in all the big distros
#allFiles: weakref.WeakValueDictionary[str,SharedStateFile] = weakref.WeakValueDictionary()

allFiles = weakref.WeakValueDictionary()

def getStateFile(fn, defaults={}, legacy={}, deleteEmptyFiles=None) -> SharedStateFile:
    with stateFileLock:
        if fn in allFiles:
            s = allFiles[fn]
        else:
            s = SharedStateFile(fn)

        s.setupDefaults(defaults, legacy)


# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Adding field 'Checkpoint.old_forum'
        db.add_column(u'misago_checkpoint', 'old_forum',
                      self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='+', null=True, to=orm['misago.Forum']),
                      keep_default=False)

        # Adding field 'Checkpoint.old_forum_name'
        db.add_column(u'misago_checkpoint', 'old_forum_name',
                      self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
                      keep_default=False)

        # Adding field 'Checkpoint.old_forum_slug'
        db.add_column(u'misago_checkpoint', 'old_forum_slug',
                      self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
                      keep_default=False)

    def backwards(self, orm):
        # Deleting field 'Checkpoint.old_forum'
        db.delete_column(u'misago_checkpoint', 'old_forum_id')

        # Deleting field 'Checkpoint.old_forum_name'
        db.delete_column(u'misago_checkpoint', 'old_forum_name')

        # Deleting field 'Checkpoint.old_forum_slug'
        db.delete_column(u'misago_checkpoint', 'old_forum_slug')

    models = {
        'misago.alert': {
            'Meta': {'object_name': 'Alert'},
            'date': ('django.db.models.fields.DateTimeField', [], {}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'message': ('django.db.models.fields.TextField', [], {}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"}),
            'variables': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
        },
        'misago.ban': {
            'Meta': {'object_name': 'Ban'},
            'ban': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'reason_admin': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            'reason_user': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            'test': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
        },
        'misago.change': {
            'Meta': {'object_name': 'Change'},
            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'change': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
            'date': ('django.db.models.fields.DateTimeField', [], {}),
            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
            'post_content': ('django.db.models.fields.TextField', [], {}),
            'reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
            'thread_name_new': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'thread_name_old': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.checkpoint': {
            'Meta': {'object_name': 'Checkpoint'},
            'action': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'date': ('django.db.models.fields.DateTimeField', [], {}),
            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
            'old_forum': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['misago.Forum']"}),
            'old_forum_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'old_forum_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
            'target_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
            'target_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'target_user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.fixture': {
            'Meta': {'object_name': 'Fixture'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.forum': {
            'Meta': {'object_name': 'Forum'},
            'attrs': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            'description_preparsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'last_poster': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
            'last_poster_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'last_poster_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'last_poster_style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'last_thread': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.Thread']"}),
            'last_thread_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'last_thread_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'last_thread_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['misago.Forum']"}),
            'posts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'posts_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
            'prune_last': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'prune_start': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'redirect': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'redirects': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'redirects_delta': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
            'show_details': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}),
            'special': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'style': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'threads': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'threads_delta': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
            'type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
        },
        'misago.forumread': {
            'Meta': {'object_name': 'ForumRead'},
            'cleared': ('django.db.models.fields.DateTimeField', [], {}),
            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'updated': ('django.db.models.fields.DateTimeField', [], {}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']"})
        },
        'misago.forumrole': {
            'Meta': {'object_name': 'ForumRole'},
            '_permissions': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'permissions'", 'blank': 'True'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.karma': {
            'Meta': {'object_name': 'Karma'},
            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'date': ('django.db.models.fields.DateTimeField', [], {}),
            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
            'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Post']"}),
            'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'user_slug': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.monitoritem': {
            'Meta': {'object_name': 'MonitorItem'},
            'id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
            'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
        },
        'misago.newsletter': {
            'Meta': {'object_name': 'Newsletter'},
            'content_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            'content_plain': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'ignore_subscriptions': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'progress': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'ranks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['misago.Rank']", 'symmetrical': 'False'}),
            'step_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'token': ('django.db.models.fields.CharField', [], {'max_length': '32'})
        },
        'misago.post': {
            'Meta': {'object_name': 'Post'},
            'agent': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
            'checkpoints': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'date': ('django.db.models.fields.DateTimeField', [], {}),
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'downvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'edit_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
            'edit_reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'edit_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['misago.User']"}),
            'edit_user_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'edit_user_slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
            'edits': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Forum']"}),
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
            'mentions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mention_set'", 'symmetrical': 'False', 'to': "orm['misago.User']"}),
            'merge': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'post': ('django.db.models.fields.TextField', [], {}),
            'post_preparsed': ('django.db.models.fields.TextField', [], {}),
            'protected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'reported': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
            'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.Thread']"}),
            'upvotes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['misago.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
            'user_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
        },
        'misago.pruningpolicy': {


from hpp.corbaserver.rbprm.rbprmbuilder import Builder
from hpp.corbaserver.rbprm.rbprmfullbody import FullBody
from hpp.gepetto import Viewer
import time
from constraint_to_dae import *
from hpp.corbaserver.rbprm.rbprmstate import State,StateHelper
from display_tools import *
import plateform_hrp2_path as tp
import time

tPlanning = tp.tPlanning

packageName = "hrp2_14_description"
meshPackageName = "hrp2_14_description"
rootJointType = "freeflyer"
##
#  Information to retrieve urdf and srdf files.
urdfName = "hrp2_14"
urdfSuffix = "_reduced"
srdfSuffix = ""
pId = tp.ps.numberPaths() -1
fullBody = FullBody ()

fullBody.loadFullBodyModel(urdfName, rootJointType, meshPackageName, packageName, urdfSuffix, srdfSuffix)
fullBody.setJointBounds ("base_joint_xyz", [-1,2, -0.5, 0.5, 0.5, 0.8])
fullBody.client.basic.robot.setDimensionExtraConfigSpace(tp.extraDof)
fullBody.client.basic.robot.setExtraConfigSpaceBounds([-1,1,-1,1,-0.5,0.5,0,0,0,0,0,0])
ps = tp.ProblemSolver( fullBody )
ps.client.problem.setParameter("aMax",tp.aMax)
ps.client.problem.setParameter("vMax",tp.vMax)

r = tp.Viewer (ps,viewerClient=tp.r.client)

q_init =[0., 0., 0.648702, 1.0, 0.0 , 0.0, 0.0,0.0, 0.0, 0.0, 0.0,0.261799388,  0.174532925, 0.0, -0.523598776, 0.0, 0.0, 0.17,0.261799388, -0.174532925, 0.0, -0.523598776, 0.0, 0.0, 0.17,0.0, 0.0, -0.453785606, 0.872664626, -0.41887902, 0.0,0.0, 0.0, -0.453785606, 0.872664626, -0.41887902, 0.0,0,0,0,0,0,0]; r (q_init)
q_ref = q_init[::]
fullBody.setCurrentConfig (q_init)
qfar=q_ref[::]
qfar[2] = -5

#~ AFTER loading obstacles
rLegId = 'hrp2_rleg_rom'
lLegId = 'hrp2_lleg_rom'
tStart = time.time()

rLeg = 'RLEG_JOINT0'
rLegOffset = [0,0,-0.105]
rLegLimbOffset=[0,0,-0.035]#0.035
rLegNormal = [0,0,1]
rLegx = 0.09; rLegy = 0.05
#fullBody.addLimbDatabase("./db/hrp2_rleg_db.db",rLegId,"forward")
fullBody.addLimb(rLegId,rLeg,'',rLegOffset,rLegNormal, rLegx, rLegy, 100000, "fixedStep1", 0.01,"_6_DOF",limbOffset=rLegLimbOffset)
fullBody.runLimbSampleAnalysis(rLegId, "ReferenceConfiguration", True)
#fullBody.saveLimbDatabase(rLegId, "./db/hrp2_rleg_db.db")

lLeg = 'LLEG_JOINT0'
lLegOffset = [0,0,-0.105]
lLegLimbOffset=[0,0,0.035]
lLegNormal = [0,0,1]
lLegx = 0.09; lLegy = 0.05
#fullBody.addLimbDatabase("./db/hrp2_lleg_db.db",lLegId,"forward")
fullBody.addLimb(lLegId,lLeg,'',lLegOffset,rLegNormal, lLegx, lLegy, 100000, "fixedStep1", 0.01,"_6_DOF",limbOffset=lLegLimbOffset)
fullBody.runLimbSampleAnalysis(lLegId, "ReferenceConfiguration", True)
#fullBody.saveLimbDatabase(lLegId, "./db/hrp2_lleg_db.db")
fullBody.setReferenceConfig (q_ref)
## Add arms (not used for contact) :

tGenerate =  time.time() - tStart
print "generate databases in : "+str(tGenerate)+" s"

q_0 = fullBody.getCurrentConfig();
#~ fullBody.createOctreeBoxes(r.client.gui, 1, rarmId, q_0,)

configSize = fullBody.getConfigSize() -fullBody.client.basic.robot.getDimensionExtraConfigSpace()

q_init = fullBody.getCurrentConfig(); q_init[0:7] = tp.ps.configAtParam(pId,0.01)[0:7] # use this to get the correct orientation
q_goal = fullBody.getCurrentConfig(); q_goal[0:7] = tp.ps.configAtParam(pId,tp.ps.pathLength(pId))[0:7]
dir_init = tp.ps.configAtParam(pId,0.01)[tp.indexECS:tp.indexECS+3]
acc_init = tp.ps.configAtParam(pId,0.01)[tp.indexECS+3:tp.indexECS+6]
dir_goal = tp.ps.configAtParam(pId,tp.ps.pathLength(pId)-0.01)[tp.indexECS:tp.indexECS+3]
acc_goal = [0,0,0]

robTreshold = 3
# copy extraconfig for start and init configurations
q_init[configSize:configSize+3] = dir_init[::]
q_init[configSize+3:configSize+6] = acc_init[::]
q_goal[configSize:configSize+3] = dir_goal[::]
q_goal[configSize+3:configSize+6] = [0,0,0]

# FIXME : test
q_init[2] = q_ref[2]+0.01
q_goal[2] = q_ref[2]+0.01

fullBody.setStaticStability(True)
# Randomly generating a contact configuration at q_init
fullBody.setCurrentConfig (q_init)
r(q_init)
#q_init = fullBody.generateContacts(q_init,dir_init,acc_init,robTreshold)
r(q_init)

# Randomly generating a contact configuration at q_end
fullBody.setCurrentConfig (q_goal)
#q_goal = fullBody.generateContacts(q_goal, dir_goal,acc_goal,robTreshold)
r(q_goal)

# specifying the full body configurations as start and goal state of the problem
#r.addLandmark('hrp2_14/BODY',0.3)
r(q_init)

fullBody.setStartState(q_init,[lLegId,rLegId])
fullBody.setEndState(q_goal,[lLegId,rLegId])

#p = fullBody.computeContactPointsAtState(init_sid)
#p = fullBody.computeContactPointsAtState(int_sid)

"""
q = q_init[::]
q[0] += 0.3
q = fullBody.generateContacts(q,dir_init,acc_init,robTreshold)
mid_sid = fullBody.addState(q,[lLegId,rLegId])
"""

from hpp.gepetto import PathPlayer
pp = PathPlayer (fullBody.client.basic, r)

import fullBodyPlayerHrp2

"""
tStart = time.time()
configsFull = fullBody.interpolate(0.001,pathId=pId,robustnessTreshold = 1, filterStates = False)
tInterpolateConfigs = time.time() - tStart
print "number of configs :", len(configsFull)
"""

q_init[0] += 0.05
createSphere('s',r)

n = [0,0,1]
p = [0,0.1,0]

sf = State(fullBody,q=q_goal,limbsIncontact=[lLegId,rLegId])
si = State(fullBody,q=q_init,limbsIncontact=[lLegId,rLegId])

n = [0.0, -0.42261828000211843, 0.9063077785212101]
p = [0.775, 0.23, -0.02]
moveSphere('s',r,p)
smid,success = StateHelper.addNewContact(si,lLegId,p,n)
assert(success)
smid2,success = StateHelper.addNewContact(sf,lLegId,p,n)
assert(success)
r(smid.q())

sf2 = State(fullBody,q=q_goal,limbsIncontact=[lLegId,rLegId])
"""
com = fullBody.getCenterOfMass()
com[1] = 0
"""
import disp_bezier
pp.dt = 0.0001
pids = []
curves = []
timings = []
pid = fullBody.isDynamicallyReachableFromState(si.sId,smid.sId,True)
#assert (len(pid)>0)
disp_bezier.showPath(r,pp,pid)
curves += [fullBody.getPathAsBezier(int(pid[0]))]
timings += [[ps.pathLength(int(pid[1])), ps.pathLength(int(pid[2])), ps.pathLength(int(pid[3]))]]
pids += pid
pid = fullBody.isDynamicallyReachableFromState(smid.sId,smid2.sId,True)
#assert (len(pid)>0)
disp_bezier.showPath(r,pp,pid)
curves += [fullBody.getPathAsBezier(int(pid[0]))]
timings += [[ps.pathLength(int(pid[1])), ps.pathLength(int(pid[2])), ps.pathLength(int(pid[3]))]]
pids += pid
pid = fullBody.isDynamicallyReachableFromState(smid2.sId,sf2.sId,True)
#assert (len(pid)>0)
curves += [fullBody.getPathAsBezier(int(pid[0]))]
timings += [[ps.pathLength(int(pid[1])), ps.pathLength(int(pid[2])), ps.pathLength(int(pid[3]))]]
pids += pid
disp_bezier.showPath(r,pp,pid)

"""

n = [0,0,1]
p = [1.2,0.1,0]
moveSphere('s',r,p)

sE,success = StateHelper.addNewContact(sE,lLegId,p,n)
assert(success)
p = [1.15,-0.1,0]