# Poker Cards
#
# Python module for working with poker cards and managing games.
#
# Copyright 2013 Michal Belica <devel@beli.sk>
#
# This file is part of Poker Cards.
#
# Poker Cards is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Poker Cards 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 Poker Cards.  If not, see <http://www.gnu.org/licenses/>.

"""
:mod:`pokercards.cards` -- Represent cards, decks and hands
===========================================================
"""

import random
import logging
from collections import OrderedDict

from const import __version__, suits, ranks, POS_TOP, POS_BOTTOM

logger = logging.getLogger(__name__)

def f_list(lst, sep=','):
    return sep.join([str(x) for x in lst])

def f_lists(lst, sep=' / '):
    return f_list(map(f_list, lst), sep)

class Card(object):
    """Represents a single french-design card with it's rank and suit.

    Cards can be compared and ordered by rank. A card, relative to
    a card of the same rank but different suit, is compared as neither
    higher, lower nor equal.

    :param rank: Either the rank (one of 'A', 'K', 'Q', 'J', 'T', '9', ... '2')
       or rank and suit together (e.g. 'AS', '8H', etc.)
    :type rank: str
    :param suit: The suit, if not given as one string with rank
       (one of 'S', 'H', 'C', 'D' for spade, heart, club or diamond)
    :type suit: str
    :raises: ValueError
    """

    def __init__(self, rank, suit=None):
        if suit is None:
            suit = rank[1]
            rank = rank[0]
        if rank not in ranks:
            raise ValueError('Card(): Invalid rank')
        if suit not in suits:
            raise ValueError('Card(): Invalid suit')
        self.rank = rank
        self.suit = suit

    @classmethod
    def card_list(cls, *args):
        """Create a list of new cards.

        Each argument should describe one card with rank and suit together.

        :param args: One or more cards.
        :type rank: str
        :returns: List of new cards, one for each input parameter.
        :rtype: list of :class:`pokercards.cards.Card` objects
        :raises: ValueError
        """
        lst = []
        for c in args:
            lst.append(cls(c))
        return lst

    def __str__(self):
        return self.rank + self.suit

    def __repr__(self):
        return 'Card(%s, %s)' % (self.rank, self.suit)

    def __hash__(self):
        return (ord(self.rank) << 8) + ord(self.suit)

    def __eq__(self, obj):
        return self.rank == obj.rank and self.suit == obj.suit

    def __ne__(self, obj):
        return self.rank != obj.rank or self.suit != obj.suit

    def __lt__(self, obj):
        return ranks.index(self.rank) > ranks.index(obj.rank)

    def __gt__(self, obj):
        return ranks.index(self.rank) < ranks.index(obj.rank)

    def __le__(self, obj):
        return ranks.index(self.rank) >= ranks.index(obj.rank)

    def __ge__(self, obj):
        return ranks.index(self.rank) <= ranks.index(obj.rank)

class Deck(object):
    """Represents a single deck of 52 :class:`card.Card` objects.

    The deck could be imagined face down on a table. All internal lists
    represent the cards in order from bottom up. So dealing the top
    card means poping last item from the list.
    """
    def __init__(self):
        self.popped = []
        self.discarded = []
        self.active = []
        for s in suits:
            for r in ranks:
                self.active.append(Card(r, s))

    def shuffle(self):
        """Shuffle the deck."""
        random.shuffle(self.active)

    def pop(self):
        """Deal the top card from the deck.

        :returns: :class:`pokercards.cards.Card` instance
        """
        card = self.active.pop()
        self.popped.append(card)
        return card

    def discard(self):
        card = self.active.pop()
        self.discarded.append(card)

    def return_cards(self, cards, pos = POS_BOTTOM):
        if pos not in (POS_BOTTOM, POS_TOP):
            raise Exception('Deck.return_cards(): invalid pos parameter')

        for card in cards[:]:
            if card in self.discarded:
                self.discarded.remove(card)
            elif card in self.popped:
                self.popped.remove(card)
            else:
                raise Exception('Deck.return_cards(): card not among removed cards')

            if pos == POS_BOTTOM:
                self.active[0:0] = [card]
            else:
                self.active.append(card)

    def return_discarded(self, pos = POS_BOTTOM):
        self.return_cards(self.discarded, pos)

    def return_popped(self, pos = POS_BOTTOM):
        self.return_cards(self.popped, pos)

    def return_all(self, pos = POS_BOTTOM):
        self.return_popped()
        self.return_discarded()

    def stats(self):
        return (len(self.active), len(self.popped), len(self.discarded))

    def __str__(self):
        return '[%s]' % ' '.join((str(card) for card in self.active))

    def __repr__(self):
        return 'Deck(%s)' % self.__str__()

