import copy
from argparse import ArgumentParser, BooleanOptionalAction
from functools import partial
from typing import Optional

import torch
import torch.nn.functional as F
import torch_optimizer as optim
from pl_bolts.callbacks.knn_online import KNNOnlineEvaluator
from pl_bolts.optimizers import linear_warmup_decay
from pytorch_lightning import Trainer, LightningModule, seed_everything
from pytorch_lightning.callbacks import LearningRateMonitor, ModelCheckpoint
from pytorch_lightning.loggers import TensorBoardLogger
from torch import nn
from torchmetrics import Accuracy

from models import SimCLRMaskedViT, PosReconHead
from utils.criteria import multi_view_info_nce_loss, multi_view_cov_reg_loss
from utils.datamodules import build_imagenet
from utils.lr_wt_decay import param_groups_lrd, exclude_from_wt_decay
from utils.transforms import SimCLRPretrainPostTransform, imagenet_normalization, MultiCropPretrainPreTransform

class RandMaskedSimCLR(LightningModule):
    def __init__(
            self,
            num_samples: int,
            num_nodes: int = 1,
            gpus: int = 1,
            batch_size: int = 128,
            max_epochs: int = 100,
            dataset: str = "imagenet",
            num_classes: int = 1000,
            size_crops: tuple[int, ...] = (224,),
            num_crops: tuple[int, ...] = (2,),
            gaussian_blur: bool = True,
            jitter_strength: float = 1.,
            img_size: int = 224,
            patch_size: int = 16,
            in_chans: int = 3,
            embed_dim: int = 384,
            depth: int = 12,
            num_heads: int = 6,
            mlp_ratio: int = 4,
            mlp_drop_rate: float = 0.,
            attention_drop_rate: float = 0.,
            drop_path_rate: float = 0.,
            layer_decay: float = 1.,
            weight_sharing: Optional[str] = None,
            position: bool = True,
            shuffle: bool = False,
            mask_ratio: float = 0.75,
            ema_momentum: float = 0.,
            mask_ratio_sg: float = 0.75,
            proj_dim: int = 128,
            temperature: float = 0.1,
            cov_reg_norm: bool = False,
            cov_reg_coeff: float = 0.,
            pos_recon_depth: int = 0,
            pos_recon_num_heads: int = 6,
            pos_recon_coeff: float = 0.,
            pos_recon_loss: str = "mse",
            optimizer: str = "adamw",
            learning_rate: float = 1e-3,
            weight_decay: float = 0.05,
            exclude_bn_bias: bool = False,
            warmup_epochs: int = 10,
            **kwargs
    ):
        super(RandMaskedSimCLR, self).__init__()
        self.save_hyperparameters()

        # Training
        self.num_samples = num_samples
        self.num_nodes = num_nodes
        self.gpus = gpus
        self.batch_size = batch_size
        self.max_epochs = max_epochs
        # compute iters per epoch
        global_batch_size = num_nodes * gpus * batch_size if gpus > 0 else batch_size
        self.train_iters_per_epoch = num_samples // global_batch_size

        # Data
        self.dataset = dataset
        self.num_classes = num_classes
        self.size_crops = size_crops
        self.num_crops = num_crops
        self.gaussian_blur = gaussian_blur
        self.jitter_strength = jitter_strength

        # ViT params
        self.img_size = img_size
        self.patch_size = patch_size
        self.in_chans = in_chans
        self.embed_dim = embed_dim
        self.depth = depth
        self.num_heads = num_heads
        self.mlp_ratio = mlp_ratio
        self.weight_sharing = weight_sharing
        # Regularization
        self.mlp_drop_rate = mlp_drop_rate
        self.attention_drop_rate = attention_drop_rate
        self.drop_path_rate = drop_path_rate
        self.layer_decay = layer_decay

        # FCLR params
        self.position = position
        self.shuffle = shuffle
        self.mask_ratio = mask_ratio
        self.ema_momentum = ema_momentum
        self.mask_ratio_sg = mask_ratio_sg
        self.proj_dim = proj_dim
        self.temperature = temperature

        # CovReg
        self.cov_reg_norm = cov_reg_norm
        self.cov_reg_coeff = cov_reg_coeff

        # PosRecon
        self.pos_recon_depth = pos_recon_depth
        self.pos_recon_num_heads = pos_recon_num_heads
        self.pos_recon_coeff = pos_recon_coeff
        self.pos_recon_loss = pos_recon_loss

        # Optimizer
        self.optim = optimizer
        self.learning_rate = learning_rate
        self.weight_decay = weight_decay
        self.exclude_bn_bias = exclude_bn_bias
        self.warmup_epochs = warmup_epochs

        # Transforms
        if dataset == "imagenet":
            normalization = imagenet_normalization()
        self.normalization = normalization
        self.transform = SimCLRPretrainPostTransform(
            img_size=img_size,
            gaussian_blur=gaussian_blur,
            jitter_strength=jitter_strength,
            normalize=normalization,
        )

        # Modules
        self.siamese_net = SimCLRMaskedViT(
            img_size,
            patch_size,
            in_chans,
            embed_dim,
            depth,
            num_heads,
            mlp_ratio,
            proj_dim,
            mlp_drop_rate,
            attention_drop_rate,
            drop_path_rate,
            norm_layer=partial(nn.LayerNorm, eps=1e-6),
            weight_sharing=weight_sharing,
        )
        if ema_momentum > 0:
            self.siamese_net_sg = copy.deepcopy(self.siamese_net)
        if pos_recon_coeff > 0:
            self.pos_recon_head = PosReconHead(
                embed_dim,
                pos_recon_depth,
                pos_recon_num_heads
            )
        if pos_recon_loss in {'mse', 'l2'}:
            self.pos_recon_criterion = nn.MSELoss()
        elif pos_recon_loss in {'mae', 'l1'}:
            self.pos_recon_criterion = nn.L1Loss()
        elif pos_recon_loss == 'smooth_l1':
            self.pos_recon_criterion = nn.SmoothL1Loss()
        else:
            raise NotImplementedError(f"Unknown PosRecon loss: {pos_recon_loss}")
        self.classifier = nn.Linear(embed_dim, num_classes)

        # Online metrics
        self.train_acc_top_1 = Accuracy(top_k=1)
        self.train_acc_top_5 = Accuracy(top_k=5)
        self.val_acc_top_1 = Accuracy(top_k=1, compute_on_step=False)
        self.val_acc_top_5 = Accuracy(top_k=5, compute_on_step=False)

    def on_train_batch_end(self, outputs, batch, batch_idx):
        if self.ema_momentum > 0:
            for online_p, target_p in zip(self.siamese_net.parameters(),
                                          self.siamese_net_sg.parameters()):
                em = self.ema_momentum
                target_p.data = target_p.data * em + online_p.data * (1.0 - em)

    def forward(self, x):
        x = self.normalization(x)
        x, _ = self.siamese_net(x)

        return x

    def forward_multi_crop(self, img):
        # Concatenate crop with the same size and mask ratio, augment, and forward
        end_indices = torch.tensor(self.num_crops).cumsum(0)
        start_indices = torch.cat((torch.tensor([0]), end_indices[:-1]))
        for i, (start_index, end_index) in enumerate(zip(start_indices, end_indices)):
            # Assume global crop at first
            mask_ratio = self.mask_ratio if i == 0 else 0.
            img_ = torch.cat([
                self.transform(i) for i in img[start_index:end_index]
            ])
            yield self.siamese_net(img_, self.position, self.shuffle, mask_ratio)

    def position_reconstruction(self, patch_embed, visible_idx):
        pos_embed = self.siamese_net.pos_embed.expand(patch_embed.size(0), -1, -1)
        if visible_idx is not None:
            pos_embed = pos_embed.gather(1, visible_idx)
        pos_embed_pred = self.pos_recon_head(patch_embed)
        loss_pos_recon = self.pos_recon_criterion(pos_embed_pred, pos_embed)

        return loss_pos_recon

    def shared_step(self, img):
        batch_size, *_ = img[0].size()
        patch_embed, visible_idx, proj = [], [], []
        for patch_embed_, visible_idx_, proj_ in self.forward_multi_crop(img):
            patch_embed.append(patch_embed_)
            visible_idx.append(visible_idx_)
            proj.append(proj_.view(-1, batch_size, self.proj_dim))
        reps = torch.cat([pe[:, 0, :] for pe in patch_embed])
        patch_embed = patch_embed[0][:, 1:, :]
        visible_idx = visible_idx[0]
        proj = torch.cat(proj).transpose(0, 1)

        if self.ema_momentum > 0:
            proj_sg = []
            with torch.no_grad():
                for _, _, proj_ in self.forward_multi_crop(img):
                    proj_sg.append(proj_.view(-1, batch_size, self.proj_dim))
            proj_sg = torch.cat(proj_sg).transpose(0, 1)
            proj = torch.cat((proj, proj_sg))

        loss_clr = multi_view_info_nce_loss(proj, self.temperature)

        loss_cov_reg = multi_view_cov_reg_loss(proj, self.cov_reg_norm)
        loss = loss_clr + self.cov_reg_coeff * loss_cov_reg

        loss_pos_recon = 0.
        if self.pos_recon_coeff > 0:
            loss_pos_recon = self.position_reconstruction(patch_embed, visible_idx)
            loss += self.pos_recon_coeff * loss_pos_recon

        return reps, proj, {
            "clr": loss_clr,
            "cov_reg": loss_cov_reg,
            "pos_recon": loss_pos_recon,
            "all": loss,
        }

    def linear_probe(self, reps, labels, acc_top_1_fn, acc_top_5_fn):
        labels = labels.repeat(reps.size(0) // labels.size(0))
        logits = self.classifier(reps)
        loss = F.cross_entropy(logits, labels)
        acc_top_1 = acc_top_1_fn(logits.softmax(-1), labels)
        acc_top_5 = acc_top_5_fn(logits.softmax(-1), labels)

        return loss, acc_top_1, acc_top_5

    def training_step(self, batch, batch_idx):
        img, labels = batch
        reps, proj, losses = self.shared_step(img)

        loss_xent, acc_top_1, acc_top_5 = self.linear_probe(
            reps.detach(), labels, self.train_acc_top_1, self.train_acc_top_5
        )

        self.log_dict({
            'loss/clr/train': losses["clr"],
            'loss/cov_reg/train': losses["cov_reg"],
            'loss/pos_recon/train': losses["pos_recon"],
            'loss/pretrain/train': losses["all"],
            'loss/linear_probe/train': loss_xent,
            'acc/linear_probe_top_1/train': acc_top_1,
            'acc/linear_probe_top_5/train': acc_top_5,
            'norm/reps': reps.norm(dim=-1).mean(),
            'norm/proj': proj.norm(dim=-1).mean(),
        }, sync_dist=True)
        return losses["all"] + loss_xent

    def validation_step(self, batch, batch_idx):
        img, labels = batch
        reps, proj, losses = self.shared_step(img)

        loss_xent, acc_top_1, acc_top_5 = self.linear_probe(
            reps.detach(), labels, self.val_acc_top_1, self.val_acc_top_5
        )

        self.log_dict({
            'loss/clr/val': losses["clr"],
            'loss/cov_reg/val': losses["cov_reg"],
            'loss/pos_recon/val': losses["pos_recon"],
            'loss/pretrain/val': losses["all"],
            'loss/linear_probe/val': loss_xent,
            'acc/linear_probe_top_1/val': acc_top_1,
            'acc/linear_probe_top_5/val': acc_top_5,
        }, sync_dist=True)
        return losses["all"] + loss_xent

    def configure_optimizers(self):
        param_groups = param_groups_lrd(
            self.siamese_net,
            lr=self.learning_rate,
            weight_decay=self.weight_decay,
            exclude_1d_params=self.exclude_bn_bias,
            no_weight_decay_list=("pos_embed", "cls_token"),
            layer_decay=self.layer_decay
        )
        if self.exclude_bn_bias:
            param_groups += exclude_from_wt_decay(self.classifier, self.weight_decay)
            if self.pos_recon_coeff > 0:
                param_groups += exclude_from_wt_decay(self.pos_recon_head, self.weight_decay)
        else:
            param_groups += [
                {"params": self.classifier.parameters(), "weight_decay": self.weight_decay}
            ]
            if self.pos_recon_coeff > 0:
                param_groups += [
                    {"params": self.pos_recon_head.parameters(), "weight_decay": self.weight_decay}
                ]

        if self.optim == "lars":
            optimizer = optim.LARS(param_groups, lr=self.learning_rate, momentum=0.9)
        elif self.optim == "adam":
            optimizer = torch.optim.Adam(param_groups, lr=self.learning_rate)
        elif self.optim == "lamb":
            optimizer = optim.Lamb(param_groups, lr=self.learning_rate)
        elif self.optim == "adamw":
            optimizer = torch.optim.AdamW(param_groups, lr=self.learning_rate)

        warmup_steps = self.train_iters_per_epoch * self.warmup_epochs
        total_steps = self.train_iters_per_epoch * self.max_epochs

        scheduler = {
            "scheduler": torch.optim.lr_scheduler.LambdaLR(
                optimizer,
                linear_warmup_decay(warmup_steps, total_steps, cosine=True),
            ),
            "interval": "step",
            "frequency": 1,
        }

        return [optimizer], [scheduler]

    @staticmethod
    def add_model_specific_args(parent_parser):
        parser = ArgumentParser(parents=[parent_parser], add_help=False)

        # training params
        parser.add_argument("--num_nodes", default=1, type=int,
                            help="number of nodes for training")
        parser.add_argument("--gpus", default=1, type=int,
                            help="number of gpus to train on")
        parser.add_argument("--max_epochs", default=100, type=int,
                            help="number of total epochs to run")
        parser.add_argument("--max_steps", default=-1, type=int,
                            help="max steps")
        parser.add_argument("--batch_size", default=128, type=int,
                            help="batch size per gpu")
        parser.add_argument("--num_workers", default=8, type=int,
                            help="num of workers per GPU")
        parser.add_argument("--fast_dev_run", default=False, type=int)
        parser.add_argument("--fp16", default=False, action='store_true',
                            help="use fp16")

        # transform params
        parser.add_argument("--dataset", type=str, default="imagenet",
                            help="dataset")
        parser.add_argument("--lmdb_25pct", default=False, action='store_true',
                            help="use dedicated 25%% LMDB dataset")
        parser.add_argument("--lmdb", default=False, action='store_true',
                            help="use LMDB dataset")
        parser.add_argument("--data_dir", type=str, default="dataset",
                            help="path to dataset")
        parser.add_argument("--sample_pct", type=int, default=100,
                            help="%% of samples for training (only for ablation)")
        parser.add_argument("--size_crops", default=(224,), type=int, nargs="+",
                            help="crop resolution")
        parser.add_argument("--num_crops", default=(2,), type=int, nargs="+",
                            help="number of crops")
        parser.add_argument("--min_scale_crops", default=(0.08,), type=float, nargs="+",
                            help="RandResizeCrop minimum scales")
        parser.add_argument("--max_scale_crops", default=(1.,), type=float, nargs="+",
                            help="RandResizeCrop maximum scales")
        parser.add_argument("--gaussian_blur", default=True,
                            action=BooleanOptionalAction, help="add gaussian blur")
        parser.add_argument("--jitter_strength", type=float, default=1.0,
                            help="jitter strength")

        # model params
        parser.add_argument("--img_size", default=224, type=int,
                            help="input image size")
        parser.add_argument("--patch_size", default=16, type=int,
                            help="patch size")
        parser.add_argument("--in_chans", default=3, type=int,
                            help="number of in channels")
        parser.add_argument("--embed_dim", default=384, type=int,
                            help="embedding dimension")
        parser.add_argument("--depth", default=12, type=int,
                            help="number of Transformer blocks")
        parser.add_argument("--num_heads", default=6, type=int,
                            help="number of self-attention heads")
        parser.add_argument("--mlp_ratio", default=4, type=int,
                            help="Ratio of embedding dim to MLP dim")
        parser.add_argument("--weight_sharing", default=None, type=str,
                            help="ALBERT-style weight sharing, "
                                 "choose from None, attn, ffn, or all")
        # regularization
        parser.add_argument("--layer_decay", default=1., type=float,
                            help="layer-wise decay")
        parser.add_argument("--mlp_drop_rate", default=0.0, type=float,
                            help="mlp dropout rate")
        parser.add_argument("--attention_drop_rate", default=0.0, type=float,
                            help="attention dropout rate")
        parser.add_argument("--drop_path_rate", default=0.0, type=float,
                            help="path dropout rate")
        # FCLR
        parser.add_argument("--position", default=True, action=BooleanOptionalAction,
                            help="add positional embedding or not")
        parser.add_argument("--shuffle", default=False, action='store_true',
                            help="shuffle positional embedding or not")
        parser.add_argument("--mask_ratio", default=0.75, type=float,
                            help="mask ratio of patches")
        parser.add_argument("--ema_momentum", default=0., type=float,
                            help="ema momentum")
        parser.add_argument("--mask_ratio_sg", default=0.75, type=float,
                            help="mask ratio of patches on target branch")
        parser.add_argument("--proj_dim", default=128, type=int,
                            help="projection head output dimension")
        parser.add_argument("--temperature", default=0.1, type=float,
                            help="temperature parameter in InfoNCE loss")
        # CovReg
        parser.add_argument("--cov_reg_norm", default=True, action=BooleanOptionalAction,
                            help="use correlation instead of covariance")
        parser.add_argument("--cov_reg_coeff", default=0., type=float,
                            help="coefficient on covariance regularization loss")
        # PosRecon
        parser.add_argument("--pos_recon_depth", default=0, type=int,
                            help="depth of PosRecon head, 0 for linear layer")
        parser.add_argument("--pos_recon_num_heads", default=6, type=int,
                            help="number of attention heads in PosRecon head")
        parser.add_argument("--pos_recon_coeff", default=0., type=float,
                            help="coefficient on position reconstruction loss")
        parser.add_argument("--pos_recon_loss", default='mse', type=str,
                            help="type of position reconstruction loss")

        # Optimizer
        parser.add_argument("--optimizer", default="adamw", type=str,
                            help="choose between adamw/lars")
        parser.add_argument("--learning_rate", default=1e-3, type=float,
                            help="base learning rate")
        parser.add_argument("--weight_decay", default=0.05, type=float,
                            help="weight decay")
        parser.add_argument("--exclude_bn_bias", default=True,
                            action=BooleanOptionalAction,
                            help="exclude bn/ln/bias from weight decay")
        parser.add_argument("--warmup_epochs", default=10, type=int,
                            help="number of warmup epochs")

        return parser

if __name__ == '__main__':
    seed_everything(0)
    parser = ArgumentParser()
    parser.add_argument("--version", default=None, type=str)
    parser.add_argument("--log_path", default="lightning_logs", type=str)
    parser.add_argument("--resume_ckpt_path", default=None, type=str)
    parser.add_argument("--track_grad", default=True, action=BooleanOptionalAction)
    parser.add_argument("--knn_probe", default=False, action='store_true')
    parser = RandMaskedSimCLR.add_model_specific_args(parser)
    args = parser.parse_args()

    if args.dataset == "imagenet":
        dm = build_imagenet(args.sample_pct, args.lmdb, args.lmdb_25pct)
    else:
        raise NotImplementedError(f"Unimplemented dataset: {args.dataset}")
    dm = dm(data_dir=args.data_dir,
            batch_size=args.batch_size,
            num_workers=args.num_workers)
    args.num_samples = dm.num_samples
    args.num_classes = dm.num_classes

    dm.train_transforms = dm.val_transforms = MultiCropPretrainPreTransform(
        args.size_crops, args.num_crops, args.min_scale_crops, args.max_scale_crops
    )

    model = RandMaskedSimCLR(**args.__dict__)

    logger = TensorBoardLogger(args.log_path, name="pretrain", version=args.version)
    lr_monitor = LearningRateMonitor(logging_interval="step")
    model_checkpoint = ModelCheckpoint(save_last=True, monitor="loss/pretrain/val")

    callbacks = [model_checkpoint]
    if args.knn_probe:
        callbacks.append(KNNOnlineEvaluator())
    callbacks.append(lr_monitor)

    if args.gpus > 1:
        if args.ema_momentum > 0:
            strategy = "ddp"
        else:
            strategy = "ddp_find_unused_parameters_false"
    else:
        strategy = None

    trainer = Trainer(
        max_epochs=args.max_epochs,
        max_steps=args.max_steps,
        devices=args.gpus if args.gpus > 0 else None,
        num_nodes=args.num_nodes,
        accelerator="gpu" if args.gpus > 0 else None,
        strategy=strategy,
        sync_batchnorm=True if args.gpus > 1 else False,
        track_grad_norm=2 if args.track_grad else -1,
        precision=16 if args.fp16 else 32,
        callbacks=callbacks,
        logger=logger,
        fast_dev_run=args.fast_dev_run,
    )

    trainer.fit(model, datamodule=dm, ckpt_path=args.resume_ckpt_path)


from collections import defaultdict
import pickle

class ImpactModel(object):

    def __init__(self, impact_terms_json, impact_rules_json, aspect_terms_json):
        self.impact_terms = [make_impact_term(term_json) for term_json in impact_terms_json]
        self.impact_rules = [make_impact_rule(rule_json) for rule_json in impact_rules_json]
        self.make_rule_index()
        self.index_aspect_terms(aspect_terms_json)

    def make_rule_index(self):
        """makes an indexes of all impact rules per impact term"""
        self.impact_rule_index = defaultdict(list)
        for impact_rule in self.impact_rules:
            self.impact_rule_index[impact_rule.impact_term.string].append(impact_rule)

    def impact_term_rules(self, impact_term):
        """returns all impact rules for a given impact term"""
        if impact_term not in self.impact_rule_index:
            return None
        else:
            return self.impact_rule_index[impact_term]

    def index_aspect_terms(self, aspect_terms_json):
        """indexes aspect terms per group and aspect groups per term"""
        self.aspect_group_index = defaultdict(dict)
        self.aspect_term_index = defaultdict(dict)
        for aspect_term_json in aspect_terms_json:
            term = aspect_term_json["Aspect_term"]
            group = aspect_term_json["Aspect_category"]
            self.aspect_group_index[group][term] = 1
            self.aspect_term_index[term][group] = 1

    def aspect_term(self, term):
        """returns a dictionary of an aspect terms and its aspect group(s)"""
        if term not in self.aspect_term_index:
            return None
        groups = self.aspect_term_index[term].keys()
        if len(groups) == 1:
            groups = groups[0]
        return {
            "aspect_term": term,
            "aspect_group": groups
        }

    def aspect_group(self, group):
        """returns a dictionary of an aspect group and its aspect terms"""
        if group not in self.aspect_group_index:
            return None
        return {
            "aspect_term": self.aspect_group_index[group].keys(),
            "aspect_group": group
        }

class ImpactTerm(object):

    def __init__(self, impact_string, impact_group, string_pos, group_type):
        self.string = impact_string
        self.group = impact_group
        self.pos = string_pos
        self.type = group_type

    def __repr__(self):
        return "%s(%r)" % (self.__class__, self.__dict__)

class AspectTerm(object):

    def __init__(self, term, category):
        self.string = term
        self.category = category

    def __repr__(self):
        return "%s(%r)" % (self.__class__, self.__dict__)

class ImpactRule(object):

    def __init__(self, impact_term, code, condition, filter_condition, remarks, ignorecase=None):
        self.impact_term = impact_term
        self.code = None if code == "" else code
        self.condition = None if condition == "" else parse_condition(condition)
        self.filter = False if filter_condition == "" else True
        self.remarks = None if remarks == "" else remarks
        self.impact_type = expand_impact_code(code)
        self.ignorecase = ignorecase

    def __repr__(self):
        return "%s(%r)" % (self.__class__, self.__dict__)

def parse_phrase_wildcards(phrase):
    return phrase.replace("*", r"\w*")

def parse_discontinuous_phrase(phrase):
    """
    Transform discontinuous phrase into a regular expression. Discontinuity is
    interpreted as taking place at any whitespace outside of terms grouped by
    parentheses. That is, the whitespace indicates that anything can be in between
    the left side and right side.
    Example 1: x1 (x2 (x3"x4")) becomes x1.+(x2 (x3|x4))
    """
    level = 0
    parsed_phrase = ""
    for index, char in enumerate(phrase):
        if char == "(":
            level += 1
        elif char == ")":
            level -= 1
        elif char == " " and level == 0:
            char = ".+"
        parsed_phrase += char
    return parsed_phrase

def term_string(impact_term):
    if "discontinuous" in impact_term["Impact_group"]:
        impact_term["Impact_term"] = parse_discontinuous_phrase(impact_term["Impact_term"])
    return impact_term["Impact_term"]

def term_group(impact_term):
    return impact_term["Impact_group"]

def term_type(impact_term):
    if "phrase" in term_group(impact_term):
        return "phrase"
    else:
        return "term"

def term_pos(impact_term):
    if "adjective" in term_group(impact_term):
        return "adj"
    elif "noun" in term_group(impact_term):
        return "noun"
    elif "adverb" in term_group(impact_term):
        return "adv"
    elif "verb" in term_group(impact_term):
        return "verb"

def make_impact_term(term_json):
    return ImpactTerm(
        term_string(term_json),
        term_group(term_json),
        term_pos(term_json),
        term_type(term_json)
    )

# Conditions
# @...   Refers to group in sheet of book aspects
# %(...) Continuous group must be present in neighbourhood
# %...   Word must be present in neighbourhood
# ^...   Word must be present at start of sentence
# #(...) discontinuous group must be present in neighbourhood

def parse_condition(condition_string):
    """parses coded condition of impact rule to more readable json"""
    condition_symbol = condition_string[0]
    condition_term = condition_string[1:]
    condition = {
        "condition_type": "context_term",
        "context_term": condition_term,
        "term_type": "term",
        "location": "neighbourhood"
    }
    if condition_symbol == "@":
        condition["condition_type"] = "aspect_term"
        del condition["context_term"]
        condition["aspect_group"] =  condition_term
    elif condition_symbol == "%":
        condition["location"] = "neighbourhood"
        if condition_term[0] == "(" and condition_term[-1] == ")":
            condition["term_type"] = "phrase"
            condition["phrase_type"] = "continuous"
    elif condition_symbol == "^":
        condition["location"] = "sentence_start"
    elif condition_symbol == "$":
        condition["location"] = "sentence_end"
    elif condition_symbol == "#":
        if condition_term[0] == "(" and condition_term[-1] == ")":
            condition["context_term"] = condition["context_term"][1:-1]
        condition["term_type"] = "phrase"
        condition["phrase_type"] = "discontinuous"
        condition["context_term"] = parse_discontinuous_phrase(condition["context_term"])
    if condition["term_type"] == "phrase":
        condition["context_term"] = parse_phrase_wildcards(condition["context_term"])
    return condition

def expand_impact_code(code):
    """map impact code to readable impact type"""
    if code == "R":
        return "Reflection"
    if code == "A":
        return "Affect"
    if code == "N":
        return "Narrative"
    if code == "S":
        return "Style"
    else:
        return None

def make_impact_rule(rule_json):
    rule_fields = ["Impact_group", "Impact_term", "Code as", "Condition", "neg filter*", "remarks"]
    impact_term = ImpactTerm(term_string(rule_json), term_group(rule_json), term_pos(rule_json), term_type(rule_json))
    ignorecase = rule_fields["Ignore case"] if "Ignore case" in rule_fields else True
    return ImpactRule(
        impact_term,
        rule_json["Code as"],
        rule_json["Condition"],
        rule_json["neg filter*"],
        rule_json["remarks"],
        ignorecase
    )

def make_aspect_term(aspect_term_json):
    return AspectTerm(aspect_term_json["Aspect_term"], aspect_term_json["Aspect_category"])

def load_model(model_file):
    """Load an impact model from a pickle file."""
    with open(model_file, 'rb') as fh:
        return pickle.load(fh)


#!--*--coding:utf-8 --*--
'''
Created on 2016年7月26日

@author: tianyongkang
'''

from kazoo.client import KazooClient

zk = KazooClient('host=10.136.3.214:2181')
zk.start(timeout=20)
r = zk.command(cmd="conf")


import sys
import asyncio

import rti.connextdds as dds
import rti.asyncio

from partitions import PartitionsExample

class PartitionsExamplePublisher:
    def __init__(self, domain_id: int):
        self.participant = dds.DomainParticipant(domain_id)

        topic = dds.Topic(
            self.participant, "Example partitions", PartitionsExample
        )

        self.publisher = dds.Publisher(self.participant)

        self.writer = dds.DataWriter(self.publisher, topic)

        self.samples_written = 0

    async def run(self, sample_count: int):
        sample = PartitionsExample()
        while self.samples_written < sample_count:
            # Modify the data to be sent here
            sample.x = self.samples_written

            # Every 5 samples we will change the partition name.
            # These are the partition expressions we are going to set:
            # "bar", "A*", "A?C", "X*Z", "zzz" and "A*C".
            new_partitions = None
            if (self.samples_written + 1) % 25 == 0:
                # Matches "ABC", name[1] here can match name[0] there,
                # as long as there is some overlapping name.
                new_partitions = ["zzz", "A*C"]
            elif (self.samples_written + 1) % 25 == 20:
                # Strings that are regular expressions aren't tested for
                # literal matches, so this won't match "X*Z".
                new_partitions = ["X*Z"]
            elif (self.samples_written + 1) % 25 == 15:
                # Matches "ABC".
                new_partitions = ["A?C"]
            elif (self.samples_written + 1) % 25 == 10:
                # Matches "ABC".
                new_partitions = ["A*"]
            elif (self.samples_written + 1) % 25 == 5:
                # No literal match for "bar".
                # For the next iterations we are using only one partition.
                new_partitions = ["bar"]

            if new_partitions is not None:
                publisher_qos = self.publisher.qos
                publisher_qos.partition.name = new_partitions
                self.publisher.qos = publisher_qos
                print(
                    f"Partition set to: {list(self.publisher.qos.partition.name)}"
                )

            print(f"Writing {sample}")
            self.writer.write(sample)

            self.samples_written += 1
            await asyncio.sleep(1)

if __name__ == "__main__":
    try:
        rti.asyncio.run(PartitionsExamplePublisher(0).run(sys.maxsize))
    except KeyboardInterrupt:
        pass


#! /usr/bin/env python
from math import pi
from gimpfu import *
import gtk

pdb = gimp.pdb

def cycle_hue(
    img,
    active_layer,
    num_frames,
    use_existing_layers,
):

    if use_existing_layers:
        num_frames = len(img.layers)

        if num_frames < 2:
            message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK)
            message.set_markup("There must be at least two layers")
            message.run()
            return

        insert_at = len(img.layers) - 2
    else:
        num_frames = int(num_frames)
        insert_at = img.layers.index(active_layer)

    pdb.gimp_image_undo_group_start(img)

    for i in range(1, num_frames): # original layer is iteration #0
        if use_existing_layers:
            layer = img.layers[insert_at]
            insert_at -= 1
        else:
            layer = pdb.gimp_layer_new_from_drawable(active_layer, img)
            pdb.gimp_image_insert_layer(img, layer, None, insert_at)

        hue_adjustment = ((i * 1.0) / num_frames) * 360

        # gimp_drawable_hue_saturation() accepts a hue_offset between [-180, 180]
        if hue_adjustment > 180:
            hue_adjustment -= 360

        pdb.gimp_drawable_hue_saturation(
            layer
            , 0                 # HUE-RANGE-ALL
            , hue_adjustment
            , 0                 # lightness adjustment
            , 0                 # saturation adjustment
            , 0                 # overlap other hue channels
        )

    pdb.gimp_image_undo_group_end(img)

