text:
import itertools
import os
import re
import operator
from decimal import Decimal as D, ROUND_DOWN, ROUND_UP

from django.core import exceptions
from django.template.defaultfilters import date as date_filter
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now, get_current_timezone
from django.utils.translation import ungettext, ugettext_lazy as _
from django.utils.importlib import import_module
from django.utils import six
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.conf import settings

from oscar.core.compat import AUTH_USER_MODEL
from oscar.core.loading import get_class, get_model
from oscar.apps.offer.managers import ActiveOfferManager
from oscar.templatetags.currency_filters import currency
from oscar.models import fields

BrowsableRangeManager = get_class('offer.managers', 'BrowsableRangeManager')

def load_proxy(proxy_class):
    module, classname = proxy_class.rsplit('.', 1)
    try:
        mod = import_module(module)
    except ImportError as e:
        raise exceptions.ImproperlyConfigured(
            "Error importing module %s: %s" % (module, e))
    try:
        return getattr(mod, classname)
    except AttributeError:
        raise exceptions.ImproperlyConfigured(
            "Module %s does not define a %s" % (module, classname))

def range_anchor(range):
    return u'%s' % (
        reverse('dashboard:range-update', kwargs={'pk': range.pk}),
        range.name)

def unit_price(offer, line):
    """
    Return the relevant price for a given basket line.

    This is required so offers can apply in circumstances where tax isn't known
    """
    return line.unit_effective_price

def apply_discount(line, discount, quantity):
    """
    Apply a given discount to the passed basket
    """
    line.discount(discount, quantity, incl_tax=False)