class PokerHand(object):
    """Compute the best hand from given cards, implementing traditional
    "high" poker hand ranks.

    The hand object can be given more than five cards (as in Texas
    Hold'em or similar variants) and the evaluation will pick the best
    hand.

    Evaluated :class:`pokercards.cards.PokerHand` objects are
    compared and sorted by the rank of the hand.

    .. attribute:: cards

       List of :class:`pokercards.cards.Card` objects to make the hand
       from. The :meth:`pokercards.cards.PokerHand.evaluate` method
       should be called after manual update to re-evaluate the updated
       hand.

    Following attributes are available after evaluating the hand.

    .. attribute:: hand_rank

       Readonly rank of the hand (0 = high card to 8 = straight flush)

    .. attribute:: hand_cards

       Readonly list of cards which complete the rank.

    .. attribute:: kickers

       Readonly list of extra cards which can break a tie.

    :param cards: List of :class:`pokercards.cards.Card` objects.
    :param evaluate: Evaluate the hand when creating.
    :type evaluate: bool
    """

    def __init__(self, cards, evaluate=True):
        cards.sort(reverse=True)
        self.cards = cards
        if evaluate:
            self.evaluate()

    def evaluate(self):
        """Evaluate the rank of the hand.

        Should be called either implicitly at start by leaving
        parameter ``evaluate`` True when creating the hand or
        explicitly by calling this method later, e.g. after changing
        the :attr:`cards` attribute manually.
        """
        self._eval_hand_rank()
        self._fill_kickers()

    def _by_rank(self, cards=None):
        if cards is None:
            cards = self.cards
        ranked = OrderedDict()
        for card in cards:
            if card.rank in ranked:
                ranked[card.rank].append(card)
            else:
                ranked[card.rank] = [card]
        return ranked

    def _by_suit(self, cards=None):
        if cards is None:
            cards = self.cards
        suited = OrderedDict()
        for card in cards:
            if card.suit in suited:
                suited[card.suit].append(card)
            else:
                suited[card.suit] = [card]
        return suited

    def _find_flushes(self, cards=None):
        if cards is None:
            cards = self.cards
        flushes = []
        for cards in self._by_suit(cards).values():
            l = len(cards)
            if l >= 5:
                for i in xrange(0, l - 4):
                    flushes.append(cards[i:i+5])
        return flushes

    def _find_straights(self, cards=None):
        if cards is None:
            cards = self.cards
        straights = []
        for i in xrange(0, len(cards) - 4):
            card_ranks = [c.rank for c in cards[i:i+5]]
            j = ranks.index(card_ranks[0])
            if card_ranks == ranks[j:j+5]:
                straights.append(cards[i:i+5])
        return straights

    def _fill_kickers(self):
        hand_count = len(self.hand_cards)
        kicker_count = 5 - hand_count
        if kicker_count > 0:
            kickers = self.cards[:]
            for card in self.hand_cards:
                kickers.remove(card)
            self.kickers = kickers[:kicker_count]
        else:
            self.kickers = []
        logger.debug("kickers: %s", f_list(self.kickers))
        logger.debug("--- -------------- ---")

    def _eval_hand_rank(self):
        logger.debug("--- Evaluating %s ---", f_list(self.cards))
        straights = self._find_straights()
        if straights: logger.debug( "straights: %s", f_lists(straights))


#!/usr/bin/python

# (c) 2017, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'certified'}

DOCUMENTATION = '''
module: na_ontap_cluster
short_description: NetApp ONTAP cluster - create, join, add license
extends_documentation_fragment:
    - netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (ng-ansibleteam@netapp.com)
description:
- Create or join or apply licenses to ONTAP clusters
options:
  state:
    description:
    - Whether the specified cluster should exist or not.
    choices: ['present']
    default: present
  cluster_name:
    description:
    - The name of the cluster to manage.
  cluster_ip_address:
    description:
    - IP address of cluster to be joined
  license_code:
    description:
    - License code to be applied to the cluster
  license_package:
    description:
    - License package name of the license to be removed
  node_serial_number:
    description:
    - Serial number of the cluster node

'''

EXAMPLES = """
    - name: Create cluster
      na_ontap_cluster:
        state: present
        cluster_name: new_cluster
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"
    - name: Add license from cluster
      na_ontap_cluster:
        state: present
        cluster_name: FPaaS-A300-01
        license_code: SGHLQDBBVAAAAAAAAAAAAAAAAAAA
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"
    - name: Join cluster
      na_ontap_cluster:
        state: present
        cluster_name: FPaaS-A300
        cluster_ip_address: 10.61.184.181
        hostname: "{{ netapp_hostname }}"
        username: "{{ netapp_username }}"
        password: "{{ netapp_password }}"
"""

RETURN = """
"""

import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils

HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()

class NetAppONTAPCluster(object):
    """
    object initialize and class methods
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            state=dict(required=False, choices=['present'], default='present'),
            cluster_name=dict(required=False, type='str'),
            cluster_ip_address=dict(required=False, type='str'),
            license_code=dict(required=False, type='str'),
            license_package=dict(required=False, type='str'),
            node_serial_number=dict(required=False, type='str')
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True,
            required_together=[
                ['license_package', 'node_serial_number']
            ],
            mutually_exclusive=[
                ['cluster_name', 'cluster_ip_address'],
            ]
        )

        parameters = self.module.params

        # set up state variables
        self.state = parameters['state']
        self.cluster_ip_address = parameters['cluster_ip_address']
        self.cluster_name = parameters['cluster_name']
        self.license_code = parameters['license_code']
        self.license_package = parameters['license_package']
        self.node_serial_number = parameters['node_serial_number']

        if HAS_NETAPP_LIB is False:
            self.module.fail_json(msg="the python NetApp-Lib module is required")
        else:
            self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)

    def create_cluster(self):
        """
        Create a cluster
        """
        cluster_create = netapp_utils.zapi.NaElement.create_node_with_children(
            'cluster-create', **{'cluster-name': self.cluster_name})

        try:
            self.server.invoke_successfully(cluster_create,
                                            enable_tunneling=True)
            return True
        except netapp_utils.zapi.NaApiError as error:
            # Error 36503 denotes node already being used.
            if to_native(error.code) == "36503":
                return False
            else:
                self.module.fail_json(msg='Error creating cluster %s: %s'
                                      % (self.cluster_name, to_native(error)),
                                      exception=traceback.format_exc())

    def cluster_join(self):
        """
        Add a node to an existing cluster
        """
        cluster_add_node = netapp_utils.zapi.NaElement.create_node_with_children(
            'cluster-join', **{'cluster-ip-address': self.cluster_ip_address})

        try:
            self.server.invoke_successfully(cluster_add_node, enable_tunneling=True)
            return True
        except netapp_utils.zapi.NaApiError as error:
            # Error 36503 denotes node already being used.
            if to_native(error.code) == "36503":
                return False
            else:
                self.module.fail_json(msg='Error adding node to cluster %s: %s'
                                      % (self.cluster_name, to_native(error)),
                                      exception=traceback.format_exc())

    def license_v2_add(self):
        """
        Apply a license to cluster
        """
        license_add = netapp_utils.zapi.NaElement.create_node_with_children('license-v2-add')
        license_add.add_node_with_children('codes', **{'license-code-v2': self.license_code})
        try:
            self.server.invoke_successfully(license_add, enable_tunneling=True)

        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error adding license to the cluster %s: %s'
                                  % (self.cluster_name, to_native(error)),
                                  exception=traceback.format_exc())

    def license_v2_delete(self):
        """
        Delete license from cluster
        """
        license_delete = netapp_utils.zapi.NaElement.create_node_with_children(
            'license-v2-delete', **{'package': self.license_package,
                                    'serial-number': self.node_serial_number})
        try:
            self.server.invoke_successfully(license_delete, enable_tunneling=True)
        except netapp_utils.zapi.NaApiError as error:
            self.module.fail_json(msg='Error deleting license from cluster %s : %s'
                                  % (self.cluster_name, to_native(error)),
                                  exception=traceback.format_exc())

    def apply(self):
        """
        Apply action to cluster
        """
        property_changed = False
        create_flag = False
        join_flag = False
        changed = False

        if self.state == 'absent':
            pass
        elif self.state == 'present':  # license add, delete
            changed = True

        if changed:
            if self.module.check_mode:
                pass
            else:


########################################################################
# $HeadURL $
# File: ReplicateAndRegister.py
# Author: Krzysztof.Ciba@NOSPAMgmail.com
# Date: 2013/03/13 18:49:12
########################################################################
""" :mod: ReplicateAndRegister
    ==========================

    .. module: ReplicateAndRegister
    :synopsis: ReplicateAndRegister operation handler
    .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com

    ReplicateAndRegister operation handler