register(
    "cycle_hue",
    "Cycle hue",
    "Cycle hue",
    "atomicparade",
    "atomicparade",
    "January 2021",
    "<Image>/Filters/Animation/Cycle hue",
    "*",
    [
        (PF_SPINNER,    "num_frames",           "Total number of frames",                                                           8,      (2, 1000, 1)    ),
        (PF_TOGGLE,     "use_existing_layers",  "Use existing layers (enabling this causes total number of frames to be ignored)",  False                   ),
    ],
    [],
    cycle_hue,
)

main()


#Hello
#Gab.com is a social media and video streaming platform that prides itself on perserving free speech
#Its Non-censorship policy has made it a main site for holocaust denirs
#This site scrapes results from a search in one of the search engines of the site gab trends

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common import exceptions
import time
import pandas as pd

def gabDriverSearch(search_term):
#The Url for serching. The search term entered by user will be added to the url to find its results
    url="https://trends.gab.com/search/item?q={}".format(search_term)
#open chrome with web driver. Driver can be downloaded at: https://chromedriver.chromium.org/downloads
    driver=webdriver.Chrome(executable_path=r"C:\Users\Alon1000\Desktop\chromedriver.exe")
#open site

    driver.get(url)
    #the list where the results will go to
    lis=list()
    #find the html element wich says how many results are on the page
    num=driver.find_elements_by_xpath('/html/body/div[1]/div/div[3]/div')
    #fetch the text you want from the element (number in first tto charachters)
    for nu in num:
        limit=int(nu.text[:2])
    i=0
    #scrape each resukt of the search
    while i<limit:
        #find the html element with the results
         ka=driver.find_elements_by_xpath('/html/body/div[1]/div/ul/li[{}]/div[2]/div[1]/a'.format(i))
         #fetch the text you want from the element and add to the list
         for t in ka:
             lis.append(t.text)
         i=i+1
    #close web -browser
    driver.quit()
    #print the list to see if it came out good
    print(lis)
    #add list to a directory in ordere to make it fit too be added into a csv file
    dict={"Content":lis}
    #create data-frame based on the list
    df = pd.DataFrame(dict)
    #add dataframe with the results into a csv file
    df.to_csv('results.csv')