@python_2_unicode_compatible
class ConditionalOffer(models.Model):
    """
    A conditional offer (eg buy 1, get 10% off)
    """
    name = models.CharField(
        _("Name"), max_length=128, unique=True,
        help_text=_("This is displayed within the customer's basket"))
    slug = fields.AutoSlugField(
        _("Slug"), max_length=128, unique=True, populate_from='name')
    description = models.TextField(_("Description"), blank=True,
                                   help_text=_("This is displayed on the offer"
                                               " browsing page"))

    # Offers come in a few different types:
    # (a) Offers that are available to all customers on the site.  Eg a
    #     3-for-2 offer.
    # (b) Offers that are linked to a voucher, and only become available once
    #     that voucher has been applied to the basket
    # (c) Offers that are linked to a user.  Eg, all students get 10% off.  The
    #     code to apply this offer needs to be coded
    # (d) Session offers - these are temporarily available to a user after some
    #     trigger event.  Eg, users coming from some affiliate site get 10%
    #     off.
    SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session")
    TYPE_CHOICES = (
        (SITE, _("Site offer - available to all users")),
        (VOUCHER, _("Voucher offer - only available after entering "
                    "the appropriate voucher code")),
        (USER, _("User offer - available to certain types of user")),
        (SESSION, _("Session offer - temporary offer, available for "
                    "a user for the duration of their session")),
    )
    offer_type = models.CharField(
        _("Type"), choices=TYPE_CHOICES, default=SITE, max_length=128)

    # We track a status variable so it's easier to load offers that are
    # 'available' in some sense.
    OPEN, SUSPENDED, CONSUMED = "Open", "Suspended", "Consumed"
    status = models.CharField(_("Status"), max_length=64, default=OPEN)

    condition = models.ForeignKey(
        'offer.Condition', verbose_name=_("Condition"))
    benefit = models.ForeignKey('offer.Benefit', verbose_name=_("Benefit"))

    # Some complicated situations require offers to be applied in a set order.
    priority = models.IntegerField(
        _("Priority"), default=0,
        help_text=_("The highest priority offers are applied first"))

    # AVAILABILITY

    # Range of availability.  Note that if this is a voucher offer, then these
    # dates are ignored and only the dates from the voucher are used to
    # determine availability.
    start_datetime = models.DateTimeField(
        _("Start date"), blank=True, null=True)
    end_datetime = models.DateTimeField(
        _("End date"), blank=True, null=True,
        help_text=_("Offers are active until the end of the 'end date'"))

    # Use this field to limit the number of times this offer can be applied in
    # total.  Note that a single order can apply an offer multiple times so
    # this is not the same as the number of orders that can use it.
    max_global_applications = models.PositiveIntegerField(
        _("Max global applications"),
        help_text=_("The number of times this offer can be used before it "
                    "is unavailable"), blank=True, null=True)

    # Use this field to limit the number of times this offer can be used by a
    # single user.  This only works for signed-in users - it doesn't really
    # make sense for sites that allow anonymous checkout.
    max_user_applications = models.PositiveIntegerField(
        _("Max user applications"),
        help_text=_("The number of times a single user can use this offer"),
        blank=True, null=True)

    # Use this field to limit the number of times this offer can be applied to
    # a basket (and hence a single order).
    max_basket_applications = models.PositiveIntegerField(
        _("Max basket applications"),
        blank=True, null=True,
        help_text=_("The number of times this offer can be applied to a "
                    "basket (and order)"))

    # Use this field to limit the amount of discount an offer can lead to.
    # This can be helpful with budgeting.
    max_discount = models.DecimalField(
        _("Max discount"), decimal_places=2, max_digits=12, null=True,
        blank=True,
        help_text=_("When an offer has given more discount to orders "
                    "than this threshold, then the offer becomes "
                    "unavailable"))

    # TRACKING

    total_discount = models.DecimalField(
        _("Total Discount"), decimal_places=2, max_digits=12,
        default=D('0.00'))
    num_applications = models.PositiveIntegerField(
        _("Number of applications"), default=0)
    num_orders = models.PositiveIntegerField(
        _("Number of Orders"), default=0)

    redirect_url = fields.ExtendedURLField(
        _("URL redirect (optional)"), blank=True)
    date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)

    objects = models.Manager()
    active = ActiveOfferManager()

    # We need to track the voucher that this offer came from (if it is a
    # voucher offer)
    _voucher = None

    class Meta:
        app_label = 'offer'
        ordering = ['-priority']
        verbose_name = _("Conditional offer")
        verbose_name_plural = _("Conditional offers")

    def save(self, *args, **kwargs):
        # Check to see if consumption thresholds have been broken
        if not self.is_suspended:
            if self.get_max_applications() == 0:
                self.status = self.CONSUMED
            else:
                self.status = self.OPEN

        return super(ConditionalOffer, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('offer:detail', kwargs={'slug': self.slug})

    def __str__(self):
        return self.name

    def clean(self):
        if (self.start_datetime and self.end_datetime and
                self.start_datetime > self.end_datetime):
            raise exceptions.ValidationError(
                _('End date should be later than start date'))

    @property
    def is_open(self):
        return self.status == self.OPEN

    @property
    def is_suspended(self):
        return self.status == self.SUSPENDED

    def suspend(self):
        self.status = self.SUSPENDED
        self.save()
    suspend.alters_data = True

    def unsuspend(self):
        self.status = self.OPEN
        self.save()
    suspend.alters_data = True

    def is_available(self, user=None, test_date=None):
        """
        Test whether this offer is available to be used
        """
        if self.is_suspended:
            return False
        if test_date is None:
            test_date = now()
        predicates = []
        if self.start_datetime:
            predicates.append(self.start_datetime > test_date)
        if self.end_datetime:
            predicates.append(test_date > self.end_datetime)
        if any(predicates):
            return False
        return self.get_max_applications(user) > 0

    def is_condition_satisfied(self, basket):
        return self.condition.proxy().is_satisfied(self, basket)

    def is_condition_partially_satisfied(self, basket):
        return self.condition.proxy().is_partially_satisfied(self, basket)

    def get_upsell_message(self, basket):
        return self.condition.proxy().get_upsell_message(self, basket)

    def apply_benefit(self, basket):
        """
        Applies the benefit to the given basket and returns the discount.
        """
        if not self.is_condition_satisfied(basket):
            return ZERO_DISCOUNT
        return self.benefit.proxy().apply(
            basket, self.condition.proxy(), self)

    def apply_deferred_benefit(self, basket, order, application):
        """
        Applies any deferred benefits.  These are things like adding loyalty
        points to somone's account.
        """
        return self.benefit.proxy().apply_deferred(basket, order, application)

    def set_voucher(self, voucher):
        self._voucher = voucher

    def get_voucher(self):
        return self._voucher

    def get_max_applications(self, user=None):
        """
        Return the number of times this offer can be applied to a basket for a
        given user.
        """
        if self.max_discount and self.total_discount >= self.max_discount:
            return 0

        # Hard-code a maximum value as we need some sensible upper limit for
        # when there are not other caps.
        limits = [10000]
        if self.max_user_applications and user:
            limits.append(max(0, self.max_user_applications -
                          self.get_num_user_applications(user)))
        if self.max_basket_applications:
            limits.append(self.max_basket_applications)
        if self.max_global_applications:
            limits.append(
                max(0, self.max_global_applications - self.num_applications))
        return min(limits)

    def get_num_user_applications(self, user):
        OrderDiscount = get_model('order', 'OrderDiscount')
        aggregates = OrderDiscount.objects.filter(offer_id=self.id,
                                                  order__user=user)\
            .aggregate(total=models.Sum('frequency'))
        return aggregates['total'] if aggregates['total'] is not None else 0

    def shipping_discount(self, charge):
        return self.benefit.proxy().shipping_discount(charge)

    def record_usage(self, discount):
        self.num_applications += discount['freq']
        self.total_discount += discount['discount']
        self.num_orders += 1
        self.save()
    record_usage.alters_data = True

    def availability_description(self):
        """
        Return a description of when this offer is available
        """
        restrictions = self.availability_restrictions()
        descriptions = [r['description'] for r in restrictions]
        return "
".join(descriptions)

    def availability_restrictions(self):  # noqa (too complex (15))
        restrictions = []
        if self.is_suspended:
            restrictions.append({
                'description': _("Offer is suspended"),
                'is_satisfied': False})

        if self.max_global_applications:
            remaining = self.max_global_applications - self.num_applications
            desc = _("Limited to %(total)d uses (%(remainder)d remaining)") \
                % {'total': self.max_global_applications,
                   'remainder': remaining}
            restrictions.append({'description': desc,
                                 'is_satisfied': remaining > 0})

        if self.max_user_applications:
            if self.max_user_applications == 1:
                desc = _("Limited to 1 use per user")
            else:
                desc = _("Limited to %(total)d uses per user") \
                    % {'total': self.max_user_applications}
            restrictions.append({'description': desc,
                                 'is_satisfied': True})

        if self.max_basket_applications:
            if self.max_user_applications == 1:
                desc = _("Limited to 1 use per basket")
            else:
                desc = _("Limited to %(total)d uses per basket") \
                    % {'total': self.max_basket_applications}
            restrictions.append({
                'description': desc,
                'is_satisfied': True})

        def hide_time_if_zero(dt):
            # Only show hours/minutes if they have been specified
            if dt.tzinfo:
                localtime = dt.astimezone(get_current_timezone())
            else:
                localtime = dt
            if localtime.hour == 0 and localtime.minute == 0:
                return date_filter(localtime, settings.DATE_FORMAT)
            return date_filter(localtime, settings.DATETIME_FORMAT)

        if self.start_datetime or self.end_datetime:
            today = now()
            if self.start_datetime and self.end_datetime:
                desc = _("Available between %(start)s and %(end)s") \
                    % {'start': hide_time_if_zero(self.start_datetime),
                       'end': hide_time_if_zero(self.end_datetime)}
                is_satisfied \
                    = self.start_datetime <= today <= self.end_datetime
            elif self.start_datetime:
                desc = _("Available from %(start)s") % {
                    'start': hide_time_if_zero(self.start_datetime)}
                is_satisfied = today >= self.start_datetime
            elif self.end_datetime:
                desc = _("Available until %(end)s") % {
                    'end': hide_time_if_zero(self.end_datetime)}
                is_satisfied = today <= self.end_datetime
            restrictions.append({
                'description': desc,
                'is_satisfied': is_satisfied})

        if self.max_discount:
            desc = _("Limited to a cost of %(max)s") % {
                'max': currency(self.max_discount)}
            restrictions.append({
                'description': desc,
                'is_satisfied': self.total_discount < self.max_discount})

        return restrictions

    @property
    def has_products(self):
        return self.condition.range is not None

    def products(self):
        """
        Return a queryset of products in this offer
        """
        Product = get_model('catalogue', 'Product')
        if not self.has_products:
            return Product.objects.none()

        cond_range = self.condition.range
        if cond_range.includes_all_products:
            # Return ALL the products
            queryset = Product.browsable
        else:
            queryset = cond_range.included_products
        return queryset.filter(is_discountable=True).exclude(
            structure=Product.CHILD)

@python_2_unicode_compatible
class Condition(models.Model):
    """
    A condition for an offer to be applied. You can either specify a custom
    proxy class, or need to specify a type, range and value.
    """
    COUNT, VALUE, COVERAGE = ("Count", "Value", "Coverage")
    TYPE_CHOICES = (
        (COUNT, _("Depends on number of items in basket that are in "
                  "condition range")),
        (VALUE, _("Depends on value of items in basket that are in "
                  "condition range")),
        (COVERAGE, _("Needs to contain a set number of DISTINCT items "
                     "from the condition range")))
    range = models.ForeignKey(
        'offer.Range', verbose_name=_("Range"), null=True, blank=True)
    type = models.CharField(_('Type'), max_length=128, choices=TYPE_CHOICES,
                            blank=True)
    value = fields.PositiveDecimalField(
        _('Value'), decimal_places=2, max_digits=12, null=True, blank=True)

    proxy_class = fields.NullCharField(
        _("Custom class"), max_length=255, unique=True, default=None)

    class Meta:
        app_label = 'offer'
        verbose_name = _("Condition")
        verbose_name_plural = _("Conditions")

    def proxy(self):
        """
        Return the proxy model
        """
        klassmap = {
            self.COUNT: CountCondition,
            self.VALUE: ValueCondition,
            self.COVERAGE: CoverageCondition}
        # Short-circuit logic if current class is already a proxy class.
        if self.__class__ in klassmap.values():
            return self

        field_dict = dict(self.__dict__)
        for field in list(field_dict.keys()):
            if field.startswith('_'):
                del field_dict[field]

        if self.proxy_class:
            klass = load_proxy(self.proxy_class)
            # Short-circuit again.
            if self.__class__ == klass:
                return self
            return klass(**field_dict)
        if self.type in klassmap:
            return klassmap[self.type](**field_dict)
        raise RuntimeError("Unrecognised condition type (%s)" % self.type)

    def __str__(self):
        return self.name

    @property
    def name(self):
        """
        A plaintext description of the condition. Every proxy class has to
        implement it.

        This is used in the dropdowns within the offer dashboard.
        """
        return self.proxy().name

    @property
    def description(self):
        """
        A description of the condition.
        Defaults to the name. May contain HTML.
        """
        return self.name

    def consume_items(self, offer, basket, affected_lines):
        pass

    def is_satisfied(self, offer, basket):
        """
        Determines whether a given basket meets this condition.  This is
        stubbed in this top-class object.  The subclassing proxies are
        responsible for implementing it correctly.
        """
        return False

    def is_partially_satisfied(self, offer, basket):
        """
        Determine if the basket partially meets the condition.  This is useful
        for up-selling messages to entice customers to buy something more in
        order to qualify for an offer.
        """
        return False

    def get_upsell_message(self, offer, basket):
        return None

    def can_apply_condition(self, line):
        """
        Determines whether the condition can be applied to a given basket line
        """
        if not line.stockrecord_id:
            return False
        product = line.product
        return (self.range.contains_product(product)
                and product.get_is_discountable())

    def get_applicable_lines(self, offer, basket, most_expensive_first=True):
        """
        Return line data for the lines that can be consumed by this condition
        """
        line_tuples = []
        for line in basket.all_lines():
            if not self.can_apply_condition(line):
                continue

            price = unit_price(offer, line)
            if not price:
                continue
            line_tuples.append((price, line))
        key = operator.itemgetter(0)
        if most_expensive_first:
            return sorted(line_tuples, reverse=True, key=key)
        return sorted(line_tuples, key=key)

@python_2_unicode_compatible
class Benefit(models.Model):
    range = models.ForeignKey(
        'offer.Range', null=True, blank=True, verbose_name=_("Range"))

    # Benefit types
    PERCENTAGE, FIXED, MULTIBUY, FIXED_PRICE = (
        "Percentage", "Absolute", "Multibuy", "Fixed price")
    SHIPPING_PERCENTAGE, SHIPPING_ABSOLUTE, SHIPPING_FIXED_PRICE = (
        'Shipping percentage', 'Shipping absolute', 'Shipping fixed price')
    TYPE_CHOICES = (
        (PERCENTAGE, _("Discount is a percentage off of the product's value")),
        (FIXED, _("Discount is a fixed amount off of the product's value")),
        (MULTIBUY, _("Discount is to give the cheapest product for free")),
        (FIXED_PRICE,
         _("Get the products that meet the condition for a fixed price")),
        (SHIPPING_ABSOLUTE,
         _("Discount is a fixed amount of the shipping cost")),
        (SHIPPING_FIXED_PRICE, _("Get shipping for a fixed price")),
        (SHIPPING_PERCENTAGE, _("Discount is a percentage off of the shipping"
                                " cost")),
    )
    type = models.CharField(
        _("Type"), max_length=128, choices=TYPE_CHOICES, blank=True)

    # The value to use with the designated type.  This can be either an integer
    # (eg for multibuy) or a decimal (eg an amount) which is slightly
    # confusing.
    value = fields.PositiveDecimalField(
        _("Value"), decimal_places=2, max_digits=12, null=True, blank=True)

    # If this is not set, then there is no upper limit on how many products
    # can be discounted by this benefit.
    max_affected_items = models.PositiveIntegerField(
        _("Max Affected Items"), blank=True, null=True,
        help_text=_("Set this to prevent the discount consuming all items "
                    "within the range that are in the basket."))

    # A custom benefit class can be used instead.  This means the
    # type/value/max_affected_items fields should all be None.
    proxy_class = fields.NullCharField(
        _("Custom class"), max_length=255, unique=True, default=None)

    class Meta:
        app_label = 'offer'
        verbose_name = _("Benefit")
        verbose_name_plural = _("Benefits")

    def proxy(self):
        klassmap = {
            self.PERCENTAGE: PercentageDiscountBenefit,
            self.FIXED: AbsoluteDiscountBenefit,
            self.MULTIBUY: MultibuyDiscountBenefit,
            self.FIXED_PRICE: FixedPriceBenefit,
            self.SHIPPING_ABSOLUTE: ShippingAbsoluteDiscountBenefit,
            self.SHIPPING_FIXED_PRICE: ShippingFixedPriceBenefit,
            self.SHIPPING_PERCENTAGE: ShippingPercentageDiscountBenefit}
        # Short-circuit logic if current class is already a proxy class.
        if self.__class__ in klassmap.values():
            return self

        field_dict = dict(self.__dict__)
        for field in list(field_dict.keys()):
            if field.startswith('_'):
                del field_dict[field]

        if self.proxy_class:
            klass = load_proxy(self.proxy_class)
            # Short-circuit again.
            if self.__class__ == klass:
                return self
            return klass(**field_dict)

        if self.type in klassmap:
            return klassmap[self.type](**field_dict)
        raise RuntimeError("Unrecognised benefit type (%s)" % self.type)

    def __str__(self):
        return self.name

    @property
    def name(self):
        """
        A plaintext description of the benefit. Every proxy class has to
        implement it.

        This is used in the dropdowns within the offer dashboard.
        """
        return self.proxy().name

    @property
    def description(self):
        """
        A description of the benefit.
        Defaults to the name. May contain HTML.
        """
        return self.name

    def apply(self, basket, condition, offer):
        return ZERO_DISCOUNT

    def apply_deferred(self, basket, order, application):
        return None

    def clean(self):
        if not self.type:
            return
        method_name = 'clean_%s' % self.type.lower().replace(' ', '_')
        if hasattr(self, method_name):
            getattr(self, method_name)()

    def clean_multibuy(self):
        if not self.range:
            raise ValidationError(
                _("Multibuy benefits require a product range"))
        if self.value:
            raise ValidationError(
                _("Multibuy benefits don't require a value"))
        if self.max_affected_items:
            raise ValidationError(
                _("Multibuy benefits don't require a 'max affected items' "
                  "attribute"))

    def clean_percentage(self):
        if not self.range:
            raise ValidationError(
                _("Percentage benefits require a product range"))
        if self.value > 100:
            raise ValidationError(
                _("Percentage discount cannot be greater than 100"))

    def clean_shipping_absolute(self):
        if not self.value:
            raise ValidationError(
                _("A discount value is required"))
        if self.range:
            raise ValidationError(
                _("No range should be selected as this benefit does not "
                  "apply to products"))
        if self.max_affected_items:
            raise ValidationError(
                _("Shipping discounts don't require a 'max affected items' "
                  "attribute"))

    def clean_shipping_percentage(self):
        if self.value > 100:
            raise ValidationError(
                _("Percentage discount cannot be greater than 100"))
        if self.range:
            raise ValidationError(
                _("No range should be selected as this benefit does not "
                  "apply to products"))
        if self.max_affected_items:
            raise ValidationError(
                _("Shipping discounts don't require a 'max affected items' "
                  "attribute"))

    def clean_shipping_fixed_price(self):
        if self.range:
            raise ValidationError(
                _("No range should be selected as this benefit does not "
                  "apply to products"))
        if self.max_affected_items:
            raise ValidationError(
                _("Shipping discounts don't require a 'max affected items' "
                  "attribute"))

    def clean_fixed_price(self):
        if self.range:
            raise ValidationError(
                _("No range should be selected as the condition range will "
                  "be used instead."))

    def clean_absolute(self):
        if not self.range:
            raise ValidationError(
                _("Fixed discount benefits require a product range"))
        if not self.value:
            raise ValidationError(
                _("Fixed discount benefits require a value"))

    def round(self, amount):
        """
        Apply rounding to discount amount
        """
        if hasattr(settings, 'OSCAR_OFFER_ROUNDING_FUNCTION'):
            return settings.OSCAR_OFFER_ROUNDING_FUNCTION(amount)
        return amount.quantize(D('.01'), ROUND_DOWN)

    def _effective_max_affected_items(self):
        """
        Return the maximum number of items that can have a discount applied
        during the application of this benefit
        """
        return self.max_affected_items if self.max_affected_items else 10000

    def can_apply_benefit(self, line):
        """
        Determines whether the benefit can be applied to a given basket line
        """
        return line.stockrecord and line.product.is_discountable

    def get_applicable_lines(self, offer, basket, range=None):
        """
        Return the basket lines that are available to be discounted

        :basket: The basket
        :range: The range of products to use for filtering.  The fixed-price
                benefit ignores its range and uses the condition range
        """
        if range is None:
            range = self.range
        line_tuples = []
        for line in basket.all_lines():
            product = line.product

            if (not range.contains(product) or
                    not self.can_apply_benefit(line)):
                continue

            price = unit_price(offer, line)
            if not price:
                # Avoid zero price products
                continue
            if line.quantity_without_discount == 0:
                continue
            line_tuples.append((price, line))

        # We sort lines to be cheapest first to ensure consistent applications
        return sorted(line_tuples, key=operator.itemgetter(0))

    def shipping_discount(self, charge):
        return D('0.00')

@python_2_unicode_compatible
class Range(models.Model):
    """
    Represents a range of products that can be used within an offer.

    Ranges only support adding parent or stand-alone products. Offers will
    consider child products automatically.
    """
    name = models.CharField(_("Name"), max_length=128, unique=True)
    slug = fields.AutoSlugField(
        _("Slug"), max_length=128, unique=True, populate_from="name")

    description = models.TextField(blank=True)

    # Whether this range is public
    is_public = models.BooleanField(
        _('Is public?'), default=False,
        help_text=_("Public ranges have a customer-facing page"))

    includes_all_products = models.BooleanField(
        _('Includes all products?'), default=False)

    included_products = models.ManyToManyField(
        'catalogue.Product', related_name='includes', blank=True,
        verbose_name=_("Included Products"), through='offer.RangeProduct')
    excluded_products = models.ManyToManyField(
        'catalogue.Product', related_name='excludes', blank=True,
        verbose_name=_("Excluded Products"))
    classes = models.ManyToManyField(
        'catalogue.ProductClass', related_name='classes', blank=True,
        verbose_name=_("Product Types"))
    included_categories = models.ManyToManyField(
        'catalogue.Category', related_name='includes', blank=True,
        verbose_name=_("Included Categories"))

    # Allow a custom range instance to be specified
    proxy_class = fields.NullCharField(
        _("Custom class"), max_length=255, default=None, unique=True)

    date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)

    __included_product_ids = None
    __excluded_product_ids = None
    __class_ids = None

    objects = models.Manager()
    browsable = BrowsableRangeManager()

    class Meta:
        app_label = 'offer'
        verbose_name = _("Range")
        verbose_name_plural = _("Ranges")

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse(
            'catalogue:range', kwargs={'slug': self.slug})

    def add_product(self, product, display_order=None):
        """ Add product to the range

        When adding product that is already in the range, prevent re-adding it.
        If display_order is specified, update it.

        Default display_order for a new product in the range is 0; this puts
        the product at the top of the list.
        """
        if product.is_child:
            raise ValueError(
                "Ranges can only contain parent and stand-alone products.")

        initial_order = display_order or 0
        relation, __ = RangeProduct.objects.get_or_create(
            range=self, product=product,
            defaults={'display_order': initial_order})

        if (display_order is not None and
                relation.display_order != display_order):
            relation.display_order = display_order
            relation.save()

    def remove_product(self, product):
        """
        Remove product from range. To save on queries, this function does not
        check if the product is in fact in the range.
        """
        RangeProduct.objects.filter(range=self, product=product).delete()

    def contains_product(self, product):  # noqa (too complex (12))
        """
        Check whether the passed product is part of this range.
        """
        # Child products are never part of the range, but the parent may be.
        if product.is_child:
            product = product.parent

        # Delegate to a proxy class if one is provided
        if self.proxy_class:
            return load_proxy(self.proxy_class)().contains_product(product)

        excluded_product_ids = self._excluded_product_ids()
        if product.id in excluded_product_ids:
            return False
        if self.includes_all_products:
            return True
        if product.product_class_id in self._class_ids():
            return True
        included_product_ids = self._included_product_ids()
        if product.id in included_product_ids:
            return True
        test_categories = self.included_categories.all()
        if test_categories:
            for category in product.get_categories().all():
                for test_category in test_categories:
                    if category == test_category \
                            or category.is_descendant_of(test_category):
                        return True
        return False

    # Shorter alias
    contains = contains_product

    def __get_pks_and_child_pks(self, queryset):
        """
        Expects a product queryset; gets the primary keys of the passed
        products and their children.

        Verbose, but database and memory friendly.
        """
        # One query to get parent and children; [(4, None), (5, 10), (5, 11)]
        pk_tuples_iterable = queryset.values_list('pk', 'children__pk')
        # Flatten list without unpacking; [4, None, 5, 10, 5, 11]
        flat_iterable = itertools.chain.from_iterable(pk_tuples_iterable)
        # Ensure uniqueness and remove None; {4, 5, 10, 11}
        return set(flat_iterable) - {None}

    def _included_product_ids(self):
        if not self.id:
            return []
        if self.__included_product_ids is None:
            self.__included_product_ids = self.__get_pks_and_child_pks(
                self.included_products)
        return self.__included_product_ids

    def _excluded_product_ids(self):
        if not self.id:
            return []
        if self.__excluded_product_ids is None:
            self.__excluded_product_ids = self.__get_pks_and_child_pks(
                self.excluded_products)
        return self.__excluded_product_ids

    def _class_ids(self):
        if None == self.__class_ids:
            self.__class_ids = self.classes.values_list('pk', flat=True)
        return self.__class_ids

    def num_products(self):
        # Delegate to a proxy class if one is provided
        if self.proxy_class:
            return load_proxy(self.proxy_class)().num_products()
        if self.includes_all_products:
            return None
        return self.included_products.all().count()

    @property
    def is_editable(self):
        """
        Test whether this product can be edited in the dashboard
        """
        return not self.proxy_class