"""
__RCSID__ = "$Id $"
# #
# @file ReplicateAndRegister.py
# @author Krzysztof.Ciba@NOSPAMgmail.com
# @date 2013/03/13 18:49:28
# @brief Definition of ReplicateAndRegister class.

# # imports
import re
# # from DIRAC
from DIRAC import S_OK, S_ERROR, gMonitor, gLogger
from DIRAC.Core.Utilities.Adler import compareAdler

from DIRAC.DataManagementSystem.Client.FTSClient                                  import FTSClient
from DIRAC.DataManagementSystem.Client.DataManager                                import DataManager
from DIRAC.DataManagementSystem.Agent.RequestOperations.DMSRequestOperationsBase  import DMSRequestOperationsBase

from DIRAC.Resources.Storage.StorageElement                                       import StorageElement
from DIRAC.Resources.Catalog.FileCatalog                                          import FileCatalog

def filterReplicas( opFile, logger = None, dataManager = None, seCache = None ):
  """ filter out banned/invalid source SEs """

  if not logger:
    logger = gLogger
  if not dataManager:
    dataManager = DataManager()
  if not seCache:
    seCache = {}

  log = logger.getSubLogger( "filterReplicas" )
  ret = { "Valid" : [], "NoMetadata" : [], "Bad" : [], 'NoReplicas':[], 'NoPFN':[] }

  replicas = dataManager.getActiveReplicas( opFile.LFN )
  if not replicas["OK"]:
    log.error( replicas["Message"] )
    return replicas
  reNotExists = re.compile( "not such file or directory" )
  replicas = replicas["Value"]
  failed = replicas["Failed"].get( opFile.LFN , "" )
  if reNotExists.match( failed.lower() ):
    opFile.Status = "Failed"
    opFile.Error = failed
    return S_ERROR( failed )

  replicas = replicas["Successful"].get( opFile.LFN, {} )

  for repSEName in replicas:

    repSE = seCache[repSEName] if repSEName in seCache else \
            seCache.setdefault( repSEName, StorageElement( repSEName ) )

    pfn = repSE.getPfnForLfn( opFile.LFN )
    if not pfn["OK"] or opFile.LFN not in pfn['Value']['Successful']:
      log.warn( "unable to create pfn for %s lfn at %s: %s" % ( opFile.LFN,
                                                                repSEName,
                                                                pfn.get( 'Message', pfn.get( 'Value', {} ).get( 'Failed', {} ).get( opFile.LFN ) ) ) )
      ret["NoPFN"].append( repSEName )
    else:
      pfn = pfn["Value"]['Successful'][ opFile.LFN ]

      repSEMetadata = repSE.getFileMetadata( pfn )
      error = repSEMetadata.get( 'Message', repSEMetadata.get( 'Value', {} ).get( 'Failed', {} ).get( pfn ) )
      if error:
        log.warn( 'unable to get metadata at %s for %s' % ( repSEName, opFile.LFN ), error.replace( '\n', '' ) )
        if 'File does not exist' in error:
          ret['NoReplicas'].append( repSEName )
        else:
          ret["NoMetadata"].append( repSEName )
      else:
        repSEMetadata = repSEMetadata['Value']['Successful'][pfn]

        seChecksum = repSEMetadata.get( "Checksum" )
        if opFile.Checksum and seChecksum and not compareAdler( seChecksum, opFile.Checksum ) :
          # The checksum in the request may be wrong, check with FC
          fcMetadata = FileCatalog().getFileMetadata( opFile.LFN )
          fcChecksum = fcMetadata.get( 'Value', {} ).get( 'Successful', {} ).get( opFile.LFN, {} ).get( 'Checksum' )
          if fcChecksum and fcChecksum != opFile.Checksum and compareAdler( fcChecksum , seChecksum ):
            opFile.Checksum = fcChecksum
            ret['Valid'].append( repSEName )
          else:
            log.warn( " %s checksum mismatch, request: %s @%s: %s" % ( opFile.LFN,
                                                                       opFile.Checksum,
                                                                       repSEName,
                                                                       seChecksum ) )
            ret["Bad"].append( repSEName )
        else:
          # # if we're here repSE is OK
          ret["Valid"].append( repSEName )

  return S_OK( ret )

########################################################################
class ReplicateAndRegister( DMSRequestOperationsBase ):
  """
  .. class:: ReplicateAndRegister

  ReplicateAndRegister operation handler
  """

  def __init__( self, operation = None, csPath = None ):
    """c'tor

    :param self: self reference
    :param Operation operation: Operation instance
    :param str csPath: CS path for this handler
    """
    super( ReplicateAndRegister, self ).__init__( operation, csPath )
    # # own gMonitor stuff for files
    gMonitor.registerActivity( "ReplicateAndRegisterAtt", "Replicate and register attempted",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "ReplicateOK", "Replications successful",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "ReplicateFail", "Replications failed",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "RegisterOK", "Registrations successful",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "RegisterFail", "Registrations failed",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    # # for FTS
    gMonitor.registerActivity( "FTSScheduleAtt", "Files schedule attempted",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "FTSScheduleOK", "File schedule successful",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    gMonitor.registerActivity( "FTSScheduleFail", "File schedule failed",
                               "RequestExecutingAgent", "Files/min", gMonitor.OP_SUM )
    # # SE cache
    self.seCache = {}

    # Clients
    self.fc = FileCatalog()

  def __call__( self ):
    """ call me maybe """
    # # check replicas first
    checkReplicas = self.__checkReplicas()
    if not checkReplicas["OK"]:
      self.log.error( checkReplicas["Message"] )
    if hasattr( self, "FTSMode" ) and getattr( self, "FTSMode" ):
      bannedGroups = getattr( self, "FTSBannedGroups" ) if hasattr( self, "FTSBannedGroups" ) else ()
      if self.request.OwnerGroup in bannedGroups:
        self.log.verbose( "usage of FTS system is banned for request's owner" )
        return self.dmTransfer()
      return self.ftsTransfer()
    return self.dmTransfer()

  def __checkReplicas( self ):
    """ check done replicas and update file states  """
    waitingFiles = dict( [ ( opFile.LFN, opFile ) for opFile in self.operation
                          if opFile.Status in ( "Waiting", "Scheduled" ) ] )
    targetSESet = set( self.operation.targetSEList )

    replicas = self.fc.getReplicas( waitingFiles.keys() )
    if not replicas["OK"]:
      self.log.error( replicas["Message"] )
      return replicas

    reMissing = re.compile( "no such file or directory" )
    for failedLFN, errStr in replicas["Value"]["Failed"].items():
      waitingFiles[failedLFN].Error = errStr
      if reMissing.search( errStr.lower() ):
        self.log.error( "file %s does not exists" % failedLFN )
        gMonitor.addMark( "ReplicateFail", len( targetSESet ) )
        waitingFiles[failedLFN].Status = "Failed"

    for successfulLFN, reps in replicas["Value"]["Successful"].items():
      if targetSESet.issubset( set( reps ) ):
        self.log.info( "file %s has been replicated to all targets" % successfulLFN )
        waitingFiles[successfulLFN].Status = "Done"

    return S_OK()

  def _addMetadataToFiles( self, toSchedule ):
    """ Add metadata to those files that need to be scheduled through FTS

        toSchedule is a dictionary:
        {'lfn1': [opFile, validReplicas, validTargets], 'lfn2': [opFile, validReplicas, validTargets]}
    """
    if toSchedule:
      self.log.info( "found %s files to schedule, getting metadata from FC" % len( toSchedule ) )
      lfns = toSchedule.keys()
    else:
      self.log.info( "No files to schedule" )
      return S_OK()

    res = self.fc.getFileMetadata( lfns )
    if not res['OK']:
      return res
    else:
      if res['Value']['Failed']:
        self.log.warn( "Can't schedule %d files: problems getting the metadata: %s" % ( len( res['Value']['Failed'] ),
                                                                                ', '.join( res['Value']['Failed'] ) ) )
      metadata = res['Value']['Successful']

    filesToScheduleList = []

    for lfnsToSchedule, lfnMetadata in metadata.items():
      opFileToSchedule = toSchedule[lfnsToSchedule][0]
      opFileToSchedule.GUID = lfnMetadata['GUID']
      opFileToSchedule.Checksum = metadata[lfnsToSchedule]['Checksum']
      opFileToSchedule.ChecksumType = metadata[lfnsToSchedule]['ChecksumType']
      opFileToSchedule.Size = metadata[lfnsToSchedule]['Size']

      filesToScheduleList.append( ( opFileToSchedule.toJSON()['Value'],
                                    toSchedule[lfnsToSchedule][1],
                                    toSchedule[lfnsToSchedule][2] ) )

    return S_OK( filesToScheduleList )

  def _filterReplicas( self, opFile ):
    """ filter out banned/invalid source SEs """
    return filterReplicas( opFile, logger = self.log, dataManager = self.dm, seCache = self.seCache )

  def ftsTransfer( self ):
    """ replicate and register using FTS """

    self.log.info( "scheduling files in FTS..." )

    bannedTargets = self.checkSEsRSS()
    if not bannedTargets['OK']:
      gMonitor.addMark( "FTSScheduleAtt" )
      gMonitor.addMark( "FTSScheduleFail" )
      return bannedTargets

    if bannedTargets['Value']:
      return S_OK( "%s targets are banned for writing" % ",".join( bannedTargets['Value'] ) )

    # Can continue now
    self.log.verbose( "No targets banned for writing" )

    toSchedule = {}

    for opFile in self.getWaitingFilesList():
      opFile.Error = ''
      gMonitor.addMark( "FTSScheduleAtt" )
      # # check replicas
      replicas = self._filterReplicas( opFile )
      if not replicas["OK"]:
        continue
      replicas = replicas["Value"]

      validReplicas = replicas["Valid"]
      noMetaReplicas = replicas["NoMetadata"]
      noReplicas = replicas['NoReplicas']
      badReplicas = replicas['Bad']
      noPFN = replicas['NoPFN']

      if validReplicas:
        validTargets = list( set( self.operation.targetSEList ) - set( validReplicas ) )
        if not validTargets:
          self.log.info( "file %s is already present at all targets" % opFile.LFN )
          opFile.Status = "Done"
        else:
          toSchedule[opFile.LFN] = [ opFile, validReplicas, validTargets ]
      else:
        gMonitor.addMark( "FTSScheduleFail" )
        if noMetaReplicas:
          self.log.warn( "unable to schedule '%s', couldn't get metadata at %s" % ( opFile.LFN, ','.join( noMetaReplicas ) ) )
          opFile.Error = "Couldn't get metadata"
        elif noReplicas:
          self.log.error( "unable to schedule %s, file doesn't exist at %s" % ( opFile.LFN, ','.join( noReplicas ) ) )
          opFile.Error = 'No replicas found'
          opFile.Status = 'Failed'
        elif badReplicas:
          self.log.error( "unable to schedule %s, all replicas have a bad checksum at %s" % ( opFile.LFN, ','.join( badReplicas ) ) )
          opFile.Error = 'All replicas have a bad checksum'
          opFile.Status = 'Failed'
        elif noPFN:
          self.log.warn( "unable to schedule %s, could not get a PFN at %s" % ( opFile.LFN, ','.join( noPFN ) ) )

    res = self._addMetadataToFiles( toSchedule )
    if not res['OK']:
      return res
    else:
      filesToScheduleList = res['Value']

    if filesToScheduleList:

      ftsSchedule = FTSClient().ftsSchedule( self.request.RequestID, self.operation.OperationID, filesToScheduleList )
      if not ftsSchedule["OK"]:
        self.log.error( "Completely failed to schedule to FTS:", ftsSchedule["Message"] )
        return ftsSchedule

      # might have nothing to schedule
      ftsSchedule = ftsSchedule["Value"]
      if not ftsSchedule:
        return S_OK()

      self.log.info( "%d files have been scheduled to FTS" % len( ftsSchedule['Successful'] ) )
      for opFile in self.operation:
        fileID = opFile.FileID
        if fileID in ftsSchedule["Successful"]:
          gMonitor.addMark( "FTSScheduleOK", 1 )
          opFile.Status = "Scheduled"
          self.log.debug( "%s has been scheduled for FTS" % opFile.LFN )
        elif fileID in ftsSchedule["Failed"]:
          gMonitor.addMark( "FTSScheduleFail", 1 )
          opFile.Error = ftsSchedule["Failed"][fileID]
          if 'sourceSURL equals to targetSURL' in opFile.Error:
            # In this case there is no need to continue
            opFile.Status = 'Failed'
          self.log.warn( "unable to schedule %s for FTS: %s" % ( opFile.LFN, opFile.Error ) )
    else:
      self.log.info( "No files to schedule after metadata checks" )

    # Just in case some transfers could not be scheduled, try them with RM
    return self.dmTransfer( fromFTS = True )

  def dmTransfer( self, fromFTS = False ):
    """ replicate and register using dataManager  """
    # # get waiting files. If none just return
    # # source SE
    sourceSE = self.operation.SourceSE if self.operation.SourceSE else None
    if sourceSE:
      # # check source se for read
      bannedSource = self.checkSEsRSS( sourceSE, 'ReadAccess' )
      if not bannedSource["OK"]:
        gMonitor.addMark( "ReplicateAndRegisterAtt", len( self.operation ) )
        gMonitor.addMark( "ReplicateFail", len( self.operation ) )
        return bannedSource

      if bannedSource["Value"]:
        self.operation.Error = "SourceSE %s is banned for reading" % sourceSE
        self.log.info( self.operation.Error )
        return S_OK( self.operation.Error )

    # # check targetSEs for write
    bannedTargets = self.checkSEsRSS()
    if not bannedTargets['OK']:
      gMonitor.addMark( "ReplicateAndRegisterAtt", len( self.operation ) )
      gMonitor.addMark( "ReplicateFail", len( self.operation ) )
      return bannedTargets

    if bannedTargets['Value']:
      self.operation.Error = "%s targets are banned for writing" % ",".join( bannedTargets['Value'] )
      return S_OK( self.operation.Error )

    # Can continue now
    self.log.verbose( "No targets banned for writing" )

    waitingFiles = self.getWaitingFilesList()
    if not waitingFiles:
      return S_OK()
    # # loop over files
    if fromFTS:
      self.log.info( "Trying transfer using replica manager as FTS failed" )
    else:
      self.log.info( "Transferring files using Data manager..." )
    for opFile in waitingFiles:

      gMonitor.addMark( "ReplicateAndRegisterAtt", 1 )
      opFile.Error = ''
      lfn = opFile.LFN

      # Check if replica is at the specified source
      replicas = self._filterReplicas( opFile )
      if not replicas["OK"]:
        self.log.error( replicas["Message"] )
        continue
      replicas = replicas["Value"]


# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program 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 this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

# Most of this has been copied from the __init__.py file for the io_scene__xx
# folders of the standard 2.59 blender package and customised to
# act as a wrapper for the conventional AC3D importer/exporter

import time
import datetime
import bpy
import mathutils
from math import radians
from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty
from bpy_extras.io_utils import ImportHelper, ExportHelper, axis_conversion

bl_info = {
	"name": "AC3D (.ac) format",
	"description": "AC3D model exporter for blender.",
	"author": "Chris Marr",
	"version": (2,0),
	"blender" : (2,6,0),
	"api": 41098,
	"location": "File > Import-Export",
	"warning": "",
	"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Blender-AC3D",
	"tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=29007&group_id=153&atid=468",
	"category": "Import-Export"
}

# To support reload properly, try to access a package var, if it's there,
# reload everything
if "bpy" in locals():
	import imp
	if 'import_ac3d' in locals():
		imp.reload(import_ac3d)
	if 'export_ac3d' in locals():
		imp.reload(export_ac3d)

def menu_func_import(self, context):
	self.layout.operator(ImportAC3D.bl_idname, text='AC3D (.ac)')

def menu_func_export(self, context):
	self.layout.operator(ExportAC3D.bl_idname, text='AC3D (.ac)')

def register():
	bpy.utils.register_module(__name__)
	bpy.types.INFO_MT_file_import.append(menu_func_import)
	bpy.types.INFO_MT_file_export.append(menu_func_export)

def unregister():
	bpy.utils.unregister_module(__name__)
	bpy.types.INFO_MT_file_import.remove(menu_func_import)
	bpy.types.INFO_MT_file_export.remove(menu_func_export)

if __name__ == "__main__":
	register()

class ImportAC3D(bpy.types.Operator, ImportHelper):
	'''Import from AC3D file format (.ac)'''
	bl_idname = 'import_scene.import_ac3d'
	bl_label = 'Import AC3D'
	bl_options = {'PRESET'}

	filename_ext = '.ac'
	filter_glob = StringProperty(default='*.ac', options={'HIDDEN'})

	axis_forward = EnumProperty(
							name="Forward",
							items=(('X', "X Forward", ""),
								('Y', "Y Forward", ""),
								('Z', "Z Forward", ""),
								('-X', "-X Forward", ""),
								('-Y', "-Y Forward", ""),
								('-Z', "-Z Forward", ""),
							),
							default='-Z',
						)

	axis_up = EnumProperty(
							name="Up",
							items=(('X', "X Up", ""),
								('Y', "Y Up", ""),
								('Z', "Z Up", ""),
								('-X', "-X Up", ""),
								('-Y', "-Y Up", ""),
								('-Z', "-Z Up", ""),
							),
							default='Y',
						)

	use_transparency = BoolProperty(
							name="Use Transparency",
							description="Enable transparency for rendering if material alpha < 1.0",
							default=True,
						)

	transparency_method = EnumProperty(
							name="Transparency Method",
							items=(('MASK', "Mask", ""),
								('Z_TRANSPARENCY', "Z_Transp", ""),
								('RAYTRACE', "RayTrace", ""),
							),
							default='Z_TRANSPARENCY',
						)

	use_auto_smooth = BoolProperty(
							name="Auto Smooth",
							description="Use object auto smooth if normal angles are beneath Crease angle",
							default=True,
						)
	use_emis_as_mircol = BoolProperty(
							name="Use Emis as Mirror colour",
							description="Use Emission colour as Mirror colour",
							default=True,
						)

	use_amb_as_mircol = BoolProperty(
							name="Use Amb as Mirror colour",
							description="Use Ambient colour as Mirror colour",
							default=False,
						)
	display_transparency = BoolProperty(
							name="Display Transparency",
							description="Display transparency in main display",
							default=True,
						)
	display_textured_solid = BoolProperty(
							name="Display textured solid",
							description="Show main window with textures applied (transparency works in only in normal direction)",
							default=False,
						)

	def execute(self, context):
		from . import import_ac3d
		keywords = self.as_keywords(ignore=("axis_forward",
											"axis_up",
											"filter_glob",
											))

		global_matrix = axis_conversion(from_forward=self.axis_forward,
										from_up=self.axis_up,
										).to_4x4()

		keywords["global_matrix"] = global_matrix

		t = time.mktime(datetime.datetime.now().timetuple())
		import_ac3d.ImportAC3D(self, context, **keywords)
		t = time.mktime(datetime.datetime.now().timetuple()) - t
		print('Finished importing in', t, 'seconds')

		return {'FINISHED'}

class ExportAC3D(bpy.types.Operator, ExportHelper):
	'''Export to AC3D file format (.ac)'''
	bl_idname = 'export_scene.export_ac3d'
	bl_label = 'Export AC3D'
	bl_options = {'PRESET'}

	filename_ext = '.ac'

	filter_glob = StringProperty(
							default='*.ac',
							options={'HIDDEN'}
							)

	axis_forward = EnumProperty(
								name="Forward",
								items=(('X', "X Forward", ""),
									('Y', "Y Forward", ""),
									('Z', "Z Forward", ""),
									('-X', "-X Forward", ""),
									('-Y', "-Y Forward", ""),
									('-Z', "-Z Forward", ""),
									),
								default='-Z',
								)

	axis_up = EnumProperty(
							name="Up",
							items=(('X', "X Up", ""),
								('Y', "Y Up", ""),
								('Z', "Z Up", ""),
								('-X', "-X Up", ""),
								('-Y', "-Y Up", ""),
								('-Z', "-Z Up", ""),
								),
							default='Y',
							)
	use_render_layers = BoolProperty(
							name="Only Render Layers",
							description="Only export from selected render layers",
							default=True,
							)
	use_selection = BoolProperty(
							name="Selection Only",
							description="Export selected objects only",
							default=False,
							)
#	skip_data = BoolProperty(
#							name="Skip Data",
#							description="don't export mesh names as data fields",
#							default=False,
#							)
#	global_coords = BoolProperty(
#							name="Global Co-ordinates",
#							description="Transform all vertices of all meshes to global coordinates",
#							default=False,
#							)
	mircol_as_emis = BoolProperty(
							name="Mirror col as Emis",
							description="export mirror colour as emissive colour",
							default=True,
							)
	mircol_as_amb = BoolProperty(
							name="Mirror col as Amb",
							description="export mirror colour as ambient colour",
							default=False,
							)
	crease_angle = FloatProperty(
							name="Default Crease Angle",
							description="Default crease angle for exported .ac faces",
							default=radians(35.0),
							options={"ANIMATABLE"},
							unit="ROTATION",
							subtype="ANGLE",
							)
# This behaviour from the original exporter - not applicable?
#	no_split = BoolProperty(
#							name="No Split",
#							description="don't split meshes with multiple textures (or both textured and non-textured polygons)",
#							default=True,
#							)
	def execute(self, context):
		from . import export_ac3d
		keywords = self.as_keywords(ignore=("axis_forward",
											"axis_up",
											"filter_glob",
											"check_existing",
											))


# This file was taken from https://github.com/hsluv/hsluv-python
#
# Copyright (c) 2015 Alexei Boronine
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import math

m = [[3.240969941904521, -1.537383177570093, -0.498610760293],
     [-0.96924363628087, 1.87596750150772, 0.041555057407175],
     [0.055630079696993, -0.20397695888897, 1.056971514242878]]
minv = [[0.41239079926595, 0.35758433938387, 0.18048078840183],
        [0.21263900587151, 0.71516867876775, 0.072192315360733],
        [0.019330818715591, 0.11919477979462, 0.95053215224966]]
refY = 1.0
refU = 0.19783000664283
refV = 0.46831999493879
kappa = 903.2962962
epsilon = 0.0088564516
hex_chars = "0123456789abcdef"

def _distance_line_from_origin(line):
    v = math.pow(line['slope'], 2) + 1
    return math.fabs(line['intercept']) / math.sqrt(v)

def _length_of_ray_until_intersect(theta, line):
    return line['intercept'] / (math.sin(theta) -
                                line['slope'] * math.cos(theta))

def _get_bounds(l):
    result = []
    sub1 = math.pow(l + 16, 3) / 1560896
    if sub1 > epsilon:
        sub2 = sub1
    else:
        sub2 = l / kappa
    _g = 0
    while _g < 3:
        c = _g
        _g = _g + 1
        m1 = m[c][0]
        m2 = m[c][1]
        m3 = m[c][2]
        _g1 = 0
        while _g1 < 2:
            t = _g1
            _g1 = _g1 + 1
            top1 = (284517 * m1 - 94839 * m3) * sub2
            top2 = ((838422 * m3 + 769860 * m2 + 731718 * m1) *
                    l * sub2 - (769860 * t) * l)
            bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
            result.append({'slope': top1 / bottom, 'intercept': top2 / bottom})
    return result

def _max_safe_chroma_for_l(l):
    bounds = _get_bounds(l)
    _hx_min = 1.7976931348623157e+308
    _g = 0
    while _g < 2:
        i = _g
        _g = _g + 1
        length = _distance_line_from_origin(bounds[i])
        if math.isnan(_hx_min):
            pass
        elif math.isnan(length):
            _hx_min = length
        else:
            _hx_min = min(_hx_min, length)
    return _hx_min

def _max_chroma_for_lh(l, h):
    hrad = h / 360 * math.pi * 2
    bounds = _get_bounds(l)
    _hx_min = 1.7976931348623157e+308
    _g = 0
    while _g < len(bounds):
        bound = bounds[_g]
        _g = (_g + 1)
        length = _length_of_ray_until_intersect(hrad, bound)
        if length >= 0:
            if math.isnan(_hx_min):
                pass
            elif math.isnan(length):
                _hx_min = length
            else:
                _hx_min = min(_hx_min, length)
    return _hx_min

def _dot_product(a, b):
    sum = 0
    _g1 = 0
    _g = len(a)
    while _g1 < _g:
        i = _g1
        _g1 = _g1 + 1
        sum += a[i] * b[i]
    return sum

def _from_linear(c):
    if c <= 0.0031308:
        return 12.92 * c
    return 1.055 * math.pow(c, 0.416666666666666685) - 0.055

def _to_linear(c):
    if c > 0.04045:
        return math.pow((c + 0.055) / 1.055, 2.4)
    return c / 12.92

def xyz_to_rgb(_hx_tuple):
    return [
        _from_linear(_dot_product(m[0], _hx_tuple)),
        _from_linear(_dot_product(m[1], _hx_tuple)),
        _from_linear(_dot_product(m[2], _hx_tuple))]

def rgb_to_xyz(_hx_tuple):
    rgbl = [_to_linear(_hx_tuple[0]),
            _to_linear(_hx_tuple[1]),
            _to_linear(_hx_tuple[2])]
    return [_dot_product(minv[0], rgbl),
            _dot_product(minv[1], rgbl),
            _dot_product(minv[2], rgbl)]

def _y_to_l(y):
    if y <= epsilon:
        return y / refY * kappa
    return 116 * math.pow(y / refY, 0.333333333333333315) - 16

def _l_to_y(l):
    if l <= 8:
        return refY * l / kappa
    return refY * math.pow((l + 16) / 116, 3)

def xyz_to_luv(_hx_tuple):
    x = float(_hx_tuple[0])
    y = float(_hx_tuple[1])
    z = float(_hx_tuple[2])
    divider = x + 15 * y + 3 * z
    var_u = 4 * x
    var_v = 9 * y
    if divider != 0:
        var_u = var_u / divider
        var_v = var_v / divider
    else:
        var_u = float("nan")
        var_v = float("nan")
    l = _y_to_l(y)
    if l == 0:
        return [0, 0, 0]
    u = 13 * l * (var_u - refU)
    v = 13 * l * (var_v - refV)
    return [l, u, v]

def luv_to_xyz(_hx_tuple):
    l = float(_hx_tuple[0])
    u = float(_hx_tuple[1])
    v = float(_hx_tuple[2])
    if l == 0:
        return [0, 0, 0]
    var_u = u / (13 * l) + refU
    var_v = v / (13 * l) + refV
    y = _l_to_y(l)
    x = 0 - ((9 * y * var_u) / (((var_u - 4) * var_v) - var_u * var_v))
    z = (((9 * y) - (15 * var_v * y)) - (var_v * x)) / (3 * var_v)
    return [x, y, z]

def luv_to_lch(_hx_tuple):
    l = float(_hx_tuple[0])
    u = float(_hx_tuple[1])
    v = float(_hx_tuple[2])
    _v = (u * u) + (v * v)
    if _v < 0:
        c = float("nan")
    else:
        c = math.sqrt(_v)
    if c < 0.00000001:
        h = 0
    else:
        hrad = math.atan2(v, u)
        h = hrad * 180.0 / 3.1415926535897932
        if h < 0:
            h = 360 + h
    return [l, c, h]

def lch_to_luv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    hrad = h / 360.0 * 2 * math.pi
    u = math.cos(hrad) * c
    v = math.sin(hrad) * c
    return [l, u, v]

def hsluv_to_lch(_hx_tuple):
    h = float(_hx_tuple[0])
    s = float(_hx_tuple[1])
    l = float(_hx_tuple[2])
    if l > 99.9999999:
        return [100, 0, h]
    if l < 0.00000001:
        return [0, 0, h]
    _hx_max = _max_chroma_for_lh(l, h)
    c = _hx_max / 100 * s
    return [l, c, h]

def lch_to_hsluv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    if l > 99.9999999:
        return [h, 0, 100]
    if l < 0.00000001:
        return [h, 0, 0]
    _hx_max = _max_chroma_for_lh(l, h)
    s = c / _hx_max * 100
    return [h, s, l]

def hpluv_to_lch(_hx_tuple):
    h = float(_hx_tuple[0])
    s = float(_hx_tuple[1])
    l = float(_hx_tuple[2])
    if l > 99.9999999:
        return [100, 0, h]
    if l < 0.00000001:
        return [0, 0, h]
    _hx_max = _max_safe_chroma_for_l(l)
    c = _hx_max / 100 * s
    return [l, c, h]

def lch_to_hpluv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    if l > 99.9999999:
        return [h, 0, 100]
    if l < 0.00000001:
        return [h, 0, 0]


import logging
import genshi
from urllib import urlencode

from ckan import plugins as p
from ckan.lib.base import c, model, request, render, h, g
from ckan.lib.base import abort
import ckan.lib.maintain as maintain
import ckan.lib.search as search
import ckan.new_authz

from ckan.controllers.group import GroupController

from ckanext.harvest.plugin import DATASET_TYPE_NAME

try:
    from collections import OrderedDict # 2.7
except ImportError:
    from sqlalchemy.util import OrderedDict

log = logging.getLogger(__name__)

class OrganizationController(GroupController):

    def source_list(self, id, limit=20):
        self.group_type = 'organization'
        context = {'model': model, 'session': model.Session,
                   'user': c.user or c.author,
                   'schema': self._db_to_form_schema(group_type=self.group_type),
                   'for_view': True}
        data_dict = {'id': id}

        # unicode format (decoded from utf8)
        q = c.q = request.params.get('q', '')

        try:
            c.group_dict = self._action('group_show')(context, data_dict)
            c.group = context['group']
        except p.toolkit.ObjectNotFound:
            abort(404, p.toolkit._('Group not found'))
        except p.toolkit.NotAuthorized:
            abort(401, p.toolkit._('Unauthorized to read group %s') % id)

        self._read(id, limit, dataset_type=DATASET_TYPE_NAME)
        return render('source/org_source_list.html')

    def _read(self, id, limit, dataset_type=None):
        ''' This is common code used by both read and bulk_process'''
        self.group_type = 'organization'
        context = {'model': model, 'session': model.Session,
                   'user': c.user or c.author,
                   'schema': self._db_to_form_schema(group_type=self.group_type),
                   'for_view': True, 'extras_as_string': True}

        q = c.q = request.params.get('q', '')
        # Search within group
        if c.group_dict.get('is_organization'):
            q += ' owner_org: "%s"' % c.group_dict.get('id')
        else:
            q += ' groups: "%s"' % c.group_dict.get('name')

        try:
            description_formatted = ckan.misc.MarkdownFormat().to_html(
            c.group_dict.get('description', ''))
            c.description_formatted = genshi.HTML(description_formatted)
        except Exception, e:
            error_msg = "%s" %\
                        p.toolkit._("Cannot render description")
            c.description_formatted = genshi.HTML(error_msg)

        context['return_query'] = True

        # c.group_admins is used by CKAN's legacy (Genshi) templates only,
        # if we drop support for those then we can delete this line.
        c.group_admins = ckan.new_authz.get_group_or_org_admin_ids(c.group.id)

        try:
            page = int(request.params.get('page', 1))
        except ValueError, e:
            abort(400, ('"page" parameter must be an integer'))

        # most search operations should reset the page counter:
        params_nopage = [(k, v) for k, v in request.params.items()
                         if k != 'page']
        #sort_by = request.params.get('sort', 'name asc')
        sort_by = request.params.get('sort', None)

        def search_url(params):
            if self.group_type == 'organization':
                if c.action == 'bulk_process':
                    url = self._url_for(controller='organization',
                                        action='bulk_process',
                                        id=id)
                elif c.action == 'source_list':
                    url = self._url_for('harvest_org_list',
                                        id=id)
                else:
                    url = self._url_for(controller='organization',
                                        action='read',
                                        id=id)
            else:
                url = self._url_for(controller='group', action='read',
                                id=id)
            params = [(k, v.encode('utf-8') if isinstance(v, basestring)
                       else str(v)) for k, v in params]
            return url + u'?' + urlencode(params)

        def drill_down_url(**by):
            return h.add_url_param(alternative_url=None,
                                   controller='group', action='read',
                                   extras=dict(id=c.group_dict.get('name')),
                                   new_params=by)

        c.drill_down_url = drill_down_url

        def remove_field(key, value=None, replace=None):
            return h.remove_url_param(key, value=value, replace=replace,
                                  controller='group', action='read',
                                  extras=dict(id=c.group_dict.get('name')))

        c.remove_field = remove_field

        def pager_url(q=None, page=None):
            params = list(params_nopage)
            params.append(('page', page))
            return search_url(params)

        try:
            c.fields = []
            search_extras = {}
            for (param, value) in request.params.items():
                if not param in ['q', 'page', 'sort'] \
                        and len(value) and not param.startswith('_'):
                    if not param.startswith('ext_'):
                        c.fields.append((param, value))
                        q += ' %s: "%s"' % (param, value)
                    else:
                        search_extras[param] = value

            fq = 'capacity:"public"'
            user_member_of_orgs = [org['id'] for org
                                   in h.organizations_available('read')]

            if (c.group and c.group.id in user_member_of_orgs):
                fq = ''
                context['ignore_capacity_check'] = True

            facets = OrderedDict()

            default_facet_titles = {'groups': p.toolkit._('Groups'),
                              'tags': p.toolkit._('Tags'),
                              'res_format': p.toolkit._('Formats'),
                              'license': p.toolkit._('Licence'), }

            for facet in g.facets:
                if facet in default_facet_titles:
                    facets[facet] = default_facet_titles[facet]
                else:
                    facets[facet] = facet
            if dataset_type:
                fq = fq + 'dataset_type:"{dataset_type}"'.format(dataset_type=dataset_type)

            # Facet titles
            for plugin in p.PluginImplementations(p.IFacets):
                if self.group_type == 'organization':
                    facets = plugin.organization_facets(
                        facets, self.group_type, dataset_type)
                else:
                    facets = plugin.group_facets(
                        facets, self.group_type, dataset_type)

            if 'capacity' in facets and (self.group_type != 'organization' or not user_member_of_orgs):
                del facets['capacity']

            c.facet_titles = facets

            data_dict = {
                'q': q,
                'fq': fq,
                'facet.field': facets.keys(),
                'rows': limit,
                'sort': sort_by,
                'start': (page - 1) * limit,
                'extras': search_extras
            }

            query = p.toolkit.get_action('package_search')(context, data_dict)

            c.page = h.Page(
                collection=query['results'],
                page=page,
                url=pager_url,
                item_count=query['count'],
                items_per_page=limit
            )

            c.facets = query['facets']
            maintain.deprecate_context_item(
              'facets',
              'Use `c.search_facets` instead.')