#holohoax is  a known hashtag for holocaust denial
search_term="holohoax"
#initiate scraper
gabDriverSearch(search_term)


import ast
import functools
import itertools
import math
import sys
import types
try:
    import cPickle as pickle
except ImportError:
    import pickle

import numpy

import histbook.util.astunparse

class ExpressionError(Exception): pass

class Expr(object):
    """A symbolic expression, constructed from a lambda or string using :py:meth:`Expr.parse <histbook.expr.Expr.parse>`."""

    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__, ", ".join(self._reprargs()))

    # assumes __eq__ and __lt__ have been defined

    def __ne__(self, other):
        return not self.__eq__(other)

    def __ge__(self, other):
        return not self.__lt__(other)

    def __gt__(self, other):
        return not self.__lt__(other) and not self.__eq__(other)

    def __le__(self, other):
        return self.__lt__(other) or self.__eq__(other)

    def __cmp__(self, other):
        if self.__eq__(other):
            return 0
        elif self.__lt__(other):
            return -1
        else:
            return 1

    @staticmethod
    def parse(expression, defs=None):
        """
        Convert a lambda or string ``expression`` into a :py:class:`Expr <histbook.expr.Expr>`.

        Parameters
        ----------
        expression : lambda or string
            expression involving constants (literals and ``pi``, ``e``, ``inf``, and ``nan``/``NaN``), field variables, and functions in ``Expr.recognized``; if a lambda, the fields are given by function parameters, if a string, any unrecognized names will be identified as fields

        defs : ``None`` or dict
            if not ``None``, provides names to recognize in ``expression`` (to avoid repetitive code)
        """
        _defs = {}
        if defs is not None:
            for n, x in defs.items():
                if isinstance(x, Expr):
                    _defs[n] = x
                elif sys.version_info[0] < 3 and isinstance(x, (str, unicode)):
                    _defs[n] = Expr.parse(x)
                elif sys.version_info[0] >= 3 and isinstance(x, str):
                    _defs[n] = Expr.parse(x)
                else:
                    try:
                        _defs[n] = Const(pickle.loads(pickle.dumps(x)))
                    except:
                        raise ExpressionError("object can't be included in an expression as symbol {0} because it refers to an unserializable object".format(repr(n)))

        pyast = ast.parse(expression, mode="eval").body

        calculate = {"+": lambda x, y: x + y,
                     "-": lambda x, y: x - y,
                     "*": lambda x, y: x * y,
                     "/": lambda x, y: float(x) / float(y),
                     "//": lambda x, y: int(x // y),
                     "%": lambda x, y: x % y,
                     "**": lambda x, y: x ** y,
                     "|": lambda x, y: numpy.uint64(x) | numpy.uint64(y),
                     "&": lambda x, y: numpy.uint64(x) & numpy.uint64(y),
                     "^": lambda x, y: numpy.uint64(x) ^ numpy.uint64(y)}

        env = dict(globals())
        def resolve(node):
            if isinstance(node, ast.Attribute):
                return getattr(resolve(node.value), node.attr, None)
            elif isinstance(node, ast.Name):
                return env.get(node.id, None)
            else:
                raise ExpressionError("functions must be named, not constructed: {0}".format(histbook.util.astunparse.tostring(node).strip()))

        def recurse(node, relations=False):
            if isinstance(node, ast.Num):
                return Const(node.n)

            elif isinstance(node, ast.Str):
                return Const(node.s)

            elif isinstance(node, ast.Dict) and len(node.keys) == 0:
                return Const(set())

            elif isinstance(node, ast.Set):
                content = [recurse(x) for x in node.elts]
                if all(isinstance(x, Const) for x in content):
                    return Const(set(x.value for x in content))
                else:
                    raise ExpressionError("sets in expressions may not contain variable contents: {0}".format(histbook.util.astunparse.tostring(node).strip()))

            elif isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):
                if node.id == "None":
                    return Const(None)
                elif node.id == "True":
                    return Const(True)
                elif node.id == "False":
                    return Const(False)
                elif node.id in _defs:
                    return _defs[node.id]
                else:
                    return Name(node.id)

            elif isinstance(node, getattr(ast, "NameConstant", tuple)):  # node will never be tuple
                if node.value is None:
                    return Const(None)
                elif node.value is True:
                    return Const(True)
                elif node.value is False:
                    return Const(False)
                else:
                    raise AssertionError(node)

            elif relations and isinstance(node, ast.Compare) and len(node.ops) == 1:
                if   isinstance(node.ops[0], ast.Eq):    cmp, swap = "==",     None
                elif isinstance(node.ops[0], ast.NotEq): cmp, swap = "!=",     None
                elif isinstance(node.ops[0], ast.Lt):    cmp, swap = "<",      False
                elif isinstance(node.ops[0], ast.LtE):   cmp, swap = "<=",     False
                elif isinstance(node.ops[0], ast.Gt):    cmp, swap = "<",      True
                elif isinstance(node.ops[0], ast.GtE):   cmp, swap = "<=",     True
                elif isinstance(node.ops[0], ast.In):    cmp, swap = "in",     False
                elif isinstance(node.ops[0], ast.NotIn): cmp, swap = "not in", False
                else:
                    raise ExpressionError("only comparision relations supported: '==', '!=', '<', '<=', '>', '>=', 'in', and 'not in': {0}".format(histbook.util.astunparse.tostring(node).strip()))

                left = recurse(node.left)
                right = recurse(node.comparators[0])
                if swap is True:
                    left, right = right, left
                elif swap is None:
                    left, right = sorted([left, right])

                if (cmp == "in" or cmp == "not in") and not (isinstance(right, Const) and isinstance(right.value, set)):
                    raise ExpressionError("comparisons 'in' and 'not in' can only be used with a set: {0}".format(histbook.util.astunparse.tostring(node).strip()))

                return Relation(cmp, left, right)

            elif relations and isinstance(node, ast.Compare) and len(node.ops) > 1:
                return recurse(ast.BoolOp(ast.And(), [ast.Compare(node.left if i == 0 else node.comparators[i - 1], [node.ops[i]], [node.comparators[i]]) for i in range(len(node.ops))]), relations=True)

            elif isinstance(node, ast.Compare):
                raise ExpressionError("comparison operators are only allowed at the top of an expression: {0}".format(histbook.util.astunparse.tostring(node).strip()))

            elif relations and isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
                content = recurse(node.operand, relations=True)
                if isinstance(content, Name):
                    return Predicate(content.value, positive=False)
                else:
                    return Logical.negate(content).simplify()

            elif relations and isinstance(node, ast.BoolOp) and isinstance(node.op, ast.And):
                return functools.reduce(LogicalAnd.combine, [Logical.normalform(recurse(x, relations=True)) for x in node.values]).simplify()

            elif relations and isinstance(node, ast.BoolOp) and isinstance(node.op, ast.Or):
                return functools.reduce(LogicalOr.combine, [Logical.normalform(recurse(x, relations=True)) for x in node.values]).simplify()

            elif isinstance(node, ast.BoolOp):
                raise ExpressionError("logical operators are only allowed at the top of an expression: {0}".format(histbook.util.astunparse.tostring(node).strip()))

            elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
                content = recurse(node.operand)
                if isinstance(content, Const):
                    return Const(-content.value)
                else:
                    return PlusMinus.negate(content).simplify()

            elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.UAdd):
                return recurse(node.operand)

            elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Invert):
                content = recurse(node.operand)
                if isinstance(content, Const):
                    return Const(~content.value)
                else:
                    return BitAnd.negate(content).simplify()

            elif isinstance(node, ast.UnaryOp):
                raise ExpressionError("only unary operators supported: 'not', '-', '+', and '~': {0}".format(histbook.util.astunparse.tostring(node).strip()))

            elif isinstance(node, ast.BinOp):
                if   isinstance(node.op, ast.Add):      fcn = "+"
                elif isinstance(node.op, ast.Sub):      fcn = "-"
                elif isinstance(node.op, ast.Mult):     fcn = "*"
                elif isinstance(node.op, ast.Div):      fcn = "/"
                elif isinstance(node.op, ast.FloorDiv): op, fcn = "//", "numpy.floor_divide"
                elif isinstance(node.op, ast.Mod):      op, fcn = "%",  "fmod"
                elif isinstance(node.op, ast.Pow):      op, fcn = "**", "pow"
                elif isinstance(node.op, ast.BitOr):    fcn = "|"
                elif isinstance(node.op, ast.BitAnd):   fcn = "&"
                elif isinstance(node.op, ast.BitXor):   op, fcn = "^",  "xor"
                else:
                    raise ExpressionError("only binary operators supported: '+', '-', '*', '/', '//', '%', '**', '|', '&', and '^': {0}".format(histbook.util.astunparse.tostring(node).strip()))

                left = recurse(node.left)
                right = recurse(node.right)

                if isinstance(left, Const) and isinstance(right, Const):
                    return Const(calculate[fcn](left.value, right.value))

                if fcn == "+":
                    return PlusMinus.combine(left, right).simplify()
                elif fcn == "-":
                    return PlusMinus.combine(left, PlusMinus.negate(right)).simplify()
                elif fcn == "*":
                    return PlusMinus.distribute(left, right).simplify()
                elif fcn == "/":
                    # PlusMinus is implemented as a RingAlgebra, but it's really a Field
                    right = PlusMinus.normalform(right)

                    if len(right.pos) == len(right.neg) == 0:
                        negation = TimesDiv(TimesDiv.negateval(right.const), (), ())
                    elif right.const == PlusMinus.identity and len(right.pos) == 1 and len(right.neg) == 0:
                        negation = TimesDiv.negate(right.pos[0])
                    elif right.const == PlusMinus.identity and len(right.pos) == 0 and len(right.neg) == 1:
                        negation = TimesDiv.negate(right.neg[0])
                        negation.const = PlusMinus.negateval(negation.const)
                    else:
                        negation = TimesDiv.negate(right)   # additive terms in the denominator

                    return PlusMinus.distribute(left, negation).simplify()

                elif fcn == "pow" and isinstance(right, Const) and right.value == round(right.value) and 1 <= right.value < 5:
                    n = int(right.value)
                    left = PlusMinus.normalform(left)
                    if left.const == PlusMinus.identity and len(left.pos) == 1 and len(left.neg) == 0:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(left.pos[0].const**n, tuple(sorted(left.pos[0].pos * n)), tuple(sorted(left.pos[0].neg * n))),), ()).simplify()
                    elif left.const == PlusMinus.identity and len(left.pos) == 0 and len(left.neg) == 1 and n % 2 == 0:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(left.neg[0].const**n, tuple(sorted(left.neg[0].pos * n)), tuple(sorted(left.neg[0].neg * n))),), ()).simplify()
                    elif left.const == PlusMinus.identity and len(left.pos) == 0 and len(left.neg) == 1:
                        return PlusMinus(PlusMinus.identity, (), (TimesDiv(left.neg[0].const**n, tuple(sorted(left.neg[0].pos * n)), tuple(sorted(left.neg[0].neg * n))),)).simplify()
                    else:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(TimesDiv.identity, tuple((left,) * n), ()),), ()).simplify()

                elif fcn == "pow" and isinstance(right, Const) and right.value == round(right.value) and -5 < right.value <= -1:
                    n = -int(right.value)
                    left = PlusMinus.normalform(left)
                    if left.const == PlusMinus.identity and len(left.pos) == 1 and len(left.neg) == 0:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(left.pos[0].const**(-n), tuple(sorted(left.pos[0].neg * n)), tuple(sorted(left.pos[0].pos * n))),), ()).simplify()
                    elif left.const == PlusMinus.identity and len(left.pos) == 0 and len(left.neg) == 1 and n % 2 == 0:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(left.neg[0].const**(-n), tuple(sorted(left.neg[0].neg * n)), tuple(sorted(left.neg[0].pos * n))),), ()).simplify()
                    elif left.const == PlusMinus.identity and len(left.pos) == 0 and len(left.neg) == 1:
                        return PlusMinus(PlusMinus.identity, (), (TimesDiv(left.neg[0].const**(-n), tuple(sorted(left.neg[0].neg * n)), tuple(sorted(left.neg[0].pos * n))),)).simplify()
                    else:
                        return PlusMinus(PlusMinus.identity, (TimesDiv(TimesDiv.identity, (), tuple((left,) * n)),), ()).simplify()

                elif fcn == "&":
                    raise NotImplementedError

                elif fcn == "|":
                    raise NotImplementedError

                else:
                    return BinOp(fcn, left, right, op)

            elif isinstance(node, ast.Call):
                fcn = Expr.recognized.get(resolve(node.func), None)

                if fcn is None and node.func.id in Expr.recognized.values():
                    fcn = node.func.id

                elif fcn is not None:
                    for n, x in Expr.recognized.items():
                        if fcn is n:
                            fcn = x
                            break

                if fcn is None:
                    raise ExpressionError("unhandled function in expression: {0}".format(histbook.util.astunparse.tostring(node).strip()))

                return Call(fcn, *(recurse(x, relations=(i == 0 and fcn == "where")) for i, x in enumerate(node.args)))

            else:
                ExpressionError("unhandled syntax in expression: {0}".format(histbook.util.astunparse.tostring(node).strip()))

        return recurse(pyast, relations=True)

    maybeconstants = {"pi": numpy.pi, "Pi": numpy.pi,
                      "e": numpy.e, "E": numpy.e,
                      "inf": numpy.inf, "Inf": numpy.inf, "infinity": numpy.inf, "Infinity": numpy.inf,
                      "nan": numpy.nan, "NaN": numpy.nan, "Nan": numpy.nan}

    recognized = {abs: "abs", max: "max", min: "min"}