class RangeProduct(models.Model):
    """ Allow ordering products inside ranges """
    range = models.ForeignKey('offer.Range')
    product = models.ForeignKey('catalogue.Product')
    display_order = models.IntegerField(default=0)

    class Meta:
        app_label = 'offer'
        unique_together = ('range', 'product')

# ==========
# Conditions
# ==========

class CountCondition(Condition):
    """
    An offer condition dependent on the NUMBER of matching items from the
    basket.
    """
    _description = _("Basket includes %(count)d item(s) from %(range)s")

    @property
    def name(self):
        return self._description % {
            'count': self.value,
            'range': six.text_type(self.range).lower()}

    @property
    def description(self):
        return self._description % {
            'count': self.value,
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Count condition")
        verbose_name_plural = _("Count conditions")

    def is_satisfied(self, offer, basket):
        """
        Determines whether a given basket meets this condition
        """
        num_matches = 0
        for line in basket.all_lines():
            if (self.can_apply_condition(line)
                    and line.quantity_without_discount > 0):
                num_matches += line.quantity_without_discount
            if num_matches >= self.value:
                return True
        return False

    def _get_num_matches(self, basket):
        if hasattr(self, '_num_matches'):
            return getattr(self, '_num_matches')
        num_matches = 0
        for line in basket.all_lines():
            if (self.can_apply_condition(line)
                    and line.quantity_without_discount > 0):
                num_matches += line.quantity_without_discount
        self._num_matches = num_matches
        return num_matches

    def is_partially_satisfied(self, offer, basket):
        num_matches = self._get_num_matches(basket)
        return 0 < num_matches < self.value

    def get_upsell_message(self, offer, basket):
        num_matches = self._get_num_matches(basket)
        delta = self.value - num_matches
        return ungettext('Buy %(delta)d more product from %(range)s',
                         'Buy %(delta)d more products from %(range)s', delta) \
            % {'delta': delta, 'range': self.range}

    def consume_items(self, offer, basket, affected_lines):
        """
        Marks items within the basket lines as consumed so they
        can't be reused in other offers.

        :basket: The basket
        :affected_lines: The lines that have been affected by the discount.
                         This should be list of tuples (line, discount, qty)
        """
        # We need to count how many items have already been consumed as part of
        # applying the benefit, so we don't consume too many items.
        num_consumed = 0
        for line, __, quantity in affected_lines:
            num_consumed += quantity
        to_consume = max(0, self.value - num_consumed)
        if to_consume == 0:
            return

        for __, line in self.get_applicable_lines(offer, basket,
                                                  most_expensive_first=True):
            quantity_to_consume = min(line.quantity_without_discount,
                                      to_consume)
            line.consume(quantity_to_consume)
            to_consume -= quantity_to_consume
            if to_consume == 0:
                break

class CoverageCondition(Condition):
    """
    An offer condition dependent on the number of DISTINCT matching items from
    the basket.
    """
    _description = _("Basket includes %(count)d distinct item(s) from"
                     " %(range)s")

    @property
    def name(self):
        return self._description % {
            'count': self.value,
            'range': six.text_type(self.range).lower()}

    @property
    def description(self):
        return self._description % {
            'count': self.value,
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Coverage Condition")
        verbose_name_plural = _("Coverage Conditions")

    def is_satisfied(self, offer, basket):
        """
        Determines whether a given basket meets this condition
        """
        covered_ids = []
        for line in basket.all_lines():
            if not line.is_available_for_discount:
                continue
            product = line.product
            if (self.can_apply_condition(line) and product.id not in
                    covered_ids):
                covered_ids.append(product.id)
            if len(covered_ids) >= self.value:
                return True
        return False

    def _get_num_covered_products(self, basket):
        covered_ids = []
        for line in basket.all_lines():
            if not line.is_available_for_discount:
                continue
            product = line.product
            if (self.can_apply_condition(line) and product.id not in
                    covered_ids):
                covered_ids.append(product.id)
        return len(covered_ids)

    def get_upsell_message(self, offer, basket):
        delta = self.value - self._get_num_covered_products(basket)
        return ungettext('Buy %(delta)d more product from %(range)s',
                         'Buy %(delta)d more products from %(range)s', delta) \
            % {'delta': delta, 'range': self.range}

    def is_partially_satisfied(self, offer, basket):
        return 0 < self._get_num_covered_products(basket) < self.value

    def consume_items(self, offer, basket, affected_lines):
        """
        Marks items within the basket lines as consumed so they
        can't be reused in other offers.
        """
        # Determine products that have already been consumed by applying the
        # benefit
        consumed_products = []
        for line, __, quantity in affected_lines:
            consumed_products.append(line.product)

        to_consume = max(0, self.value - len(consumed_products))
        if to_consume == 0:
            return

        for line in basket.all_lines():
            product = line.product
            if not self.can_apply_condition(line):
                continue
            if product in consumed_products:
                continue
            if not line.is_available_for_discount:
                continue
            # Only consume a quantity of 1 from each line
            line.consume(1)
            consumed_products.append(product)
            to_consume -= 1
            if to_consume == 0:
                break

    def get_value_of_satisfying_items(self, offer, basket):
        covered_ids = []
        value = D('0.00')
        for line in basket.all_lines():
            if (self.can_apply_condition(line) and line.product.id not in
                    covered_ids):
                covered_ids.append(line.product.id)
                value += unit_price(offer, line)
            if len(covered_ids) >= self.value:
                return value
        return value

class ValueCondition(Condition):
    """
    An offer condition dependent on the VALUE of matching items from the
    basket.
    """
    _description = _("Basket includes %(amount)s from %(range)s")

    @property
    def name(self):
        return self._description % {
            'amount': currency(self.value),
            'range': six.text_type(self.range).lower()}

    @property
    def description(self):
        return self._description % {
            'amount': currency(self.value),
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Value condition")
        verbose_name_plural = _("Value conditions")

    def is_satisfied(self, offer, basket):
        """
        Determine whether a given basket meets this condition
        """
        value_of_matches = D('0.00')
        for line in basket.all_lines():
            if (self.can_apply_condition(line) and
                    line.quantity_without_discount > 0):
                price = unit_price(offer, line)
                value_of_matches += price * int(line.quantity_without_discount)
            if value_of_matches >= self.value:
                return True
        return False

    def _get_value_of_matches(self, offer, basket):
        if hasattr(self, '_value_of_matches'):
            return getattr(self, '_value_of_matches')
        value_of_matches = D('0.00')
        for line in basket.all_lines():
            if (self.can_apply_condition(line) and
                    line.quantity_without_discount > 0):
                price = unit_price(offer, line)
                value_of_matches += price * int(line.quantity_without_discount)
        self._value_of_matches = value_of_matches
        return value_of_matches

    def is_partially_satisfied(self, offer, basket):
        value_of_matches = self._get_value_of_matches(offer, basket)
        return D('0.00') < value_of_matches < self.value

    def get_upsell_message(self, offer, basket):
        value_of_matches = self._get_value_of_matches(offer, basket)
        return _('Spend %(value)s more from %(range)s') % {
            'value': currency(self.value - value_of_matches),
            'range': self.range}

    def consume_items(self, offer, basket, affected_lines):
        """
        Marks items within the basket lines as consumed so they
        can't be reused in other offers.

        We allow lines to be passed in as sometimes we want them sorted
        in a specific order.
        """
        # Determine value of items already consumed as part of discount
        value_consumed = D('0.00')
        for line, __, qty in affected_lines:
            price = unit_price(offer, line)
            value_consumed += price * qty

        to_consume = max(0, self.value - value_consumed)
        if to_consume == 0:
            return

        for price, line in self.get_applicable_lines(
                offer, basket, most_expensive_first=True):
            quantity_to_consume = min(
                line.quantity_without_discount,
                (to_consume / price).quantize(D(1), ROUND_UP))
            line.consume(quantity_to_consume)
            to_consume -= price * quantity_to_consume
            if to_consume <= 0:
                break

# ============
# Result types
# ============

class ApplicationResult(object):
    is_final = is_successful = False
    # Basket discount
    discount = D('0.00')
    description = None

    # Offer applications can affect 3 distinct things
    # (a) Give a discount off the BASKET total
    # (b) Give a discount off the SHIPPING total
    # (a) Trigger a post-order action
    BASKET, SHIPPING, POST_ORDER = 0, 1, 2
    affects = None

    @property
    def affects_basket(self):
        return self.affects == self.BASKET

    @property
    def affects_shipping(self):
        return self.affects == self.SHIPPING

    @property
    def affects_post_order(self):
        return self.affects == self.POST_ORDER

class BasketDiscount(ApplicationResult):
    """
    For when an offer application leads to a simple discount off the basket's
    total
    """
    affects = ApplicationResult.BASKET

    def __init__(self, amount):
        self.discount = amount

    @property
    def is_successful(self):
        return self.discount > 0

    def __str__(self):
        return '<Basket discount of %s>' % self.discount

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.discount)

# Helper global as returning zero discount is quite common
ZERO_DISCOUNT = BasketDiscount(D('0.00'))

class ShippingDiscount(ApplicationResult):
    """
    For when an offer application leads to a discount from the shipping cost
    """
    is_successful = is_final = True
    affects = ApplicationResult.SHIPPING

SHIPPING_DISCOUNT = ShippingDiscount()

class PostOrderAction(ApplicationResult):
    """
    For when an offer condition is met but the benefit is deferred until after
    the order has been placed.  Eg buy 2 books and get 100 loyalty points.
    """
    is_final = is_successful = True
    affects = ApplicationResult.POST_ORDER

    def __init__(self, description):
        self.description = description

# ========
# Benefits
# ========

class PercentageDiscountBenefit(Benefit):
    """
    An offer benefit that gives a percentage discount
    """
    _description = _("%(value)s%% discount on %(range)s")

    @property
    def name(self):
        return self._description % {
            'value': self.value,
            'range': self.range.name}

    @property
    def description(self):
        return self._description % {
            'value': self.value,
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Percentage discount benefit")
        verbose_name_plural = _("Percentage discount benefits")

    def apply(self, basket, condition, offer, discount_percent=None,
              max_total_discount=None):
        if discount_percent is None:
            discount_percent = self.value

        discount_amount_available = max_total_discount

        line_tuples = self.get_applicable_lines(offer, basket)

        discount = D('0.00')
        affected_items = 0
        max_affected_items = self._effective_max_affected_items()
        affected_lines = []
        for price, line in line_tuples:
            if affected_items >= max_affected_items:
                break
            if discount_amount_available == 0:
                break

            quantity_affected = min(line.quantity_without_discount,
                                    max_affected_items - affected_items)
            line_discount = self.round(discount_percent / D('100.0') * price
                                       * int(quantity_affected))

            if discount_amount_available is not None:
                line_discount = min(line_discount, discount_amount_available)
                discount_amount_available -= line_discount

            apply_discount(line, line_discount, quantity_affected)

            affected_lines.append((line, line_discount, quantity_affected))
            affected_items += quantity_affected
            discount += line_discount

        if discount > 0:
            condition.consume_items(offer, basket, affected_lines)
        return BasketDiscount(discount)

class AbsoluteDiscountBenefit(Benefit):
    """
    An offer benefit that gives an absolute discount
    """
    _description = _("%(value)s discount on %(range)s")

    @property
    def name(self):
        return self._description % {
            'value': currency(self.value),
            'range': self.range.name.lower()}

    @property
    def description(self):
        return self._description % {
            'value': currency(self.value),
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Absolute discount benefit")
        verbose_name_plural = _("Absolute discount benefits")

    def apply(self, basket, condition, offer, discount_amount=None,
              max_total_discount=None):
        if discount_amount is None:
            discount_amount = self.value

        # Fetch basket lines that are in the range and available to be used in
        # an offer.
        line_tuples = self.get_applicable_lines(offer, basket)

        # Determine which lines can have the discount applied to them
        max_affected_items = self._effective_max_affected_items()
        num_affected_items = 0
        affected_items_total = D('0.00')
        lines_to_discount = []
        for price, line in line_tuples:
            if num_affected_items >= max_affected_items:
                break
            qty = min(line.quantity_without_discount,
                      max_affected_items - num_affected_items)
            lines_to_discount.append((line, price, qty))
            num_affected_items += qty
            affected_items_total += qty * price

        # Ensure we don't try to apply a discount larger than the total of the
        # matching items.
        discount = min(discount_amount, affected_items_total)
        if max_total_discount is not None:
            discount = min(discount, max_total_discount)

        if discount == 0:
            return ZERO_DISCOUNT

        # Apply discount equally amongst them
        affected_lines = []
        applied_discount = D('0.00')
        for i, (line, price, qty) in enumerate(lines_to_discount):
            if i == len(lines_to_discount) - 1:
                # If last line, then take the delta as the discount to ensure
                # the total discount is correct and doesn't mismatch due to
                # rounding.
                line_discount = discount - applied_discount
            else:
                # Calculate a weighted discount for the line
                line_discount = self.round(
                    ((price * qty) / affected_items_total) * discount)
            apply_discount(line, line_discount, qty)
            affected_lines.append((line, line_discount, qty))
            applied_discount += line_discount

        condition.consume_items(offer, basket, affected_lines)

        return BasketDiscount(discount)

class FixedPriceBenefit(Benefit):
    """
    An offer benefit that gives the items in the condition for a
    fixed price.  This is useful for "bundle" offers.

    Note that we ignore the benefit range here and only give a fixed price
    for the products in the condition range.  The condition cannot be a value
    condition.

    We also ignore the max_affected_items setting.
    """
    _description = _("The products that meet the condition are sold "
                     "for %(amount)s")

    @property
    def name(self):
        return self._description % {
            'amount': currency(self.value)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Fixed price benefit")
        verbose_name_plural = _("Fixed price benefits")

    def apply(self, basket, condition, offer):  # noqa (too complex (10))
        if isinstance(condition, ValueCondition):
            return ZERO_DISCOUNT

        # Fetch basket lines that are in the range and available to be used in
        # an offer.
        line_tuples = self.get_applicable_lines(offer, basket,
                                                range=condition.range)
        if not line_tuples:
            return ZERO_DISCOUNT

        # Determine the lines to consume
        num_permitted = int(condition.value)
        num_affected = 0
        value_affected = D('0.00')
        covered_lines = []
        for price, line in line_tuples:
            if isinstance(condition, CoverageCondition):
                quantity_affected = 1
            else:
                quantity_affected = min(
                    line.quantity_without_discount,
                    num_permitted - num_affected)
            num_affected += quantity_affected
            value_affected += quantity_affected * price
            covered_lines.append((price, line, quantity_affected))
            if num_affected >= num_permitted:
                break
        discount = max(value_affected - self.value, D('0.00'))
        if not discount:
            return ZERO_DISCOUNT

        # Apply discount to the affected lines
        discount_applied = D('0.00')
        last_line = covered_lines[-1][1]
        for price, line, quantity in covered_lines:
            if line == last_line:
                # If last line, we just take the difference to ensure that
                # rounding doesn't lead to an off-by-one error
                line_discount = discount - discount_applied
            else:
                line_discount = self.round(
                    discount * (price * quantity) / value_affected)
            apply_discount(line, line_discount, quantity)
            discount_applied += line_discount
        return BasketDiscount(discount)

class MultibuyDiscountBenefit(Benefit):
    _description = _("Cheapest product from %(range)s is free")

    @property
    def name(self):
        return self._description % {
            'range': self.range.name.lower()}

    @property
    def description(self):
        return self._description % {
            'range': range_anchor(self.range)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Multibuy discount benefit")
        verbose_name_plural = _("Multibuy discount benefits")

    def apply(self, basket, condition, offer):
        line_tuples = self.get_applicable_lines(offer, basket)
        if not line_tuples:
            return ZERO_DISCOUNT

        # Cheapest line gives free product
        discount, line = line_tuples[0]
        apply_discount(line, discount, 1)

        affected_lines = [(line, discount, 1)]
        condition.consume_items(offer, basket, affected_lines)

        return BasketDiscount(discount)

# =================
# Shipping benefits
# =================

class ShippingBenefit(Benefit):

    def apply(self, basket, condition, offer):
        condition.consume_items(offer, basket, affected_lines=())
        return SHIPPING_DISCOUNT

    class Meta:
        app_label = 'offer'
        proxy = True

class ShippingAbsoluteDiscountBenefit(ShippingBenefit):
    _description = _("%(amount)s off shipping cost")

    @property
    def name(self):
        return self._description % {
            'amount': currency(self.value)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Shipping absolute discount benefit")
        verbose_name_plural = _("Shipping absolute discount benefits")

    def shipping_discount(self, charge):
        return min(charge, self.value)

class ShippingFixedPriceBenefit(ShippingBenefit):
    _description = _("Get shipping for %(amount)s")

    @property
    def name(self):
        return self._description % {
            'amount': currency(self.value)}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Fixed price shipping benefit")
        verbose_name_plural = _("Fixed price shipping benefits")

    def shipping_discount(self, charge):
        if charge < self.value:
            return D('0.00')
        return charge - self.value

class ShippingPercentageDiscountBenefit(ShippingBenefit):
    _description = _("%(value)s%% off of shipping cost")

    @property
    def name(self):
        return self._description % {
            'value': self.value}

    class Meta:
        app_label = 'offer'
        proxy = True
        verbose_name = _("Shipping percentage discount benefit")
        verbose_name_plural = _("Shipping percentage discount benefits")

    def shipping_discount(self, charge):
        discount = charge * self.value / D('100.0')
        return discount.quantize(D('0.01'))

class RangeProductFileUpload(models.Model):
    range = models.ForeignKey('offer.Range', related_name='file_uploads',
                              verbose_name=_("Range"))
    filepath = models.CharField(_("File Path"), max_length=255)
    size = models.PositiveIntegerField(_("Size"))
    uploaded_by = models.ForeignKey(AUTH_USER_MODEL,
                                    verbose_name=_("Uploaded By"))
    date_uploaded = models.DateTimeField(_("Date Uploaded"), auto_now_add=True)

    PENDING, FAILED, PROCESSED = 'Pending', 'Failed', 'Processed'
    choices = (
        (PENDING, PENDING),
        (FAILED, FAILED),
        (PROCESSED, PROCESSED),
    )
    status = models.CharField(_("Status"), max_length=32, choices=choices,
                              default=PENDING)
    error_message = models.CharField(_("Error Message"), max_length=255,
                                     blank=True)

    # Post-processing audit fields
    date_processed = models.DateTimeField(_("Date Processed"), null=True)
    num_new_skus = models.PositiveIntegerField(_("Number of New SKUs"),
                                               null=True)
    num_unknown_skus = models.PositiveIntegerField(_("Number of Unknown SKUs"),
                                                   null=True)
    num_duplicate_skus = models.PositiveIntegerField(
        _("Number of Duplicate SKUs"), null=True)

    class Meta:
        app_label = 'offer'
        ordering = ('-date_uploaded',)
        verbose_name = _("Range Product Uploaded File")
        verbose_name_plural = _("Range Product Uploaded Files")

    @property
    def filename(self):
        return os.path.basename(self.filepath)

    def mark_as_failed(self, message=None):
        self.date_processed = now()
        self.error_message = message
        self.status = self.FAILED
        self.save()

    def mark_as_processed(self, num_new, num_unknown, num_duplicate):
        self.status = self.PROCESSED
        self.date_processed = now()
        self.num_new_skus = num_new
        self.num_unknown_skus = num_unknown
        self.num_duplicate_skus = num_duplicate
        self.save()

    def was_processing_successful(self):
        return self.status == self.PROCESSED

    def process(self):
        """
        Process the file upload and add products to the range
        """
        all_ids = set(self.extract_ids())
        products = self.range.included_products.all()
        existing_skus = products.values_list(
            'stockrecords__partner_sku', flat=True)
        existing_skus = set(filter(bool, existing_skus))
        existing_upcs = products.values_list('upc', flat=True)
        existing_upcs = set(filter(bool, existing_upcs))
        existing_ids = existing_skus.union(existing_upcs)
        new_ids = all_ids - existing_ids

        Product = models.get_model('catalogue', 'Product')
        products = Product._default_manager.filter(
            models.Q(stockrecords__partner_sku__in=new_ids) |
            models.Q(upc__in=new_ids))
        for product in products:
            self.range.add_product(product)

        # Processing stats
        found_skus = products.values_list(
            'stockrecords__partner_sku', flat=True)
        found_skus = set(filter(bool, found_skus))
        found_upcs = set(filter(bool, products.values_list('upc', flat=True)))
        found_ids = found_skus.union(found_upcs)
        missing_ids = new_ids - found_ids
        dupes = set(all_ids).intersection(existing_ids)

        self.mark_as_processed(products.count(), len(missing_ids), len(dupes))

    def extract_ids(self):
        """
        Extract all SKU- or UPC-like strings from the file
        """
        for line in open(self.filepath, 'r'):
            for id in re.split('[^\w:\.-]', line):
                if id:
                    yield id

    def delete_file(self):
        os.unlink(self.filepath)


text:
var util = require('util');
var fs = require('fs');
var async = require('async');
var path = require('path');

var session;

var now = new Date();
var uniqueName = 'Name' + now.getTime();
var resourceName = 'Pic' + now.getTime();

module.exports = function(sessionParam,cb) {
    session = sessionParam;
    console.log('Starting test');
    deleteAll("Contact",function(err) {
        exitOnErr(err);
        deleteAll("Document",function(err) {
            exitOnErr(err);
            upload1();
        });
    });
}

function exitOnErr(err) {
    if(err) {
        console.log(err);
        process.exit(1);
    }
}

function deleteAll(type,cb) {
    session.directive({
        op: "SELECT",
        targetType: type,
        properties: ["id"]
    },function(err,result) {
        if(result.results.length === 0) return cb();
        console.log("Existing rows in " + type);
        console.dir(result.results);
        console.log('Deleting ' + result.results.length + ' rows in ' + type);
        async.forEachSeries(result.results,function(row,cb2) {
            session.directive({
                op: "DELETE",
                targetType: type,
                where: {id: row.id}
            },cb2);
        },cb);
    });
}

function upload1() {
    console.log("Uploading resource " + uniqueName);

    resourceName = "/photos/"+uniqueName;

    var d = {assetType:'resource',"filename":"icon.gif","contentType":"image/gif","resourceName":resourceName};
    var data = fs.readFileSync(path.join(__dirname,'icon.gif'));

    d.file = {filename:d.filename, contents: data};

    session.console("updateGlobalAsset",d,function(err,result) {
        if(!err) {
            // do something if needed?
            console.log(result);
        }
        insert1(err,result);
    });
}

function insert1() {
    console.log("Inserting, name is " + uniqueName);
    session.directive({
        op:'INSERT',
        targetType: 'Contact',
        values: {
            Name: 'Adam',
            LastName: uniqueName,
            AssistantName: resourceName
        }
    },wait1);
}

function wait1(err,res) {
    //console.dir(res);
    console.log('wait1...');
    setTimeout(select1,5000);
}

function select1(res) {
    session.directive({
        op: "SELECT",
        targetType:"Contact",
        properties:["id","LastName","AssistantName","Birthdate"],
        where: {LastName:uniqueName}
    },update1);
}

function update1(err,res) {
    if(res && res.results && res.results[0] && res.results[0].id) {
        var id = res.results[0].id;
        session.directive({
            op:'UPDATE',
            targetType: 'Contact',
            values: {
                AssistantName: resourceName,
            },
            where : {id: id}
        },wait2);
    } else {
        console.dir(res);
        console.log("\nres.results[0].id is not present!");
        console.log("TEST FAILED!");
        process.exit(1);
    }

}

function wait2(err,res) {
    console.dir(res);
    console.log('wait2...');
    setTimeout(select2,2000);
}

function select2(err,res) {
    //console.log('downloading ' + uniqueName);
    session.directive({
        op: "SELECT",
        targetType:"Contact",
        properties:["id","LastName","AssistantName","Birthdate"],
        where: {LastName:uniqueName}
    },wait3);
}

function wait3(err,res) {
    if(res.results.length != 1) {
        console.dir(res);
        console.log("TEST FAILED!");
        process.exit(1);
    }
    console.dir(res);
    console.log('wait3...');
    setTimeout(select3,2000);
}

function select3(err,res) {
    session.directive({
        op: "SELECT",
        targetType:"Document",
        properties:["id","Name","ContentType","Body"]
    },done);
}

function done(err,res) {
    if(res && res.results) {
        res.results.forEach(function(row) {
            if(row.Name === uniqueName) {
                console.log("TEST PASSED!");
                process.exit(0);
            }
        });
     }
    console.dir(res);
    console.log(uniqueName + " not found!");
    console.log("TEST FAILED!");
    process.exit(1);
}

redpajama_set_name: RedPajamaGithub


text:
/*
@date  : 11.05.2015
@author: Pavel Durov
@note  :  In order to use ActionSheetProxy on Universal Windows Platform -
		  Make sure that you are referencing base.js and ui.js
 */
function ActionSheet() { /* CTOR...*/ }

ActionSheet.prototype.show = function (options, successCallback, errorCallback) {

    ActionSheet.prototype.successCallBack = successCallback;
    ActionSheet.prototype.errorCallback = errorCallback;

    if (cordova.platformId == "windows") {
        ActionSheet.prototype._injectWinJsFlyoutHTML();
    }

    if (options) {
        var actionSheetProxyFlyoutDiv = document.getElementById("actionSheetProxyFlyoutDiv");
        //Settings popup
        this._clearChildren(actionSheetProxyFlyoutDiv);

        if (options.title) {    //Append Title
            this._addTitle(options.title, actionSheetProxyFlyoutDiv);
        }

        if (!options.destructiveButtonLast && options.addDestructiveButtonWithLabel) {    //Generate Desctructive Button
            this._addDestructiveButton(options.addDestructiveButtonWithLabel, actionSheetProxyFlyoutDiv);
        }

        if (options.buttonLabels && options.buttonLabels instanceof Array) {    //Appending Buttons to main Flyout Div
            this._addbuttons(options.buttonLabels, actionSheetProxyFlyoutDiv);
        }

        if (options.destructiveButtonLast && options.addDestructiveButtonWithLabel) {    //Generate Desctructive Button
            this._addDestructiveButton(options.addDestructiveButtonWithLabel, actionSheetProxyFlyoutDiv);
        }

        if (options.winphoneEnableCancelButton && options.addCancelButtonWithLabel) {

            this._addCancelButton(options.addCancelButtonWithLabel, actionSheetProxyFlyoutDiv);
        }

        var fly = document.getElementById("fly-test");
        WinJS.UI.process(fly).done(function () {

            if (fly && fly.winControl) {
                //var bodys = document.getElementsByClassName('bodyClass')[0];
                var anchor = document.getElementById("actionSheetSetPoint");
                if (anchor) {
                    fly.winControl.show(anchor, "bottom");
                }

            } else {
                console.log("winControl is undefined");
            }
        });
    }

    document.addEventListener('backbutton', window.plugins.actionsheet.hide, false)
};

ActionSheet.prototype.hide = function (options, successCallback, errorCallback) {
    console.log("//\\//\\//\\//\\//\\ ActionSheet.prototype.hide ");

    var fly = document.getElementById("fly-test");

    WinJS.UI.process(fly).done(function () {
        if (fly.winControl && fly.winControl.hide) {
            //WinJS.UI.Animation.hidePopup(fly).done( /* Your success and error handlers */);
            fly.winControl.hide();
        }
    });
};

ActionSheet.prototype.ANDROID_THEMES = {
    THEME_TRADITIONAL: 1, // default
    THEME_HOLO_DARK: 2,
    THEME_HOLO_LIGHT: 3,
    THEME_DEVICE_DEFAULT_DARK: 4,
    THEME_DEVICE_DEFAULT_LIGHT: 5
};

ActionSheet.prototype.install = function () {
    if (!window.plugins) {
        window.plugins = {};
    }
    window.plugins.actionsheet = new ActionSheet();

    return window.plugins.actionsheet;
};

cordova.addConstructor(ActionSheet.prototype.install);
cordova.commandProxy.add("ActionSheet", ActionSheet);

//Helpers

ActionSheet.prototype._addTitle = function (label, destinationCtrl) {
    var textCtrl = this._generateTitle(label)
    destinationCtrl.appendChild(textCtrl);
}

ActionSheet.prototype._addDestructiveButton = function (label, destinationCtrl) {
    if (label && destinationCtrl) {
        var destructive_btn = this._generateButton(label, this._getDestructiveButtonStyle());
        destinationCtrl.appendChild(destructive_btn);
    }
}

ActionSheet.prototype._addCancelButton = function (label, destinationCtrl) {
    if (label && destinationCtrl) {
        var cancel_btn = this._generateButton(label, this._getCancelButtonStyle());
        cancel_btn.onclick = function () {
            ActionSheet.prototype.hide();
        }

        destinationCtrl.appendChild(cancel_btn);
    }
}

ActionSheet.prototype._addbuttons = function (lables, destinationCtrl) {
    if (lables && destinationCtrl) {
        for (var i = 0; i < lables.length; i++) {
            var btn = this._generateButton(lables[i], this._getButtonStyle());
	    (function (i) {
            	btn.onclick = function () {
                    if (ActionSheet.prototype.successCallBack) {
                    	ActionSheet.prototype.successCallBack(i + 1);
                    	ActionSheet.prototype.hide();
                    }
            	}
            })(i);
            destinationCtrl.appendChild(btn);
        }
    }
}

ActionSheet.prototype._clearChildren = function (element) {
    if (element && element.hasChildNodes) {
        while (element.hasChildNodes()) {
            element.removeChild(element.lastChild);
        }
    }
}

ActionSheet.prototype._generateButton = function (content, style) {

    var btn = document.createElement("input");
    btn.setAttribute("type", "button");
    btn.setAttribute("value", content);

    btn.setAttribute("style", style);//;

    return btn;
}

//Style
ActionSheet.prototype._getButtonStyle = function () {
    return "display: table-row; font-size: 20px; background-color :black;" +
            " color: white; width: 98%; position: relative; margin: 1%; background: black; margin-top: 20px; height: 60px;"
}
//The only diffrence beweet regulat button and cancel vuttokn styles is WIDTH!!
ActionSheet.prototype._getCancelButtonStyle = function () {
    return "display: table-row; font-size: 20px; background-color :black;" +
            " color: white; width: 49%; position: relative; margin: 1%; background: black; margin-top: 20px; height: 60px;"
}
//The only diffrence beweet destructive button and regular is its color (orange)!
ActionSheet.prototype._getDestructiveButtonStyle = function () {
    return "display: table-row; font-size: 20px; background-color : black;" +
            " color: orange; width: 98%; position: relative; margin: 1%; background: black; margin-top: 20px; height: 60px;"
}

ActionSheet.prototype._getMainDivStyle = function () {
    return "position:absolute; width:99%; display: table; background-color: #4F4F4F; padding: 15px; visibility:collapse ; text-align : center;";
}

ActionSheet.prototype._getTitleStyle = function () {
    return "color:white; font-size: 30px; ";
}

ActionSheet.prototype._generateTitle = function (textLebel) {
    var spanCtrl = document.createElement("span");
    spanCtrl.setAttribute("style", this._getTitleStyle());
    spanCtrl.innerHTML = textLebel;
    return spanCtrl;
}

ActionSheet.prototype._injectWinJsFlyoutHTML = function () {
    var divSetPoint = document.createElement("div");
    divSetPoint.setAttribute("id", "actionSheetSetPoint");
    divSetPoint.setAttribute("aria-haspopup", "true");
    divSetPoint.setAttribute("style", "visibility:collapse");

    var flyoutDiv = document.createElement("div");
    flyoutDiv.setAttribute("data-win-control", "WinJS.UI.Flyout");
    flyoutDiv.setAttribute("id", "fly-test");
    flyoutDiv.setAttribute("style", this._getMainDivStyle());

    var internalDiv = document.createElement("div");
    internalDiv.setAttribute("id", "actionSheetProxyFlyoutDiv");

    flyoutDiv.appendChild(internalDiv);

    document.body.appendChild(flyoutDiv);
    document.body.appendChild(divSetPoint);
}


text:
<!DOCTYPE html>

        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="webjars/bootstrap/3.3.5/dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">

        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" integrity="sha384-aUGj/X2zp5rLCbBxumKTCw2Z50WgIr1vs/PFN4praOTvYXWlVyh2UtNUU0KAUhAX" crossorigin="anonymous">

        <link type="text/css" rel="stylesheet" href="css/styles.css"/>

                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                        Toggle navigation

                    </button>
                    Agenda Online

                            Agendamentos (current)

                            Clientes

                            Serviços

                             Usuário

 Configurações

 Sair

© 2015


text:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class RingController : MonoBehaviour {

	// ring stack vars
	public float speed; // defines the spin rate of each ring.
	public float vertOrder; // defines the vertical order of the ring in the stack
	private bool spin = true; // a flag used to determin if this ring should spin
	private bool focus = false; // a flag used to determine if this ring is the point of focus of the main camera view.

	// ring expand coroutine params
	private Vector3 startV3; // defines the starting location of a ring
	private Vector3 endV3; // defines the ending location of a ring
	private float fract; // a mulitplier used to influence transition speed
	private float startTime;
	private float journeyLength;

	// the vert order of the broadcasting ring
	private float currentSrcVertOrder;

	// ring close coroutine params
	public Vector3 originPosition;
	public Vector3 shiftedPosition;
	private float resetStartTime;
	private float resetJourneyLength;
	public float zone = 1000;
	private float resetFract;

	// title popup label
	private Popup oppPopup;
	private bool popupOn = false;
	private Vector3 fromPoint;
	private Vector3 hitPoint;
	private float popupStartTime;
	private float popdownStartTime;
	public float popupWaitTime = 1f;

	// default title text.
	public string titleTextValue = "Loading...";

	// ring aligment rotation vars
	private Quaternion startRotation, endRotation;
	private float period, time;
	private float transFract = 0.05f;

	// used for ring shifting
	private bool ringIsExpanded = false;
	private bool ringIsExpanding = false;
	private bool ringIsClosing = false;

	// triggered by the raycast hit event from the main view
	void HitByMainView (Vector3[] tempStorage) {

		fromPoint = tempStorage[0];
		hitPoint = tempStorage[1];
		focus = true;

	}

	void Start(){

		fract = 10.0f;
		resetFract = 150.0f;

		originPosition = transform.position;
		shiftedPosition = transform.position;

		GameObject popObject = (GameObject)Instantiate(Resources.Load ("Label Holder"));
		oppPopup = popObject.GetComponent<Popup>();

	}

	//------------------------------------------------------------
	//----------------- Frame Update Methods ---------------------
	//------------------------------------------------------------

	// Update is called once per frame
	void Update () {

		Renderer rend = transform.GetComponent<Renderer>();

		// process the ring color base on the speed
		ColorUtil.processRingColor(speed, rend);

		// onfocus modify the visual appearence of the ring.
		setFocusHighlight();

		// spins ring for this frame if spin = true
		spinRing();

		checkForMouseEvents();

		// update the shiftedPostion of the ring on every frame
		//*****-------Critical-------******//
		shiftedPosition = transform.position;

		showTitlePopup();

		focus = false; // must reset the focus to false

	}

	//------------------------------------------------------------
	//---------- Broadcast Event Listener Methods ----------------
	//------------------------------------------------------------

	// setup the ring shift animation and set shift to true.
	void shiftOtherRings(float srcRingVertOrder){

		currentSrcVertOrder = srcRingVertOrder;

		// set the start position of each ring
		startV3 = transform.position;

		// define the start time for the transition.
		float startTimeExpand = Time.time;

		// ignore broadcast messages from this ring
		if(vertOrder != srcRingVertOrder){

			if(vertOrder > srcRingVertOrder){

				endV3 = new Vector3(transform.position.x,(transform.position.y + 10f),transform.position.z);
				journeyLength = Vector3.Distance(startV3, endV3);

			}else if(vertOrder < srcRingVertOrder){

				endV3 = new Vector3(transform.position.x,(transform.position.y - 10f),transform.position.z);
				journeyLength = Vector3.Distance(startV3, endV3);

			}

		}else{

			// if this ring is the broadcaster freeze it position
			endV3 = transform.position;
			journeyLength = Vector3.Distance(startV3, endV3);
		}

		StartCoroutine(shiftTheRing(startTimeExpand, startV3, endV3));

	}

	void resetRingShift(){

		resetStartTime = Time.time;
		resetJourneyLength = Vector3.Distance(shiftedPosition,originPosition);

		StartCoroutine(restTheRing(resetStartTime, shiftedPosition, originPosition));

		ringIsClosing = true;

	}

	IEnumerator shiftTheRing(float startTimeExpand, Vector3 startV3, Vector3 endV3){

		float distCovered = (Time.time - startTimeExpand) * fract;
		float fracJourney = distCovered / journeyLength;

		if(float.IsNaN(fracJourney)){
			fracJourney = 0f;
		}

		ringIsExpanding = true;

		while(distCovered <= 10f ){

			distCovered = (Time.time - startTimeExpand) * fract;
			fracJourney = distCovered / journeyLength;

			if(float.IsNaN(fracJourney)){
				fracJourney = 0f;
			}

			transform.position = Vector3.Lerp(startV3, endV3, fracJourney);

			yield return null;

		}

		if(vertOrder == currentSrcVertOrder){
			transform.BroadcastMessage("showSubRings");
			ringIsExpanded = true;

		}

		ringIsExpanding = false;

		shiftedPosition = transform.position;

	}

	IEnumerator restTheRing(float resetStartTime, Vector3 currentPosition, Vector3 originPosition){

		float distCovered = (Time.time - resetStartTime) * resetFract;
		float fracResetJourney = distCovered / resetJourneyLength;

		ringIsClosing = true;

		while(distCovered <= resetJourneyLength){

			distCovered = (Time.time - resetStartTime) * resetFract;
			fracResetJourney = distCovered / resetJourneyLength;

			transform.position = Vector3.Lerp(currentPosition, originPosition, fracResetJourney);

			yield return null;

		}

		ringIsClosing = false;
		ringIsExpanded = false;

	}

	//------------------------------------------------------------
	//--------------------- Text and Popups ----------------------
	//------------------------------------------------------------

	void showTitlePopup() {

		if ((focus == true) && (popupOn == false)) { // && (Input.GetButton("Fire3"))) {

			if (popupStartTime == 0) {
				popupStartTime = Time.time;
			} else if ((Time.time - popupStartTime) > popupWaitTime) {
				popupOn = true;
				popdownStartTime = 0;
				showPopup(titleTextValue, fromPoint, hitPoint);
			}

		} else if ((focus == false) && (popupOn == true)) {

			if (popdownStartTime == 0) {
				popdownStartTime = Time.time;
			} else if ((Time.time - popdownStartTime) > popupWaitTime) {
				hidePopup();
				popupOn = false;
				popupStartTime = 0;
			}

		} else if ((focus == false) && (popupOn == false) && (popupStartTime > 0)) {

			popupStartTime = 0;

		} else if ((focus == true) && (popupOn == true) && (popdownStartTime > 0)) {

			popdownStartTime = 0;

		}

	}

	public void showPopup(string popupText, Vector3 playerPoint, Vector3 popupPoint) {

		oppPopup.transform.position = Vector3.MoveTowards(playerPoint, popupPoint, 10f);
		oppPopup.transform.LookAt(playerPoint);
		oppPopup.transform.Rotate (new Vector3(0, oppPopup.transform.position.y + 180f, 0));
		oppPopup.doPopup (popupText);

	}

	public void hidePopup() {

		oppPopup.doPopdown ();

	}

	//------------------------------------------------------------
	//--------------- Controller/Gamepad Events ------------------
	//------------------------------------------------------------

	void checkForMouseEvents(){

		// onbutton up: input A or mouse button one
		// if both the focus and the click event occur then stop the spin
		if(Input.GetButtonUp("Fire1") && focus == true){

			//toggle the spin of this ring
			spin = ( spin == true ) ? false : true;

			// this sends a messages to all other rings to shift their vert postion.
			if(ringIsExpanding == false && ringIsExpanded == false && ringIsClosing == false ){
				transform.parent.BroadcastMessage("shiftOtherRings", vertOrder);
			}

			alignGapWithView();

		}

		// onbutton up: input A or mouse button one
		// if both the focus and the click event occur then stop the spin
		if(Input.GetButtonUp("Fire2") && ringIsClosing == false){

			transform.parent.BroadcastMessage("resetRingShift");
			transform.parent.BroadcastMessage("hideSubRings");
			//transform.BroadcastMessage("hideSubRings");

		}

	}

	//------------------------------------------------------------
	//--------------- Set Ring Spin and Color --------------------
	//------------------------------------------------------------

	void setFocusHighlight(){

		if(focus == true){

			Renderer rend = GetComponent<Renderer>();
			ColorUtil.setFocusHighlightColor(rend);

		}else{

			Renderer rend = GetComponent<Renderer>();
			ColorUtil.removeFocusHighlightColor(rend);

		}

	}

	void spinRing(){

		if(spin == true && zone != 1000){
			transform.Rotate(Vector3.up, speed * Time.deltaTime);
		}

	}

	//------------------------------------------------------------
	//------------------ Align the ring gap ----------------------
	//------------------------------------------------------------

	// align ring gap with views direction
	void alignGapWithView(){

		float fromPointX = fromPoint.x;
		float fromPointZ = fromPoint.z;

		startRotation = transform.rotation;

		if(Mathf.Abs(fromPointX) < 21 && Mathf.Abs (fromPointZ) < 21){

			fromPoint = Vector3.MoveTowards(fromPoint, hitPoint, -110f);

		}

		endRotation = Quaternion.LookRotation((new Vector3(fromPointX,0f,fromPointZ)));

		Vector3 adjustment = endRotation.eulerAngles;
		adjustment.y = adjustment.y + 45f;

		endRotation = Quaternion.Euler(adjustment);

		float time = 0f;
		float period = 3f;  // it will take 3 sec to look

		float startTime = Time.time;

		StartCoroutine(rotateRingToView(startRotation,endRotation,time,period));

	}

	// coroutine that animates the ring rotation to align the ring gap with the view.
	IEnumerator rotateRingToView(Quaternion startRotation, Quaternion endRotation, float time, float period){

		while(transform.rotation != endRotation) {

			transform.rotation = Quaternion.Slerp(startRotation, endRotation, time);

			time += 0.01f;

			yield return null;

		}

	}

}


text:
class PIFAlizator;

#ifdef _WIN32
 #define MODULE_EXTENSION    ".dll"
#else
 #define MODULE_EXTENSION    ".so"
 #define HANDLE              void *
 #define HMODULE             HANDLE
#endif

#define ON_CREATE            "__CONCEPT_MEMORY_MANAGEMENT_CreateContext"
#define ON_DESTROY           "__CONCEPT_MEMORY_MANAGEMENT_DestroyContext"

#define SYSTEM_SOCKET        INTEGER

typedef INTEGER (*CALL_BACK_VARIABLE_SET)(VariableDATA *VD, INTEGER TYPE, char *STRING_VALUE, NUMBER NUMBER_VALUE);
typedef INTEGER (*CALL_BACK_VARIABLE_GET)(VariableDATA *VD, INTEGER *TYPE, char **STRING_VALUE, NUMBER *NUMBER_VALUE);

typedef INTEGER (*CALL_BACK_CLASS_MEMBER_GET)(void *CLASS_PTR, const char *class_member_name, INTEGER *TYPE, char **STRING_VALUE, NUMBER *NUMBER_VALUE);
typedef INTEGER (*CALL_BACK_CLASS_MEMBER_SET)(void *CLASS_PTR, const char *class_member_name, INTEGER TYPE, const char *STRING_VALUE, NUMBER NUMBER_VALUE);

typedef INTEGER (*INVOKE_CALL)(INTEGER INVOKE_TYPE, ...);

typedef POINTER (*CONCEPT_MANAGEMENT_CALL)(INTEGER _MANAGEMENT_UNIQUE_ID, void *HANDLER, INVOKE_CALL Invoker);

typedef POINTER (*EXTERNAL_CALL)(ParamListExtra *PARAMETERS, VariableDATA **LOCAL_CONTEXT, VariableDATA *RESULT, CALL_BACK_VARIABLE_SET CallBACKSet, CALL_BACK_VARIABLE_GET CallBACKGet, SYSTEM_SOCKET CLIENT_SOCKET, char *LOCAL_PUBLIC_KEY, char *LOCAL_PRIVATE_KEY, char *REMOTE_PUBLIC_KEY, CALL_BACK_CLASS_MEMBER_SET CallBACKClassSet, CALL_BACK_CLASS_MEMBER_GET CallBACKClassGet, INVOKE_CALL Invoke);

INTEGER ImportModule(AnsiString& MODULE_MASK, AnsiList *Errors, INTEGER line, AnsiString FILENAME, AnsiList *TARGET, PIFAlizator *Sender, INTEGER _NO_ERROR_REPORT = 0);
INTEGER UnImportModule(HMODULE hMODULE, PIFAlizator *Sender = 0);
SYS_INT LinkFunction(void *PIF, const char *FUNCTION_NAME, AnsiList *TARGET, void **CACHED_hDLL);
void DoneLinking();

INTEGER SetVariable(VariableDATA *VD, INTEGER TYPE, char *STRING_VALUE, NUMBER NUMBER_VALUE);
INTEGER GetVariable(VariableDATA *VD, INTEGER *TYPE, char **STRING_VALUE, NUMBER *NUMBER_VALUE);

INTEGER GetClassMember(void *CLASS_PTR, const char *class_member_name, INTEGER *TYPE, char **STRING_VALUE, NUMBER *NUMBER_VALUE);
INTEGER SetClassMember(void *CLASS_PTR, const char *class_member_name, INTEGER TYPE, const char *STRING_VALUE, NUMBER NUMBER_VALUE);

INTEGER Invoke(INTEGER INVOKE_TYPE, ...);
#endif // __MODULE_LINK_H