class _Placeholder(object):
    count = 0
    def __init__(self):
        self.index = _Placeholder.count
        _Placeholder.count += 1
    def __repr__(self):
        return "Placeholder({0})".format(self.index)
    def __hash__(self):
        return hash((_Placeholder, self.index))
    def __eq__(self, other):
        return self.__class__ is other.__class__ and self.index == other.index
    def __ne__(self, other):
        return not self.__eq__(other)

def _recognize(module, pyname, name):
    if hasattr(module, pyname):
        Expr.recognized[getattr(module, pyname)] = name
    else:
        Expr.recognized[_Placeholder()] = name

Expr.recognized[_Placeholder()] = "fabs"
Expr.recognized[_Placeholder()] = "fmin"
Expr.recognized[_Placeholder()] = "fmax"
Expr.recognized[_Placeholder()] = "fmod"

_recognize(math, "acos", "arccos")
_recognize(math, "acosh", "arccosh")
_recognize(math, "asin", "arcsin")
_recognize(math, "asinh", "arcsinh")
_recognize(math, "atan2", "arctan2")
_recognize(math, "atan", "arctan")
_recognize(math, "atanh", "arctanh")
_recognize(math, "ceil", "ceil")
_recognize(math, "copysign", "copysign")
_recognize(math, "cos", "cos")
_recognize(math, "cosh", "cosh")
_recognize(math, "degrees", "rad2deg")    # not in numexpr
_recognize(math, "erfc", "erfc")
_recognize(math, "erf", "erf")
_recognize(math, "exp", "exp")
_recognize(math, "expm1", "expm1")
_recognize(math, "factorial", "factorial")    # not in numexpr
_recognize(math, "floor", "floor")
_recognize(math, "fmod", "mod")
_recognize(math, "gamma", "gamma")    # not in numexpr (has lgamma and tgamma)
_recognize(math, "hypot", "hypot")
_recognize(math, "isinf", "isinf")
_recognize(math, "isnan", "isnan")
_recognize(math, "lgamma", "lgamma")
_recognize(math, "log10", "log10")
_recognize(math, "log1p", "log1p")
_recognize(math, "log", "log")
_recognize(math, "pow", "pow")    # not in numexpr (only as operator **)
_recognize(math, "radians", "deg2rad")    # not in numexpr
_recognize(math, "sinh", "sinh")
_recognize(math, "sin", "sin")
_recognize(math, "sqrt", "sqrt")
_recognize(math, "tanh", "tanh")
_recognize(math, "tan", "tan")
_recognize(math, "trunc", "trunc")

_recognize(numpy, "absolute", "abs")
_recognize(numpy, "arccos", "arccos")
_recognize(numpy, "arccosh", "arccosh")
_recognize(numpy, "arcsin", "arcsin")
_recognize(numpy, "arcsinh", "arcsinh")
_recognize(numpy, "arctan2", "arctan2")
_recognize(numpy, "arctan", "arctan")
_recognize(numpy, "arctanh", "arctanh")
_recognize(numpy, "ceil", "ceil")
_recognize(numpy, "conjugate", "conj")
_recognize(numpy, "copysign", "copysign")
_recognize(numpy, "cos", "cos")
_recognize(numpy, "cosh", "cosh")
_recognize(numpy, "deg2rad", "deg2rad")    # not in numexpr
_recognize(numpy, "degrees", "rad2deg")    # not in numexpr
_recognize(numpy, "exp2", "exp2")
_recognize(numpy, "exp", "exp")
_recognize(numpy, "expm1", "expm1")
_recognize(numpy, "floor", "floor")
_recognize(numpy, "fmod", "fmod")
_recognize(numpy, "heaviside", "heaviside")    # not in numexpr
_recognize(numpy, "hypot", "hypot")
_recognize(numpy, "isfinite", "isfinite")
_recognize(numpy, "isinf", "isinf")
_recognize(numpy, "isnan", "isnan")
_recognize(numpy, "log10", "log10")
_recognize(numpy, "log1p", "log1p")
_recognize(numpy, "log2", "log2")
_recognize(numpy, "logaddexp2", "logaddexp2")    # not in numexpr
_recognize(numpy, "logaddexp", "logaddexp")    # not in numexpr
_recognize(numpy, "log", "log")
_recognize(numpy, "maximum", "fmax")
_recognize(numpy, "minimum", "fmin")
_recognize(numpy, "power", "pow")    # not in numexpr (only as operator **)
_recognize(numpy, "rad2deg", "rad2deg")    # not in numexpr
_recognize(numpy, "radians", "deg2rad")    # not in numexpr
_recognize(numpy, "remainder", "mod")
_recognize(numpy, "rint", "rint")
_recognize(numpy, "round", "round")
_recognize(numpy, "sign", "sign")    # not in numexpr
_recognize(numpy, "sinh", "sinh")
_recognize(numpy, "sin", "sin")
_recognize(numpy, "sqrt", "sqrt")
_recognize(numpy, "tanh", "tanh")
_recognize(numpy, "tan", "tan")
_recognize(numpy, "trunc", "trunc")
_recognize(numpy, "where", "where")

### in numexpr, but not defined here (TODO)
# "abs2"
# "bool"
# "cast"
# "cbrt"
# "complex"
# "copy"
# "crosspower"
# "fdim"
# "float32"
# "float64"
# "fma"
# "fpclassify"
# "ilogb"
# "imag"
# "int16"
# "int32"
# "int64"
# "int8"
# "isnormal"
# "lgamma"
# "logb"
# "logical_and"
# "logical_or"
# "lrint"
# "lround"
# "nearbyint"
# "neg"
# "nextafter"
# "nexttoward"
# "ones_like"
# "real"
# "scalbln"
# "signbit"
# "tgamma"
# "uint16"
# "uint32"
# "uint64"
# "uint8"
# "unsafe_cast"

class Const(Expr):
    """Represents a literal constant in the expression tree, such as a number, boolean, or ``None``."""

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

    def _reprargs(self):
        return (repr(self.value),)

    def __str__(self):
        return str(self.value)

    def __hash__(self):
        if isinstance(self.value, set):
            value = (set, tuple(sorted(self.value)))
        else:
            value = self.value
        return hash((Const, value))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.value == other.value

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            if type(self.value).__name__ == type(other.value).__name__:
                return self.value < other.value
            else:
                return type(self.value).__name__ < type(other.value).__name__
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def rename(self, names):
        return self

class Name(Expr):
    """Represents a named variable in the expression tree."""

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

    def _reprargs(self):
        return (repr(self.value),)

    def __str__(self):
        return self.value

    def __hash__(self):
        return hash((Name, self.value))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.value == other.value

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return self.value < other.value
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def rename(self, names):
        assert self in names
        return Name(names[self])

class BroadcastConst(Name):
    """Represents a literal constant in the expression tree that should be broadcast to the length of field data."""

    def __init__(self, name, value):
        self.name = name
        self.value = value

    def _reprargs(self):
        return (repr(self.name), repr(self.value))

    def __str__(self):
        if self.name is None:
            return str(self.value)
        else:
            return self.name

    def __hash__(self):
        if isinstance(self.value, set):
            value = (set, tuple(sorted(self.value)))
        else:
            value = self.value
        return hash((Const, self.name, value))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.name == other.name and self.value == other.value

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            if type(self.name).__name__ == type(other.name).__name__:
                if self.name == other.name:
                    if type(self.value).__name__ == type(other.value).__name__:
                        return self.value < other.value
                    else:
                        return type(self.value).__name__ < type(other.value).__name__
                else:
                    self.name < other.name
            else:
                return type(self.name).__name__ < type(other.name).__name__
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def rename(self, names):
        assert self in names
        return BroadcastConst(names[self], self.value)

class Call(Expr):
    """Represents a function call in the expression tree."""

    def __init__(self, fcn, *args):
        self.fcn = fcn
        self.args = args

    def _reprargs(self):
        return (repr(self.fcn),) + tuple(repr(x) for x in self.args)

    def __str__(self):
        return "{0}({1})".format(self.fcn, ", ".join(str(x) for x in self.args))

    def __hash__(self):
        return hash((Call, self.fcn, self.args))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.fcn == other.fcn and self.args == other.args

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return (self.fcn, self.args) < (other.fcn, other.args)
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def rename(self, names):
        if self in names:
            return Name(names[self])
        else:
            return self.__class__(self.fcn, *(x.rename(names) for x in self.args))

class BinOp(Call):
    """Represents a binary operation in the expression tree, such as ``%`` or ``**``."""

    def __init__(self, fcn, left, right, op):
        super(BinOp, self).__init__(fcn, left, right)
        self.op = op

    def __str__(self):
        return (" " + self.op + " ").join(("(" + str(x) + ")") if isinstance(x, BinOp) else str(x) for x in self.args)

class RingAlgebra(Expr):
    """Abstract class for ring algebras, such as addition and multiplication."""

    def __init__(self, const, pos, neg):
        self.const = const
        self.pos = pos
        self.neg = neg

    def _reprargs(self):
        return (repr(self.const), repr(self.pos), repr(self.neg))

    def __hash__(self):
        return hash((self.__class__, self.const, self.pos, self.neg))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.const == other.const and self.pos == other.pos and self.neg == other.neg

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return (self.const, self.pos, self.neg) < (other.const, other.pos, other.neg)
        else:
            return self.__class__.__name__ < other.__class__.__name__

    commutative = False

    @classmethod
    def negate(op, arg):
        arg = op.normalform(arg)
        return op(op.negateval(arg.const), arg.neg, arg.pos)

    @classmethod
    def collect(op, arg):
        return arg

    @classmethod
    def combine(op, left, right):
        left, right = op.normalform(left), op.normalform(right)

        if op.commutative:
            pos = tuple(sorted(left.pos + right.pos))
            neg = tuple(sorted(left.neg + right.neg))
        else:
            pos = left.pos + right.pos
            neg = left.neg + right.neg

        return op.collect(op(op.calcval(left.const, right.const), pos, neg))

    def rename(self, names):
        if self in names:
            return Name(names[self])
        else:
            return self.__class__(self.const, tuple(x.rename(names) for x in self.pos), tuple(x.rename(names) for x in self.neg))

class RingAlgebraMultLike(RingAlgebra):
    """Abstract class for the multiplication-like part of a ring algebra."""

    @classmethod
    def normalform(op, arg):
        if isinstance(arg, op):
            return arg
        elif isinstance(arg, Const):
            return op(arg.value, (), ())
        else:
            return op(op.identity, (arg,), ())

    def similar(self, other):
        return isinstance(other, self.__class__) and self.pos == other.pos and self.neg == other.neg

class RingAlgebraAddLike(RingAlgebra):
    """Abstract class for the addition-like part of a ring algebra."""

    @classmethod
    def normalform(op, arg):
        if isinstance(arg, op):
            return arg
        else:
            arg = op.subop.normalform(arg)
            if len(arg.pos) == len(arg.neg) == 0:
                return op(arg.const, (), ())
            elif op.isnegval(arg.const):
                return op(op.identity, (), (op.subop(op.negateval(arg.const), arg.pos, arg.neg),))
            else:
                return op(op.identity, (arg,), ())

    @classmethod
    def collect(op, arg):
        const = arg.const
        terms = []

        def cancel(x):
            pos, neg = list(x.pos), []
            for y in x.neg:
                try:
                    i = x.pos.index(y)
                except ValueError:
                    neg.append(y)
                else:
                    del pos[i]
            return op.subop(x.const, tuple(pos), tuple(neg))

        for x in arg.pos:
            x = cancel(x)
            if x.const == op.identity:
                pass
            elif len(x.pos) == len(x.neg) == 0:
                const = op.calcval(const, x.const)
            else:
                for y in terms:
                    if x.similar(y):
                        y.const = op.calcval(x.const, y.const)
                        break
                else:
                    terms.append(x)

        for x in arg.neg:
            x = cancel(x)
            if x.const == op.identity:
                pass
            elif len(x.pos) == len(x.neg) == 0:
                const = op.calcval(const, op.negateval(x.const))
            else:
                for y in terms:
                    if x.similar(y):
                        y.const = op.calcval(op.negateval(x.const), y.const)
                        break
                else:
                    terms.append(op.subop(op.negateval(x.const), x.pos, x.neg))

        posterms, negterms = [], []
        for x in terms:
            if x.const == op.identity:
                pass
            elif op.isnegval(x.const):
                x.const = op.negateval(x.const)
                negterms.append(x)
            else:
                posterms.append(x)

        if op.commutative:
            posterms.sort()
            negterms.sort()

        return op(const, tuple(posterms), tuple(negterms))

    @classmethod
    def distribute(op, left, right):
        left, right = op.normalform(left), op.normalform(right)
        pos, neg = [], []

        pos += [op.subop.combine(x, Const(right.const)) for x in left.pos]
        neg += [op.subop.combine(x, Const(right.const)) for x in left.neg]
        pos += [op.subop.combine(Const(left.const), y) for y in right.pos]
        neg += [op.subop.combine(Const(left.const), y) for y in right.neg]

        pos += [op.subop.combine(x, y) for x, y in itertools.product(left.pos, right.pos)]
        neg += [op.subop.combine(x, y) for x, y in itertools.product(left.pos, right.neg)]
        pos += [op.subop.combine(x, y) for x, y in itertools.product(left.neg, right.neg)]
        neg += [op.subop.combine(x, y) for x, y in itertools.product(left.neg, right.pos)]

        return op.collect(op(op.identity, tuple(pos), tuple(neg)))

    def simplify(self):
        out = self.__class__(self.const, self.pos, self.neg)

        if len(out.pos) == len(out.neg) == 0:
            return Const(out.const)

        elif out.const == out.identity and len(out.pos) + len(out.neg) == 1:
            if len(out.pos) == 1:
                out = out.pos[0]
            else:
                out = self.subop(self.negateval(out.neg[0].const), out.neg[0].pos, out.neg[0].neg)

            if len(out.pos) == len(out.neg) == 0:
                return Const(out.const)
            elif out.const == out.identity and len(out.pos) == 1 and len(out.neg) == 0:
                return out.pos[0]

        return out

class RingAlgebraBinOp(object):
    """Abstract class for a binary operation in a ring algebra (for pretty printing)."""

    def __str__(self):
        out = []
        if self.const != self.identity or len(self.pos) == 0:
            out.append(repr(self.const))
        for x in self.pos:
            if len(out) > 0:
                out.append(self.posop)
            out.append(str(x))
        for x in self.neg:
            out.append(self.negop)
            out.append(str(x))
        return "".join(out)

class TimesDiv(RingAlgebraBinOp, RingAlgebraMultLike):
    """Represents multiplication and division in a ring algebra (actually, a whole field because it includes division)."""

    posop = "*"
    negop = "/"

    commutative = True
    identity = 1

    @staticmethod
    def negateval(value):
        return 1.0 / value            # only because arithmetic is a field, not a ring

    @staticmethod
    def calcval(left, right):
        return left * right

class PlusMinus(RingAlgebraBinOp, RingAlgebraAddLike):
    """Represents addition and subtraction in a ring algebra (actually, a whole field because it includes division)."""

    posop = " + "
    negop = " - "

    subop = TimesDiv
    commutative = True
    identity = 0

    @staticmethod
    def negateval(value):
        return -value

    @staticmethod
    def isnegval(value):
        return value < 0

    @staticmethod
    def calcval(left, right):
        return left + right

### FIXME: NotImplementedError

# class BitAnd(RingAlgebraMultLike):
#     commutative = True
#     identity = numpy.uint64(-1)

#     @staticmethod
#     def negateval(value):
#         raise AssertionError(value)   # bitwise arithmetic really is a ring

#     @staticmethod
#     def calcval(left, right):
#         return numpy.uint64(left) & numpy.uint64(right)

# class BitOr(RingAlgebraAddLike):
#     subop = BitAnd
#     commutative = True
#     identity = numpy.uint64(0)

#     @staticmethod
#     def negateval(value):
#         return ~numpy.uint64(value)

#     @staticmethod
#     def isnegval(value):
#         return False

#     @staticmethod
#     def calcval(left, right):
#         return numpy.uint64(left) | numpy.uint64(right)

class Logical(object):
    """Mix-in for logical operations."""

    commutative = True

    def __init__(self, *args):
        self.args = tuple(sorted(set(args)))

    def _reprargs(self):
        return tuple(repr(x) for x in self.args)

    def __hash__(self):
        return hash((self.__class__, self.args))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.args == other.args

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return self.args < other.args
        else:
            return self.__class__.__name__ < other.__class__.__name__

    @classmethod
    def normalform(cls, arg):
        if isinstance(arg, Name):
            return LogicalOr(LogicalAnd(Predicate(arg.value)))
        elif isinstance(arg, Predicate):
            return LogicalOr(LogicalAnd(arg))
        elif isinstance(arg, Relation):
            return LogicalOr(LogicalAnd(arg))
        elif isinstance(arg, LogicalAnd):
            return LogicalOr(arg)
        elif isinstance(arg, LogicalOr):
            return arg
        else:
            raise AssertionError(arg)

    @classmethod
    def negate(op, arg):
        logicalor = op.normalform(arg)
        upsidedown = [[relation.negate() for relation in logicaland.args] for logicaland in logicalor.args]
        return LogicalOr(*(LogicalAnd(*x) for x in itertools.product(*upsidedown)))

    def rename(self, names):
        if self in names:
            return Name(names[self])
        else:
            return self.__class__(*(x.rename(names) for x in self.args))

class LogicalAnd(Logical, RingAlgebraMultLike):
    """Represents logical and in a ring algebra."""

    @classmethod
    def combine(op, left, right):
        left, right = op.normalform(left), op.normalform(right)
        return LogicalOr(*(LogicalAnd(*(x.args + y.args)) for x, y in itertools.product(left.args, right.args)))

    def simplify(self):
        if len(self.args) == 0 or all(x == Const(True) for x in self.args):
            return Const(True)
        elif any(x == Const(False) for x in self.args):
            return Const(False)
        elif len(self.args) == 1:
            return self.args[0]
        else:
            return self

class LogicalOr(Logical, RingAlgebraAddLike):
    """Represents logical or in a ring algebra."""

    @classmethod
    def combine(op, left, right):
        left, right = op.normalform(left), op.normalform(right)
        return LogicalOr(*(left.args + right.args))

    def simplify(self):
        if len(self.args) == 0 or all(x == Const(False) for x in self.args):
            return Const(False)
        elif any(x == Const(True) for x in self.args):
            return Const(True)
        elif len(self.args) == 1:
            return self.args[0].simplify()
        else:
            return self

class Relation(Expr):
    """Represents a logical relation in the expression tree, such as ``==`` and ``in``."""

    def __init__(self, cmp, left, right):
        self.cmp = cmp
        self.left = left
        self.right = right

    def _reprargs(self):
        return (repr(self.cmp), repr(self.left), repr(self.right))

    def __str__(self):
        return "{0} {1} {2}".format(str(self.left), self.cmp, str(self.right))

    def __hash__(self):
        return hash((Relation, self.cmp, self.left, self.right))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.cmp == other.cmp and self.left == other.left and self.right == other.right

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return (self.cmp, self.left, self.right) < (other.cmp, other.left, other.right)
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def negate(self):
        if self.cmp == "==":
            return Relation("!=", self.left, self.right)
        elif self.cmp == "!=":
            return Relation("==", self.left, self.right)
        elif self.cmp == "<":
            return Relation("<=", self.right, self.left)
        elif self.cmp == "<=":
            return Relation("<", self.right, self.left)
        elif self.cmp == "in":
            return Relation("not in", self.left, self.right)
        elif self.cmp == "not in":
            return Relation("in", self.left, self.right)
        else:
            raise AssertionError(self.cmp)

class Predicate(Expr):
    """Represents a logical predicate (name identified as boolean) in the expression tree."""

    def __init__(self, value, positive=True):
        self.value = value
        self.positive = positive

    def _reprargs(self):
        return (repr(self.value), repr(self.positive))

    def __str__(self):
        if self.positive:
            return self.value
        else:
            return "not " + self.value

    def __hash__(self):
        return hash((Predicate, self.value, self.positive))

    def __eq__(self, other):
        return self.__class__.__name__ == other.__class__.__name__ and self.value == other.value and self.positive == other.positive

    def __lt__(self, other):
        if self.__class__.__name__ == other.__class__.__name__:
            return (self.value, self.positive) < (other.value, other.positive)
        else:
            return self.__class__.__name__ < other.__class__.__name__

    def negate(self):
        return Predicate(self.value, positive=not self.positive)

    def rename(self, names):
        assert self in names
        return Predicate(names[self])


# -*- coding: utf-8 -*-

# 新闻下载中间件
# by wooght 2011-11
# 雪球头条,新浪/网易/腾讯 股票相关新闻,雪球访谈

import time

import numpy as np
from scrapy.http import HtmlResponse

from caijing_scrapy.middlewares.Wmiddlewares import Wdownloadmiddlewares
from common.werror import Werror

class WooghtDownloadMiddleware(Wdownloadmiddlewares):
    # 开启webdriver 设置phantomjs
    def __init__(self):
        self.stdout_utf8 = False
        self.timeout = 20
        self.set_cap()

    # 执行下载操作,返回response
    def process_request(self, request, spider):
        js = "var q=document.body.scrollTop=2000"
        url = request.url
        self.delay()

        # 返回None  将交由下一个middle操作
        # 返回request 将从新构建请求
        # 返回response 返回给spider的paser操作,跳过后面的downloadermiddlewares
        if 'phantomjs' not in request.meta.keys():
            print('get agentmiddleware to run', request.url)
            return None
        # 尝试打开网页 无法打开则跳过 返回None
        try:
            self.open_url(url)
        except Werror as e:
            print('open_url deild', e)
        except ConnectionRefusedError:
            return None

        # 雪球头条
        if spider.name == 'topics':
            print('------------>xueqiu------>')
            if url == "https://xueqiu.com":
                arr_num = np.arange(20)
                for i in arr_num:
                    time.sleep(1)
                    js = "var a=document.documentElement.scrollTop=" + str(i * 3000) + ";"
                    self.driver.execute_script(js)
                for i in arr_num:
                    time.sleep(2)
                    button_class = self.driver.find_element_by_class_name('home__timeline__more')
                    print('-=-=---===xueqiu button click--====--=-=-=-=')
                    button_class.click()
        # 新闻
        elif spider.name == 'news':
            not_html = 'html' in url
            if not not_html and 'yicai' in url:
                print('------------>yicai------>')
                arr_num = np.arange(20)
                for i in arr_num:
                    button_id = self.driver.find_element_by_id('divMore')  # 多次点击更多按钮
                    time.sleep(2)
                    button_id.click()
            elif "money.163.com" in url:
                print('------------>mony163------>')
                arr_num = np.arange(20)
                for i in arr_num:
                    time.sleep(2)
                    # js = 'var a = clickLoadMore();'
                    # self.driver.execute_script(js)
                    try:
                        load_more_btn = self.driver.find_element_by_class_name('load_more_btn')
                        load_more_btn.click()
                        print('cliek_button:', i)
                    except Exception as e:
                        print('no more..')
                        break
            else:
                print('------------>sina,qq-->---------->')

        # 雪球访谈
        elif spider.name == 'xq_talks':
            # 获取token的值 并存放在title标签中,供spider使用
            js = "var a = WATCH.appStart;var b = SNB.data.access_token;document.getElementsByTagName('title')[" \
                 "0].innerHTML=a+','+b; "
            self.driver.execute_script(js)
        body = self.driver.page_source
        print(self.driver.title + 'SUCCESS--To spider')
        # print(self.driver.window_handles)                   #打印当前所有窗口
        return HtmlResponse(body=body, encoding='utf-8', request=request, url=str(url))


# Source: https://www.hackerrank.com/challenges/repeated-string/problem?isFullScreen=true

import math

def main():

    INPUT_STRING = input()
    N_CHARACTERS = int(input())

    OBJ = RepeatedString(INPUT_STRING, N_CHARACTERS)
    QUANTITY_OF_A = OBJ.getQuantityOfA()
    print(QUANTITY_OF_A)

class RepeatedString:

    def __init__(self, inputString, nCharacters):

        self.__inputString = inputString
        self.__nCharacters = nCharacters
        self.__quantityOfA = 0

        self.__findQuantityOfAInRepeatedString()

    def __findQuantityOfAInRepeatedString(self):

        self.__quantityOfA = self.__findQuantityOfAInString(self.__inputString)
        self.__quantityOfA *= self.__findQuantityOfRepeatedEntireString()
        self.__quantityOfA += self.__findQuantityOfRemainingA()

    def __findQuantityOfAInString(self, inputString):

        quantityOfA = 0

        for character in inputString:
            if character == 'a':
                quantityOfA += 1

        return quantityOfA

    def __findQuantityOfRepeatedEntireString(self):

        N_STRINGS = math.trunc(self.__nCharacters / len(self.__inputString))
        return N_STRINGS

    def __findQuantityOfRemainingA(self):

        N_REMAINING_CHARACTERS_OF_STRING = self.__nCharacters % len(self.__inputString)

        if N_REMAINING_CHARACTERS_OF_STRING == 0:
            return 0

        SUB_STRING = self.__inputString[:N_REMAINING_CHARACTERS_OF_STRING]
        return self.__findQuantityOfAInString(SUB_STRING)

    def getQuantityOfA(self):

        return self.__quantityOfA

if __name__ == "__main__":
    main()

hackerrank/problem solving/algorithms/2 - implementation/36 - repeated string/oo/repeated_string_oo.py


import os
import pwd
import grp
import time
import errno
from stat import *

from Mailman import mm_cfg
from Mailman import Utils
from Mailman import LockFile
from Mailman.i18n import C_
from Mailman.MailList import MailList
from Mailman.MTA.Utils import makealiases
from Mailman.Logging.Syslog import syslog

LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'creator')
ALIASFILE = os.path.join(mm_cfg.DATA_DIR, 'aliases')
VIRTFILE = os.path.join(mm_cfg.DATA_DIR, 'virtual-mailman')
# Desired mode for aliases(.db) and virtual-mailman(.db) for both creation
# and check_perms.
targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH

def _update_maps():
    # Helper function to fix owner and mode.
    def fixom(file):
        # It's not necessary for the non-db file to be S_IROTH, but for
        # simplicity and compatibility with check_perms, we set it.
        stat = os.stat(file)
        if (stat[ST_MODE] & targetmode) != targetmode:
            os.chmod(file, stat[ST_MODE] | targetmode)
        dbfile = file + '.db'
        try:
            stat = os.stat(dbfile)
        except OSError as e:
            if e.errno != errno.ENOENT:
                raise
            return
        if (stat[ST_MODE] & targetmode) != targetmode:
            os.chmod(dbfile, stat[ST_MODE] | targetmode)
        user = mm_cfg.MAILMAN_USER
        if stat[ST_UID] != pwd.getpwnam(user)[2]:
            uid = pwd.getpwnam(user)[2]
            gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2]
            os.chown(dbfile, uid, gid)
    msg = 'command failed: %s (status: %s, %s)'
    acmd = mm_cfg.POSTFIX_ALIAS_CMD + ' ' + ALIASFILE
    status = (os.system(acmd) >> 8) & 0xff
    if status:
        errstr = os.strerror(status)
        syslog('error', msg, acmd, status, errstr)
        raise RuntimeError(msg % (acmd, status, errstr))
    # Fix owner and mode of .db if needed.
    fixom(ALIASFILE)
    if os.path.exists(VIRTFILE):
        vcmd = mm_cfg.POSTFIX_MAP_CMD + ' ' + VIRTFILE
        status = (os.system(vcmd) >> 8) & 0xff
        if status:
            errstr = os.strerror(status)
            syslog('error', msg, vcmd, status, errstr)
            raise RuntimeError(msg % (vcmd, status, errstr))
        # Fix owner and mode of .db if needed.
        fixom(VIRTFILE)

def makelock():
    return LockFile.LockFile(LOCKFILE)

def _zapfile(filename):
    # Truncate the file w/o messing with the file permissions, but only if it
    # already exists.
    if os.path.exists(filename):
        fp = open(filename, 'w')
        fp.close()

def clear():
    _zapfile(ALIASFILE)
    _zapfile(VIRTFILE)

def _addlist(mlist, fp):
    # Set up the mailman-loop address
    loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0]
    loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox')
    # Seek to the end of the text file, but if it's empty write the standard
    # disclaimer, and the loop catch address.
    fp.seek(0, 2)
    if not fp.tell():
        print("""\
# This file is generated by Mailman, and is kept in sync with the
# binary hash file aliases.db.  YOU SHOULD NOT MANUALLY EDIT THIS FILE
# unless you know what you're doing, and can keep the two files properly
# in sync.  If you screw it up, you're on your own.
""", file=fp)
        print('# The ultimate loop stopper address', file=fp)
        print('%s: %s' % (loopaddr, loopmbox), file=fp)
        print(file=fp)
    # Bootstrapping.  bin/genaliases must be run before any lists are created,
    # but if no lists exist yet then mlist is None.  The whole point of the
    # exercise is to get the minimal aliases.db file into existance.
    if mlist is None:
        return
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    # The text file entries get a little extra info
    print('# STANZA START:', listname, file=fp)
    print('# CREATED:', time.ctime(time.time()), file=fp)
    # Now add all the standard alias entries
    for k, v in makealiases(listname):
        # Format the text file nicely
        print(k + ':', ((fieldsz - len(k)) * ' ') + v, file=fp)
    # Finish the text file stanza
    print('# STANZA END:', listname, file=fp)
    print(file=fp)

def _isvirtual(mlist):
    return (mlist and mlist.host_name.lower() in
            [d.lower() for d in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS])

def _addvirtual(mlist, fp):
    listname = mlist.internal_name()
    fieldsz = len(listname) + len('-unsubscribe')
    hostname = mlist.host_name
    # Set up the mailman-loop address
    loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
    loopdest = Utils.ParseEmail(loopaddr)[0]
    # And the site list posting address.
    siteaddr = Utils.get_site_email(mlist.host_name)
    sitedest = Utils.ParseEmail(siteaddr)[0]
    # And the site list -owner, -bounces and -request addresses.
    siteowneraddr = Utils.get_site_email(mlist.host_name, extra='owner')
    siteownerdest = Utils.ParseEmail(siteowneraddr)[0]
    sitebouncesaddr = Utils.get_site_email(mlist.host_name, extra='bounces')
    sitebouncesdest = Utils.ParseEmail(sitebouncesaddr)[0]
    siterequestaddr = Utils.get_site_email(mlist.host_name, extra='request')
    siterequestdest = Utils.ParseEmail(siterequestaddr)[0]
    if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN:
        loopdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        sitedest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        siteownerdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        sitebouncesdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        siterequestdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
    # If the site list's host_name is a virtual domain, adding the list and
    # owner addresses to the SITE ADDRESSES will duplicate the entries in the
    # stanza for the list.  Postfix doesn't like dups so we try to comment them
    # here, but only for the actual site list domain.
    if (MailList(mm_cfg.MAILMAN_SITE_LIST, lock=False).host_name.lower() ==
            hostname.lower()):
        siteaddr = '#' + siteaddr
        siteowneraddr = '#' + siteowneraddr
        sitebouncesaddr = '#' + sitebouncesaddr
        siterequestaddr = '#' + siterequestaddr
    # Seek to the end of the text file, but if it's empty write the standard
    # disclaimer, and the loop catch address and site address.
    fp.seek(0, 2)
    if not fp.tell():
        print("""\
# This file is generated by Mailman, and is kept in sync with the binary hash
# file virtual-mailman.db.  YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you
# know what you're doing, and can keep the two files properly in sync.  If you
# screw it up, you're on your own.
#
# Note that you should already have this virtual domain set up properly in
# your Postfix installation.  See README.POSTFIX for details.

# LOOP ADDRESSES START
%s\t%s
# LOOP ADDRESSES END

# We also add the site list address in each virtual domain as that address
# is exposed on admin and listinfo overviews, and we add the site list-owner,
# -bounces and -request addresses as they are exposed in the list created
# and/or password reminder email notices.

# SITE ADDRESSES START
%s\t%s
%s\t%s
%s\t%s
%s\t%s
# SITE ADDRESSES END
""" % (loopaddr, loopdest, siteaddr, sitedest, siteowneraddr, siteownerdest,
       sitebouncesaddr, sitebouncesdest, siterequestaddr, siterequestdest), file=fp)
    # The text file entries get a little extra info
    print('# STANZA START:', listname, file=fp)
    print('# CREATED:', time.ctime(time.time()), file=fp)
    # Now add all the standard alias entries
    for k, v in makealiases(listname):
        fqdnaddr = '%s@%s' % (k, hostname)
        if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN:
            localaddr = '%s@%s' % (k, mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN)
        else:
            localaddr = k
        # Format the text file nicely
        print(fqdnaddr, ((fieldsz - len(k)) * ' '), localaddr, file=fp)
    # Finish the text file stanza
    print('# STANZA END:', listname, file=fp)
    print(file=fp)

# Blech.
def _check_for_virtual_loopaddr(mlist, filename):
    loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
    loopdest = Utils.ParseEmail(loopaddr)[0]
    siteaddr = Utils.get_site_email(mlist.host_name)
    sitedest = Utils.ParseEmail(siteaddr)[0]
    siteowneraddr = Utils.get_site_email(mlist.host_name, extra='owner')
    siteownerdest = Utils.ParseEmail(siteowneraddr)[0]
    sitebouncesaddr = Utils.get_site_email(mlist.host_name, extra='bounces')
    sitebouncesdest = Utils.ParseEmail(sitebouncesaddr)[0]
    siterequestaddr = Utils.get_site_email(mlist.host_name, extra='request')
    siterequestdest = Utils.ParseEmail(siterequestaddr)[0]
    if mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN:
        loopdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        sitedest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        siteownerdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        sitebouncesdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
        siterequestdest += '@' + mm_cfg.VIRTUAL_MAILMAN_LOCAL_DOMAIN
    # If the site list's host_name is a virtual domain, adding the list and
    # owner addresses to the SITE ADDRESSES will duplicate the entries in the
    # stanza for the list.  Postfix doesn't like dups so we try to comment them
    # here, but only for the actual site list domain.
    if (MailList(mm_cfg.MAILMAN_SITE_LIST, lock=False).host_name.lower() ==
            mlist.host_name.lower()):
        siteaddr = '#' + siteaddr
        siteowneraddr = '#' + siteowneraddr
        sitebouncesaddr = '#' + sitebouncesaddr
        siterequestaddr = '#' + siterequestaddr
    infp = open(filename)
    omask = os.umask(0o007)
    try:
        outfp = open(filename + '.tmp', 'w')
    finally:
        os.umask(omask)
    try:
        # Find the start of the loop address block
        while True:
            line = infp.readline()
            if not line:
                break
            outfp.write(line)
            if line.startswith('# LOOP ADDRESSES START'):
                break
        # Now see if our domain has already been written
        while True:
            line = infp.readline()
            if not line:
                break
            if line.startswith('# LOOP ADDRESSES END'):
                # It hasn't
                print >> outfp, '%s\t%s' % (loopaddr, loopdest)
                outfp.write(line)
                break
            elif line.startswith(loopaddr):
                # We just found it
                outfp.write(line)
                break
            else:
                # This isn't our loop address, so spit it out and continue
                outfp.write(line)
        # Now do it all again for the site list address. It must follow the
        # loop addresses.
        while True:
            line = infp.readline()
            if not line:
                break
            outfp.write(line)
            if line.startswith('# SITE ADDRESSES START'):
                break
        # Now see if our domain has already been written
        while True:
            line = infp.readline()
            if not line:
                break
            if line.startswith('# SITE ADDRESSES END'):
                # It hasn't
                print >> outfp, '%s\t%s' % (siteaddr, sitedest)
                print >> outfp, '%s\t%s' % (siteowneraddr, siteownerdest)
                print >> outfp, '%s\t%s' % (sitebouncesaddr, sitebouncesdest)
                print >> outfp, '%s\t%s' % (siterequestaddr, siterequestdest)
                outfp.write(line)
                break
            elif line.startswith(siteaddr) or line.startswith('#' + siteaddr):
                # We just found it
                outfp.write(line)
                break
            else:
                # This isn't our loop address, so spit it out and continue
                outfp.write(line)
        outfp.writelines(infp.readlines())
    finally:
        infp.close()
        outfp.close()
    os.rename(filename + '.tmp', filename)

def _do_create(mlist, textfile, func):
    # Crack open the plain text file
    try:
        fp = open(textfile, 'r+')
    except IOError as e:
        if e.errno != errno.ENOENT: raise
        omask = os.umask(0o007)
        try:
            fp = open(textfile, 'w+')
        finally:
            os.umask(omask)
    try:
        func(mlist, fp)
    finally:
        fp.close()
    # Now double check the virtual plain text file
    if func is _addvirtual:
        _check_for_virtual_loopaddr(mlist, textfile)

def create(mlist, cgi=False, nolock=False, quiet=False):
    # Acquire the global list database lock.  quiet flag is ignored.
    lock = None
    if not nolock:
        lock = makelock()
        lock.lock()
    # Do the aliases file, which need to be done in any case
    try:
        _do_create(mlist, ALIASFILE, _addlist)
        if _isvirtual(mlist):
            _do_create(mlist, VIRTFILE, _addvirtual)
        # bin/genaliases is the only one that calls create with nolock = True.
        # Use that to only update the maps at the end of genaliases.
        if not nolock:
            _update_maps()
    finally:
        if lock:
            lock.unlock(unconditionally=True)

def _do_remove(mlist, textfile, virtualp):
    listname = mlist.internal_name()
    # Now do our best to filter out the proper stanza from the text file.
    # The text file better exist!
    outfp = None
    try:
        infp = open(textfile)
    except IOError as e:
        if e.errno != errno.ENOENT: raise
        # Otherwise, there's no text file to filter so we're done.
        return
    try:
        omask = os.umask(0o007)
        try:
            outfp = open(textfile + '.tmp', 'w')
        finally:
            os.umask(omask)
        filteroutp = False
        start = '# STANZA START: ' + listname
        end = '# STANZA END: ' + listname
        oops = '# STANZA START: '
        while 1:
            line = infp.readline()
            if not line:
                break
            # If we're filtering out a stanza, just look for the end marker and
            # filter out everything in between.  If we're not in the middle of
            # filtering out a stanza, we're just looking for the proper begin
            # marker.
            if filteroutp:
                if line.strip() == end:
                    filteroutp = False
                    # Discard the trailing blank line, but don't worry if
                    # we're at the end of the file.
                    infp.readline()
                elif line.startswith(oops):
                    # Stanza end must be missing - start writing from here.
                    filteroutp = False
                    outfp.write(line)
                # Otherwise, ignore the line
            else:
                if line.strip() == start:
                    # Filter out this stanza
                    filteroutp = True
                else:
                    outfp.write(line)
    # Close up shop, and rotate the files
    finally:
        infp.close()
        outfp.close()
    os.rename(textfile+'.tmp', textfile)

def remove(mlist, cgi=False):
    # Acquire the global list database lock
    lock = makelock()
    lock.lock()
    try:
        _do_remove(mlist, ALIASFILE, False)
        if _isvirtual(mlist):
            _do_remove(mlist, VIRTFILE, True)
        # Regenerate the alias and map files
        _update_maps()
    finally:
        lock.unlock(unconditionally=True)

def checkperms(state):
    for file in ALIASFILE, VIRTFILE:
        if state.VERBOSE:
            print(C_('checking permissions on %(file)s'))
        stat = None
        try:
            stat = os.stat(file)
        except OSError as e:
            if e.errno != errno.ENOENT:
                raise
        if stat and (stat[ST_MODE] & targetmode) != targetmode:
            state.ERRORS += 1
            octmode = oct(stat[ST_MODE])
            print(C_('%(file)s permissions must be 0664 (got %(octmode)s)'),)
            if state.FIX:
                print(C_('(fixing)'))
                os.chmod(file, stat[ST_MODE] | targetmode)
            else:
                print
        # Make sure the corresponding .db files are owned by the Mailman user.
        # We don't need to check the group ownership of the file, since
        # check_perms checks this itself.
        dbfile = file + '.db'
        stat = None
        try:
            stat = os.stat(dbfile)
        except OSError as e:
            if e.errno != errno.ENOENT:
                raise
            continue
        if state.VERBOSE:
            print(C_('checking ownership of %(dbfile)s'))
        user = mm_cfg.MAILMAN_USER
        ownerok = stat[ST_UID] == pwd.getpwnam(user)[2]
        if not ownerok:
            try:
                owner = pwd.getpwuid(stat[ST_UID])[0]
            except KeyError:
                owner = 'uid %d' % stat[ST_UID]
            print(C_(
                '%(dbfile)s owned by %(owner)s (must be owned by %(user)s'),)
            state.ERRORS += 1
            if state.FIX:
                print(C_('(fixing)'))
                uid = pwd.getpwnam(user)[2]
                gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2]
                os.chown(dbfile, uid, gid)
            else:
                print
        if stat and (stat[ST_MODE] & targetmode) != targetmode:
            state.ERRORS += 1
            octmode = oct(stat[ST_MODE])
            print(C_('%(dbfile)s permissions must be 0664 (got %(octmode)s)'),)
            if state.FIX:
                print(C_('(fixing)'))
                os.chmod(dbfile, stat[ST_MODE] | targetmode)
            else:
                print()