import torch
import torch.nn as nn
import torch.autograd

from torch.optim.lr_scheduler import StepLR
from tensorboardX import SummaryWriter

import numpy as np
import task_generator as tg
import os
import models
import a2cAgent
from collections import OrderedDict

writer = SummaryWriter(logdir='scalar')

FEATURE_DIM = 64  # args.feature_dim
RELATION_DIM = 8  # args.relation_dim
CLASS_NUM = 5  # args.class_num
SAMPLE_NUM_PER_CLASS = 5  # args.sample_num_per_class
BATCH_NUM_PER_CLASS = 15  # args.batch_num_per_class
EPISODE = 5000000  # args.episode
TEST_EPISODE = 600  # args.test_episode
LEARNING_RATE = 0.001  # args.learning_rate
HIDDEN_UNIT = 10  # args.hidden_unit
GAMMA = 0.9
ENTROPY_WEIGHT = 1e-2
ENV_LENGTH = 5
INNER_BATCH_RANGE = 3
META_BATCH_RANGE = 3

def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # * Step 1: init data folders
    print("init data folders")

    # * Init character folders for dataset construction
    metatrain_character_folders, metatest_character_folders = tg.mini_imagenet_folders()

    # * Step 2: init neural networks
    print("init neural networks")

    feature_encoder = models.CNNEncoder()
    actor = models.Actor(FEATURE_DIM, RELATION_DIM, CLASS_NUM)
    critic = models.Critic(FEATURE_DIM, RELATION_DIM)

    #feature_encoder = torch.nn.DataParallel(feature_encoder)
    #actor = torch.nn.DataParallel(actor)
    #critic = torch.nn.DataParallel(critic)

    feature_encoder.train()
    actor.train()
    critic.train()

    feature_encoder.apply(models.weights_init)
    actor.apply(models.weights_init)
    critic.apply(models.weights_init)

    feature_encoder.to(device)
    actor.to(device)
    critic.to(device)

    cross_entropy = nn.CrossEntropyLoss()

    feature_encoder_optim = torch.optim.Adam(feature_encoder.parameters(), lr=LEARNING_RATE)
    feature_encoder_scheduler = StepLR(feature_encoder_optim, step_size=10000, gamma=0.5)

    actor_optim = torch.optim.Adam(actor.parameters(), lr=LEARNING_RATE)
    actor_scheduler = StepLR(actor_optim, step_size=10000, gamma=0.5)

    critic_optim = torch.optim.Adam(critic.parameters(), lr=LEARNING_RATE * 10)
    critic_scheduler = StepLR(critic_optim, step_size=10000, gamma=0.5)

    agent = a2cAgent.A2CAgent(actor, critic, GAMMA, ENTROPY_WEIGHT, FEATURE_DIM, RELATION_DIM, CLASS_NUM, device)

    if os.path.exists(str("./models/miniimagenet_feature_encoder_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")):
        feature_encoder.load_state_dict(torch.load(str("./models/miniimagenet_feature_encoder_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")))
        print("load feature encoder success")

    if os.path.exists(str("./models/miniimagenet_actor_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")):
        actor.load_state_dict(torch.load(str("./models/miniimagenet_actor_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")))
        print("load actor network success")

    if os.path.exists(str("./models/miniimagenet_critic_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")):
        critic.load_state_dict(torch.load(str("./models/miniimagenet_critic_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")))
        print("load critic network success")

    # * Step 3: build graph
    print("Training...")

    last_accuracy = 0.0
    mbal_loss_list = []
    mbcl_loss_list = []
    loss_list = []
    number_of_query_image = 15
    for episode in range(EPISODE):
        #print(f"EPISODE : {episode}")
        policy_losses = []
        value_losses = []

        for meta_batch in range(META_BATCH_RANGE):
            meta_env_states_list = []
            meta_env_labels_list = []
            for inner_batch in range(INNER_BATCH_RANGE):
                # * Generate environment
                env_states_list = []
                env_labels_list = []
                task = tg.MiniImagenetTask(metatrain_character_folders, CLASS_NUM, SAMPLE_NUM_PER_CLASS, number_of_query_image)
                sample_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=SAMPLE_NUM_PER_CLASS, split="train", shuffle=False)
                batch_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=5, split="test", shuffle=True)

                samples, sample_labels = next(iter(sample_dataloader))
                samples, sample_labels = samples.to(device), sample_labels.to(device)

                for batches, batch_labels in batch_dataloader:
                    batches, batch_labels = batches.to(device), batch_labels.to(device)

                    inner_sample_features = feature_encoder(samples)
                    inner_sample_features = inner_sample_features.view(CLASS_NUM, SAMPLE_NUM_PER_CLASS, FEATURE_DIM, 19, 19)
                    inner_sample_features = torch.sum(inner_sample_features, 1).squeeze(1)

                    inner_batch_features = feature_encoder(batches)
                    inner_sample_feature_ext = inner_sample_features.unsqueeze(0).repeat(5 * CLASS_NUM, 1, 1, 1, 1)
                    inner_batch_features_ext = inner_batch_features.unsqueeze(0).repeat(CLASS_NUM, 1, 1, 1, 1)
                    inner_batch_features_ext = torch.transpose(inner_batch_features_ext, 0, 1)

                    inner_relation_pairs = torch.cat((inner_sample_feature_ext, inner_batch_features_ext), 2).view(-1, FEATURE_DIM * 2, 19, 19)
                    env_states_list.append(inner_relation_pairs)
                    env_labels_list.append(batch_labels)

                inner_env = a2cAgent.env(env_states_list, env_labels_list)
                agent.train(inner_env, inner_update=True)

            task = tg.MiniImagenetTask(metatrain_character_folders, CLASS_NUM, SAMPLE_NUM_PER_CLASS, number_of_query_image)
            sample_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=SAMPLE_NUM_PER_CLASS, split="train", shuffle=False)
            batch_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=number_of_query_image, split="test", shuffle=True)
            # * num_per_class : number of query images

            # * sample datas
            samples, sample_labels = next(iter(sample_dataloader))
            samples, sample_labels = samples.to(device), sample_labels.to(device)
            # * Generate env for meta update
            batches, batch_labels = next(iter(batch_dataloader))
            # * init dataset
            # * sample_dataloader is to obtain previous samples for compare
            # * batch_dataloader is to batch samples for training
            batches, batch_labels = batches.to(device), batch_labels.to(device)

            # * calculates features
            #feature_encoder.weight = feature_fast_weights

            sample_features = feature_encoder(samples)
            sample_features = sample_features.view(CLASS_NUM, SAMPLE_NUM_PER_CLASS, FEATURE_DIM, 19, 19)
            sample_features = torch.sum(sample_features, 1).squeeze(1)
            batch_features = feature_encoder(batches)

            # * calculate relations
            # * each batch sample link to every samples to calculate relations
            # * to form a 100 * 128 matrix for relation network
            sample_features_ext = sample_features.unsqueeze(0).repeat(number_of_query_image * CLASS_NUM, 1, 1, 1, 1)
            batch_features_ext = batch_features.unsqueeze(0).repeat(CLASS_NUM, 1, 1, 1, 1)
            batch_features_ext = torch.transpose(batch_features_ext, 0, 1)
            relation_pairs = torch.cat((sample_features_ext, batch_features_ext), 2).view(-1, FEATURE_DIM * 2, 19, 19)

            meta_env_states_list.append(relation_pairs)
            meta_env_labels_list.append(batch_labels)

            meta_env = a2cAgent.env(meta_env_states_list, meta_env_labels_list)
            agent.train(meta_env, policy_loss_list=policy_losses, value_loss_list=value_losses)

        feature_encoder_optim.zero_grad()
        actor_optim.zero_grad()
        critic_optim.zero_grad()

        torch.nn.utils.clip_grad_norm_(feature_encoder.parameters(), 0.5)
        torch.nn.utils.clip_grad_norm_(actor.parameters(), 0.5)
        torch.nn.utils.clip_grad_norm_(critic.parameters(), 0.5)

        meta_batch_actor_loss = torch.stack(policy_losses).mean()
        meta_batch_critic_loss = torch.stack(value_losses).mean()

        meta_batch_actor_loss.backward(retain_graph=True)
        meta_batch_critic_loss.backward()

        feature_encoder_optim.step()
        actor_optim.step()
        critic_optim.step()

        feature_encoder_scheduler.step()
        actor_scheduler.step()
        critic_scheduler.step()

        if (episode + 1) % 100 == 0:
            mbal = meta_batch_actor_loss.cpu().detach().numpy()
            mbcl = meta_batch_critic_loss.cpu().detach().numpy()
            print(f"episode : {episode+1}, meta_batch_actor_loss : {mbal:.4f}, meta_batch_critic_loss : {mbcl:.4f}")

            mbal_loss_list.append(mbal)
            mbcl_loss_list.append(mbcl)
            loss_list.append(mbal + mbcl)

        if (episode + 1) % 500 == 0:
            print("Testing...")
            total_reward = 0

            total_num_of_test_samples = 0
            for i in range(TEST_EPISODE):
                # * Generate env
                env_states_list = []
                env_labels_list = []

                number_of_query_image = 10
                task = tg.MiniImagenetTask(metatest_character_folders, CLASS_NUM, SAMPLE_NUM_PER_CLASS, number_of_query_image)
                sample_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=SAMPLE_NUM_PER_CLASS, split="train", shuffle=False)
                test_dataloader = tg.get_mini_imagenet_data_loader(task, num_per_class=number_of_query_image, split="test", shuffle=True)
                sample_images, sample_labels = next(iter(sample_dataloader))
                sample_images, sample_labels = sample_images.to(device), sample_labels.to(device)

                test_images, test_labels = next(iter(test_dataloader))
                total_num_of_test_samples += len(test_labels)
                test_images, test_labels = test_images.to(device), test_labels.to(device)

                # * calculate features
                sample_features = feature_encoder(sample_images)
                sample_features = sample_features.view(CLASS_NUM, SAMPLE_NUM_PER_CLASS, FEATURE_DIM, 19, 19)
                sample_features = torch.sum(sample_features, 1).squeeze(1)
                test_features = feature_encoder(test_images)

                # * calculate relations
                # * each batch sample link to every samples to calculate relations
                # * to form a 100x128 matrix for relation network

                sample_features_ext = sample_features.unsqueeze(0).repeat(number_of_query_image * CLASS_NUM, 1, 1, 1, 1)
                test_features_ext = test_features.unsqueeze(0).repeat(CLASS_NUM, 1, 1, 1, 1)
                test_features_ext = torch.transpose(test_features_ext, 0, 1)

                relation_pairs = torch.cat((sample_features_ext, test_features_ext), 2).view(-1, FEATURE_DIM * 2, 19, 19)
                env_states_list.append(relation_pairs)
                env_labels_list.append(test_labels)

                test_env = a2cAgent.env(env_states_list, env_labels_list)
                rewards = agent.test(test_env)
                total_reward += rewards

            test_accuracy = total_reward / (1.0 * total_num_of_test_samples)

            mean_loss = np.mean(loss_list)
            mean_actor_loss = np.mean(mbal_loss_list)
            mean_critic_loss = np.mean(mbcl_loss_list)

            print(f'mean loss : {mean_loss}')
            print("test accuracy : ", test_accuracy)

            writer.add_scalar('1.loss', mean_loss, episode + 1)
            writer.add_scalar('2.mean_actor_loss', mean_actor_loss, episode + 1)
            writer.add_scalar('3.mean_critic_loss', mean_critic_loss, episode + 1)
            writer.add_scalar('4.test accuracy', test_accuracy, episode + 1)

            loss_list = []
            mbal_loss_list = []
            mbcl_loss_list = []

            if test_accuracy > last_accuracy:
                # save networks
                torch.save(
                    feature_encoder.state_dict(),
                    str("./models/miniimagenet_feature_encoder_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")
                )
                torch.save(
                    actor.state_dict(),
                    str("./models/miniimagenet_actor_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")
                )

                torch.save(
                    critic.state_dict(),
                    str("./models/miniimagenet_critic_network_" + str(CLASS_NUM) + "way_" + str(SAMPLE_NUM_PER_CLASS) + "shot.pkl")
                )
                print("save networks for episode:", episode)
                last_accuracy = test_accuracy

if __name__ == "__main__":
    main()


"""Training Noise2Noise model
https://github.com/NVlabs/noise2noise

Train once, test in varying imaging configurations (types) & noise levels.
Dataset:
    training set: mixed noise levels, microscopies and cells
    test set: mixed
"""

import torch
import torch.nn.functional as F
from torchvision import transforms
from models.unet import UnetN2N, UnetN2Nv2
from utils.metrics import cal_psnr
from utils.data_loader import (load_denoising_n2n_train,
                               load_denoising_test_mix, fluore_to_tensor)
from utils.practices import OneCycleScheduler, adjust_learning_rate, find_lr
from utils.misc import mkdirs, module_size
from utils.plot import save_samples, save_stats
import numpy as np
import argparse
import json
import random
import time
import sys
from pprint import pprint
import matplotlib.pyplot as plt
plt.switch_backend('agg')

from utils_SFM import random_drop

class Parser(argparse.ArgumentParser):
    def __init__(self):
        super(Parser, self).__init__(description='Training N2N')
        self.add_argument('--exp-name', type=str, default='n2n', help='experiment name')
        self.add_argument('--exp-dir', type=str, default="./experiments", help='directory to save experiments')
        self.add_argument('--post', action='store_true', default=False, help='post proc mode')
        self.add_argument('--debug', action='store_true', default=False, help='verbose stdout')
        self.add_argument('--net', type=str, default='unet', choices=['unet', 'unetv2'])
        # data
        self.add_argument('--data-root', type=str, default="./dataset", help='directory to dataset root')
        self.add_argument('--imsize', type=int, default=256)
        self.add_argument('--in-channels', type=int, default=1)
        self.add_argument('--out-channels', type=int, default=1)
        self.add_argument('--transform', type=str, default='four_crop', choices=['four_crop', 'center_crop'])
        self.add_argument('--noise-levels-train', type=list, default=[1, 2, 4, 8, 16])
        self.add_argument('--noise-levels-test', type=list, default=[1])
        self.add_argument('--test-group', type=int, default=19)
        self.add_argument('--captures', type=int, default=50, help='how many captures in each group to load')
        # training
        self.add_argument('--epochs', type=int, default=400, help='number of iterations to train')
        self.add_argument('--batch-size', type=int, default=4, help='input batch size for training')
        self.add_argument('--lr', type=float, default=1e-4, help='learning rate')
        self.add_argument('--wd', type=float, default=0., help="weight decay")
        self.add_argument('--test-batch-size', type=int, default=2, help='input batch size for testing')
        self.add_argument('--seed', type=int, default=1, help='manual seed used in Tensor')
        self.add_argument('--cuda', type=int, default=0, help='cuda number')
        # logging
        self.add_argument('--ckpt-freq', type=int, default=10, help='how many epochs to wait before saving model')
        self.add_argument('--print-freq', type=int, default=100, help='how many minibatches to wait before printing training status')
        self.add_argument('--log-freq', type=int, default=1, help='how many epochs to wait before logging training status')
        self.add_argument('--plot-epochs', type=int, default=50, help='how many epochs to wait before plotting test output')
        self.add_argument('--cmap', type=str, default='inferno', help='attach notes to the run dir')
        # SFM
        self.add_argument('--DCT_DOR', type=float, default=0, help='DCT Dropout Rate, if 0 no DCT dropout')

    def parse(self):
        args = self.parse_args()
        date = '{}'.format(time.strftime('%b_%d'))
        args.run_dir = args.exp_dir + '/' + args.exp_name + '/' + date \
            + f'/{args.net}_noise_train{args.noise_levels_train}_'\
            f'test{args.noise_levels_test}_{args.transform}_'\
            f'epochs{args.epochs}_bs{args.batch_size}_lr{args.lr}'\
            f'SFM{args.DCT_DOR}'
        args.ckpt_dir = args.run_dir + '/checkpoints'

        if not args.post:
            mkdirs([args.run_dir, args.ckpt_dir])

        # seed
        if args.seed is None:
            args.seed = random.randint(1, 10000)
        print("Random Seed: ", args.seed)
        random.seed(args.seed)
        torch.manual_seed(args.seed)
        torch.backends.cudnn.benchmark=True

        print('Arguments:')
        pprint(vars(args))

        if not args.post:
            with open(args.run_dir + "/args.txt", 'w') as args_file:
                json.dump(vars(args), args_file, indent=4)

        return args

args = Parser().parse()
device = torch.device(f'cuda:{args.cuda}' if torch.cuda.is_available() else 'cpu')

args.train_dir = args.run_dir + "/training"
args.pred_dir = args.train_dir + "/predictions"
mkdirs([args.train_dir, args.pred_dir])
if args.net == 'unet':
    model = UnetN2N(args.in_channels, args.out_channels).to(device)
elif args.net == 'unetv2':
    model = UnetN2Nv2(args.in_channels, args.out_channels).to(device)

if args.debug:
    print(model)
    print(model.model_size)

if args.transform == 'four_crop':
    # wide field images may have complete noise in center-crop case
    transform = transforms.Compose([
        transforms.FiveCrop(args.imsize),
        transforms.Lambda(lambda crops: torch.stack([
            fluore_to_tensor(crop) for crop in crops[:4]])),
        transforms.Lambda(lambda x: x.float().div(255).sub(0.5))
        ])
elif args.transform == 'center_crop':
    # default transform
    transform = None

train_loader = load_denoising_n2n_train(args.data_root,
    batch_size=args.batch_size, noise_levels=args.noise_levels_train,
    types=None, transform=transform, target_transform=transform,
    patch_size=args.imsize, test_fov=args.test_group)

test_loader = load_denoising_test_mix(args.data_root,
    batch_size=args.test_batch_size, noise_levels=args.noise_levels_test,
    transform=transform, patch_size=args.imsize)

optimizer = torch.optim.Adam(model.parameters(), lr=args.lr,
    weight_decay=args.wd, betas=[0.9, 0.99])
# scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.2, patience=10)
scheduler = OneCycleScheduler(lr_max=args.lr, div_factor=10, pct_start=0.3)

multiplier = 4 if args.transform == 'four_crop' else 1
n_train_samples = len(train_loader.dataset) * multiplier
n_test_samples = len(test_loader.dataset) * multiplier
pixels_per_sample = train_loader.dataset[0][0].numel()
n_train_pixels = n_train_samples * pixels_per_sample
n_test_pixels = n_test_samples * pixels_per_sample

np.random.seed(113)
fixed_idx = np.random.permutation(len(test_loader.dataset))[:8]
print(f'fixed test index: {fixed_idx}')

fixed_test_noisy = torch.stack([(test_loader.dataset[i][0]) for i in fixed_idx])
fixed_test_clean = torch.stack([(test_loader.dataset[i][1]) for i in fixed_idx])
if args.transform == 'four_crop':
    fixed_test_noisy = fixed_test_noisy[:, -1]
    fixed_test_clean = fixed_test_clean[:, -1]
print(f'fixed test noisy shape: {fixed_test_noisy.shape}')
fixed_test_noisy = fixed_test_noisy.to(device)

logger = {}
logger['psnr_train'] = []
logger['rmse_train'] = []
logger['psnr_test'] = []
logger['rmse_test'] = []

total_steps = args.epochs * len(train_loader)
print('Start training........................................................')
torch.manual_seed(0)
try:
    tic = time.time()
    iters = 0
    for epoch in range(1, args.epochs + 1):
        model.train()
        # if epoch == 1:
        #     print('start finding lr...')
        #     log_lrs, losses = find_lr(model, train_loader, optimizer,
        #         F.mse_loss, device=device)
        #     plt.plot(log_lrs[10:-5],losses[10:-5])
        #     plt.savefig('find_lr_n2n.png')
        #     plt.close()
        #     sys.exit(0)
        psnr, mse = 0., 0.
        for batch_idx, (noisy_input, noisy_target, clean) in enumerate(train_loader):
            iters += 1
            noisy_input, noisy_target, clean = noisy_input.to(device), \
                noisy_target.to(device), clean.to(device)

            if args.transform == 'four_crop':
                # fuse batch and four crop
                noisy_input = noisy_input.view(-1, *noisy_input.shape[2:])
                noisy_target = noisy_target.view(-1, *noisy_target.shape[2:])
                clean = clean.view(-1, *clean.shape[2:])

            # DCT SFM
            if args.DCT_DOR > 0:
                noisy_input_SFM = np.zeros(noisy_input.size(),dtype='float32')
                dct_bool = np.random.choice([1, 0], size=(noisy_input.size()[0],), p=[args.DCT_DOR, 1-args.DCT_DOR])
                for img_idx in range(noisy_input.size()[0]):
                    if dct_bool[img_idx] == 1:

                        img_numpy, mask = random_drop(noisy_input[img_idx,:,:,:].cpu().data.numpy(), mode=2, SFM_center_radius_perc=0.85, SFM_center_sigma_perc=0.15)
                        noisy_input_SFM[img_idx,0,:,:] = img_numpy
                noisy_input = torch.from_numpy(noisy_input_SFM).cuda()

            model.zero_grad()
            denoised = model(noisy_input)
            loss = F.mse_loss(denoised, noisy_target, reduction='sum')
            loss.backward()

            step = epoch * len(train_loader) + batch_idx + 1
            pct = step / total_steps
            lr = scheduler.step(pct)
            adjust_learning_rate(optimizer, lr)

            optimizer.step()

            mse += loss.item()
            with torch.no_grad():
                psnr += cal_psnr(clean, denoised.detach()).sum().item()
            if iters % args.print_freq == 0:
                print(f'[{batch_idx+1}|{len(train_loader)}]'\
                    f'[{epoch}|{args.epochs}] training PSNR: '\
                    f'{(psnr / (batch_idx+1) / args.batch_size / multiplier):.6f}')
        print(f'Epoch {epoch}, lr {lr}')

        psnr = psnr / n_train_samples
        rmse = np.sqrt(mse / n_train_pixels)
        scheduler.step(psnr)

        if epoch % args.log_freq == 0:
            logger['psnr_train'].append(psnr)
            logger['rmse_train'].append(rmse)
        print("Epoch {} training PSNR: {:.6f}, RMSE: {:.6f}".format(epoch, psnr, rmse))

        # save model
        if epoch % args.ckpt_freq == 0:
            torch.save(model.state_dict(), args.ckpt_dir + "/model_epoch{}.pth".format(epoch))

        # test ------------------------------
        if epoch % 5 == 0:
            with torch.no_grad():
                model.eval()
                psnr, mse = 0., 0.
                for batch_idx, (noisy, clean) in enumerate(test_loader):
                    noisy, clean = noisy.to(device), clean.to(device)
                    if args.transform == 'four_crop':
                        # fuse batch and four crop
                        noisy = noisy.view(-1, *noisy.shape[2:])
                        clean = clean.view(-1, *clean.shape[2:])
                    denoised = model(noisy)
                    loss = F.mse_loss(denoised, clean, reduction='sum')
                    mse += loss.item()
                    psnr += cal_psnr(clean, denoised).sum().item()

                psnr = psnr / n_test_samples
                rmse = np.sqrt(mse / n_test_pixels)

                if epoch % args.plot_epochs == 0:
                    print('Epoch {}: plot test denoising [input, denoised, clean, denoised - clean]'.format(epoch))
                    samples = torch.cat((noisy[:4], denoised[:4], clean[:4], denoised[:4] - clean[:4]))
                    save_samples(args.pred_dir, samples, epoch, 'test', epoch=True, cmap=args.cmap)
                    # fixed test
                    fixed_denoised = model(fixed_test_noisy)
                    samples = torch.cat((fixed_test_noisy[:4].cpu(),
                        fixed_denoised[:4].cpu(), fixed_test_clean[:4],
                        fixed_denoised[:4].cpu() - fixed_test_clean[:4]))
                    save_samples(args.pred_dir, samples, epoch, 'fixed_test1', epoch=True, cmap=args.cmap)
                    samples = torch.cat((fixed_test_noisy[4:8].cpu(),
                        fixed_denoised[4:8].cpu(), fixed_test_clean[4:8],
                        fixed_denoised[4:8].cpu() - fixed_test_clean[4:8]))
                    save_samples(args.pred_dir, samples, epoch, 'fixed_test2', epoch=True, cmap=args.cmap)

                if epoch % args.log_freq == 0:
                    logger['psnr_test'].append(psnr)
                    logger['rmse_test'].append(rmse)
                print("Epoch {}: test PSNR: {:.6f}, RMSE: {:.6f}".format(epoch, psnr, rmse))

    tic2 = time.time()
    print("Finished training {} epochs using {} seconds"
        .format(args.epochs, tic2 - tic))

    x_axis = np.arange(args.log_freq, args.epochs + args.log_freq, args.log_freq)
    # plot the rmse, r2-score curve and save them in txt
#     save_stats(args.train_dir, logger, x_axis, 'psnr_train', 'psnr_test',
#         'rmse_train', 'rmse_test')

    args.training_time = tic2 - tic
    args.n_params, args.n_layers = module_size(model)
    with open(args.run_dir + "/args.txt", 'w') as args_file:
        json.dump(vars(args), args_file, indent=4)

except KeyboardInterrupt:
    print('Keyboard Interrupt captured...Saving models & training logs')
    tic2 = time.time()
    torch.save(model.state_dict(), args.ckpt_dir + "/model_epoch{}.pth".format(epoch))
    x_axis = np.arange(args.log_freq, args.epochs + args.log_freq, args.log_freq)
    # plot the rmse, r2-score curve and save them in txt
    save_stats(args.train_dir, logger, x_axis, 'psnr_train', 'psnr_test',
        'rmse_train', 'rmse_test')

    args.training_time = tic2 - tic
    args.n_params, args.n_layers = module_size(model)
    with open(args.run_dir + "/args.txt", 'w') as args_file:
        json.dump(vars(args), args_file, indent=4)


import filecmp
from tqdm import tqdm
import numpy as np
import shutil
from os import path, scandir, remove, cpu_count, stat
from queue import Queue
from threading import Thread
from tkinter import Tk, Button, Frame

class FileOperations:

    def __init__(self, master):
        # Number of threads to execute
        self.NO_OF_THREADS = cpu_count()
        self.queue_objects = [Queue() for i in range(self.NO_OF_THREADS)]
        self.dataset_folder = r"/home/webwerks/Desktop/test/source/"
        self.destination_folder = r"/home/webwerks/Desktop/test/destination/"
        self.src_list = []
        self.dst_list = []
        self.read_folders(self.dataset_folder, self.destination_folder)
        self.read_destination_files()
        self.source_check()
        frame = Frame(master)
        frame.pack()
        self.Transfer = Button(master, text="Copy", command=lambda: self.run())
        self.Transfer.pack(pady=20)

    @staticmethod
    def check_file(image_path, dest_path):
        """
        Take source destination and copy destination check if file exists

        Args:
                image_path(str): Source file path
                dest_path(str): Destination path
        Returns:
                0: File doesn't exist or file exist but content is different
                1: File exist
        """
        if path.exists(dest_path):
            dest_size = stat(dest_path).st_size
            # take the size of source file
            src_size = stat(image_path).st_size
            if dest_size == src_size:
                # same files(including content)
                print("same files(including content)")
                return 1
            else:
                # same file name, different content
                print("same file name, different content")
                return 0

    # File copy code
    def copy_data(self, q):
        """
        Take source destination from queue and copy to the given destination
        Args:
                q(queue): Single queue containing source path of files
        Returns:
                None
        """
        pbar = tqdm(total=q.qsize(), position=0, leave=True)
        while not q.empty():
            image_path, dest_path = q.get()
            if self.check_file(image_path, dest_path):
                pbar.update(1)
                continue
            else:
                # shutil.copy always overights the file if exists at dest
                shutil.copy(image_path, dest_path)
                pbar.update(1)
        pbar.close()

    #  Read all folders recursively
    def read_folders(self, data_dir, destination_folder):
        """
        Read folders recursively and put files into queues
        Args:
                data_dir(str): Source path
                destination_folder(str): Destination path
        Returns:
                None
        """
        folder_elements = scandir(data_dir)
        counter = 0
        # reading all sub-folders of the dataset & tqdm is used for creating a progress bar
        for element in folder_elements:
            element_path = element.path

            destination_path = path.join(destination_folder, element.name)

            if element.is_file() and element_path[-4:] in ['.jpg', '.png', '.bmp']:
                self.queue_objects[counter % self.NO_OF_THREADS].put(
                    (element_path, destination_path))
                counter += 1
                self.src_list.append(element.name)

            elif element.is_dir():
                self.read_folders(element_path, destination_path)

    #  reading the destination files
    def read_destination_files(self):
        folder_elements = scandir(self.destination_folder)
        for element in folder_elements:
            self.dst_list.append(element.name)

    #  checking if source file is deleted if any
    def source_check(self):
        diff_list = [
            deleted_file for deleted_file in self.dst_list if deleted_file not in self.src_list]
        return diff_list

    #  Reading the full path and deleting the file present in the path
    @staticmethod
    def delete_from_destination(path_name):
        file_path = path.join(path_name)
        remove(file_path)

    #  if file not present in Source deleting it in destination too(Syncing)
    def sync_source_destination(self):
        folder_elements = scandir(self.destination_folder)
        for element in folder_elements:
            if element.name not in self.src_list:
                self.delete_from_destination(element.path)

    # Start n separate threads
    def run(self):
        for obj in self.queue_objects:
            Thread(target=self.copy_data, args=(obj,)).start()
        self.sync_source_destination()

if __name__ == '__main__':
    root = Tk()
    root.title('Copying the Data')
    root.geometry("400x200")
    cls_obj = FileOperations(root)
    root.mainloop()


import tkinter
from functools import partial

EMPTY = 0
COMP = -1
USER = 1

class Field(tkinter.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.buttons = [[None for _ in range(10)] for _ in range(10)]
        self.fill_content()

    def fill_content(self):
        for row in range(10):
            tkinter.Label(self, text=row + 1, width=3).grid(row=0, column=row + 1)
            tkinter.Label(self, text=row + 1, width=3).grid(row=row + 1, column=0)
            for col in range(10):
                handler = partial(self.make_turn, row, col)
                self.buttons[row][col] = tkinter.Button(self, command=handler)
                self.buttons[row][col].grid(row=row + 1, column=col + 1)

    def make_turn(self, row, column):
        button = self.buttons[row][column]
        button.destroy()

        print(f'{row} {column}')

# class Field(tkinter.Frame):
#     def __init__(self, *args, **kwargs):
#         super().__init__(*args, **kwargs)
#         self.winner = None
#         self.flags = [EMPTY for _ in range(9)]
#         self.buttons = [
#             tkinter.Button(self, text='', command=self.turn(i))
#             for i in range(9)
#         ]
#         for i, btn in enumerate(self.buttons):
#             btn.grid(row=int(i / 3) + 1, column=i % 3)
#
#     def get_empty(self, indexes):
#         for i in indexes:
#             if self.flags[i] == EMPTY:
#                 return i
#
#     def find_closest(self, user):
#         for i in range(3):
#             if sum(self.flags[i * 3: i * 3 + 3]) == 2 * user:
#                 return self.get_empty(range(i * 3, i * 3 + 3))
#
#             if sum(self.flags[i::3]) == 2 * user:
#                 return self.get_empty(range(i, len(self.flags), 3))
#
#         if sum(self.flags[::4]) == 2 * user:
#             return self.get_empty(range(0, len(self.flags), 3))
#
#         if sum(self.flags[2:7:2]) == 2 * user:
#             return self.get_empty(range(2, 7, 2))
#
#     def ai_turn(self):
#         win_id = self.find_closest(COMP)
#         if win_id:
#             return win_id
#         block_id = self.find_closest(USER)
#         if block_id:
#             return block_id
#
#         random_ids = [i for i, x in enumerate(self.flags) if x == EMPTY]
#         random.shuffle(random_ids)
#         return random_ids[0]
#
#     def make_turn(self, cell, player):
#         self.buttons[cell].destroy()
#         lbl = tkinter.Label(self, text='X' if player == USER else 'O', padx=7, pady=6)
#         lbl.grid(row=int(cell / 3) + 1, column=cell % 3)
#         self.flags[cell] = player
#         self.winner = self.get_winner()
#
#     def turn(self, user_cell):
#         def f():
#             if self.winner is not None:
#                 return
#             self.make_turn(user_cell, USER)
#             if self.winner is not None:
#                 return self.end_game()
#
#             comp_cell = self.ai_turn()
#             self.make_turn(comp_cell, COMP)
#
#             if self.winner is not None:
#                 return self.end_game()
#
#         return f
#
#     def end_game(self):
#         if self.winner == USER:
#             winner_text = 'You are win'
#         elif self.winner == COMP:
#             winner_text = 'You are lose'
#         else:
#             winner_text = 'You are draw'
#         lbl = tkinter.Label(self, text=winner_text)
#         lbl.grid(row=0, column=0, columnspan=3)
#
#     def get_winner(self):
#         sums = [
#             *(sum(self.flags[i * 3: i * 3 + 3]) for i in range(3)),
#             *(sum(self.flags[i::3]) for i in range(3)),
#             sum(self.flags[::4]),
#             sum(self.flags[2:7:2])
#         ]
#
#         filtered_sums = [
#             USER if s == USER * 3 else COMP
#             for s in sums
#             if s / 3 in (USER, COMP)
#         ]
#
#         if filtered_sums:
#             return filtered_sums[0]
#
#         if all(map(lambda x: x != EMPTY, self.flags)):
#             return EMPTY

def configure(frame):
    icon = tkinter.PhotoImage(height=16, width=16)
    icon.blank()
    frame.iconphoto(True, icon)
    frame.title('')
    frame.resizable(height=False, width=False)

if __name__ == '__main__':
    root = tkinter.Tk()
    configure(root)
    Field(root).grid(row=0, column=0)
    Field(root).grid(row=0, column=1)
    root.mainloop()


import pygame

class MovingObjects(object):

    def __init__(self, screen, camera):
        self.screen = screen
        self.camera = camera

        self.image = pygame.image.load('aestroid_brown.png')
        self.asteroid_2 = pygame.image.load('aestroid_gray.png')
        self.screen_rect = self.screen.get_rect()

        self.rect = self.image.get_rect()
        self.rect_2 = self.asteroid_2.get_rect()

        self.rect.centery = self.screen_rect.centery
        self.rect.x = 2000 # asteroid 1
        self.rect_2.centery = self.screen_rect.centery + 100
        self.rect_2.x = 4000 # asteroid 2

        self.image = pygame.transform.scale(self.image, (80,80))
        self.asteroid_2 = pygame.transform.scale(self.asteroid_2, (80,80))

    def update(self):

        self.move_asteroids()
        self.blitme()

    def move_asteroids(self):
        self.rect.x -= 1
        self.rect_2.x -= 1
        if self.rect.x < -1000:
            self.rect.x = 4000
        if self.rect_2.x < -1000:
            self.rect_2.x = 4000

    def blitme(self):
        self.position1 = self.rect.x - self.camera.camera_position
        self.position2 = self.rect_2.x - self.camera.camera_position # only blit when on screen
        if (self.position1 > -500 and self.position1 < 1200):
            self.screen.blit(self.image, (self.rect.x - self.camera.camera_position, self.rect.y ))
        if (self.position2 > -500 and self.position2 < 1200):
            self.screen.blit(self.asteroid_2, (self.rect_2.x - self.camera.camera_position, self.rect_2.y))


from setuptools import setup, find_packages
import sys, os

version = '0.1.1'

setup(name='z3c.indexing.dispatch',
      version=version,
      description="Transaction-safe indexing dispatcher.",
      long_description=open('README.txt').read(),
      classifiers=[
        "Framework :: Plone",
        "Framework :: Zope2",
        "Framework :: Zope3",
        "Programming Language :: Python",
        "Topic :: Software Development :: Libraries :: Python Modules",
        ],
      keywords='',
      author='Zope Corporation and Contributors',
      author_email='zope3-dev@zope.org',
      url='',
      license='ZPL',
      packages=find_packages('src'),
      package_dir={'': 'src'},
      namespace_packages=['z3c', 'z3c.indexing'],
      include_package_data=True,
      zip_safe=False,
      install_requires=[
          'setuptools',
          'zope.interface',
          'zope.component',
          'zope.testing',
          'zope.app.publication',
          'transaction',
          # -*- Extra requirements: -*-
      ],
      entry_points="""
      # -*- Entry points: -*-
      """,
      )


from flask import render_template, request
from app import app
import time

datain = None
dataout = None

@app.route('/')
@app.route('/index')
def index():
    global datain
    return render_template('index.html', datain=datain, title='cashbox')

@app.route('/longpolling', methods=['POST'])
def longpolling():
    global datain
    global dataout

    datain = request.get_json()
    while dataout is None:
        time.sleep(0.1)
    datain = ""
    dataoutreal = dataout
    dataout = None

    return dataoutreal

@app.route('/check', methods=['GET'])
def check():
    global datain
    while datain is None:
        time.sleep(0.1)
    senddata = datain
    datain = None
    return senddata

@app.route('/sendcashbox', methods=['POST'])
def oauth():
    global dataout
    dataout = request.get_data()
    return ""


# TODO: drop to PDB option
# TODO: iteration count debug print seems one higher?

# *** Not prioritized for v0 ***
# TODO: increase test coverage: TypeVar('T', int, str) vs bounded type vars
# TODO: consider raises conditions (guaranteed to raise, guaranteed to not raise?)
# TODO: precondition strengthening ban (Subclass constraint rule)
# TODO: double-check counterexamples
# TODO: mutating symbolic Callables?
# TODO: contracts on the contracts of function and object inputs/outputs?

from dataclasses import dataclass, replace
import collections
from contextlib import ExitStack
import copy
import enum
import inspect
from inspect import BoundArguments
from inspect import Signature
import itertools
import functools
import linecache
import os.path
import sys
import time
import traceback
import types
from typing import *
import typing

import typing_inspect  # type: ignore
import z3  # type: ignore

from crosshair import dynamic_typing

from crosshair.codeconfig import collect_options
from crosshair.condition_parser import condition_parser
from crosshair.condition_parser import get_current_parser
from crosshair.condition_parser import Conditions
from crosshair.condition_parser import ConditionExpr
from crosshair.condition_parser import ConditionExprType
from crosshair.condition_parser import UNABLE_TO_REPR

from crosshair.enforce import EnforcedConditions
from crosshair.enforce import NoEnforce
from crosshair.enforce import WithEnforcement
from crosshair.enforce import PreconditionFailed
from crosshair.enforce import PostconditionFailed
from crosshair.fnutil import resolve_signature
from crosshair.options import AnalysisOptions
from crosshair.options import AnalysisOptionSet
from crosshair.options import DEFAULT_OPTIONS
from crosshair.statespace import context_statespace
from crosshair.statespace import optional_context_statespace
from crosshair.statespace import prefer_true
from crosshair.statespace import AnalysisMessage
from crosshair.statespace import CallAnalysis
from crosshair.statespace import MessageType
from crosshair.statespace import SinglePathNode
from crosshair.statespace import SimpleStateSpace
from crosshair.statespace import StateSpace
from crosshair.statespace import StateSpaceContext
from crosshair.statespace import VerificationStatus
from crosshair.fnutil import FunctionInfo
from crosshair.tracers import COMPOSITE_TRACER
from crosshair.tracers import NoTracing
from crosshair.tracers import PatchingModule
from crosshair.tracers import ResumedTracing
from crosshair.tracers import TracingModule
from crosshair.tracers import TracingOnly
from crosshair.tracers import is_tracing
from crosshair.type_repo import get_subclass_map
from crosshair.util import debug
from crosshair.util import frame_summary_for_fn
from crosshair.util import name_of_type
from crosshair.util import samefile
from crosshair.util import smtlib_typename
from crosshair.util import sourcelines
from crosshair.util import test_stack
from crosshair.util import AttributeHolder
from crosshair.util import CrosshairInternal
from crosshair.util import CrosshairUnsupported
from crosshair.util import DynamicScopeVar
from crosshair.util import IgnoreAttempt
from crosshair.util import UnexploredPath

_MISSING = object()

_OPCODE_PATCHES: List[TracingModule] = []

_PATCH_REGISTRATIONS: Dict[Callable, Callable] = {}

class Patched(TracingModule):
    def __enter__(self):
        ptchs = {}
        for idwrapper, callable in _PATCH_REGISTRATIONS.items():
            ptchs[idwrapper] = callable
        COMPOSITE_TRACER.push_module(PatchingModule(ptchs))
        push_count = 1
        if len(_OPCODE_PATCHES) == 0:
            raise CrosshairInternal("Opcode patches haven't been loaded yet.")
        for module in _OPCODE_PATCHES:
            COMPOSITE_TRACER.push_module(module)
            push_count += 1
        self.push_count = push_count
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for _ in range(self.push_count):
            COMPOSITE_TRACER.pop_config()
        return False

class _StandaloneStatespace(ExitStack):
    def __enter__(self):
        # We explicitly don't set up contexts to enforce conditions - that's because
        # conditions involve a choice, and standalone_statespace is for testing that
        # does not require making any choices.
        super().__enter__()
        space = SimpleStateSpace()
        self.enter_context(condition_parser(DEFAULT_OPTIONS.analysis_kind))
        self.enter_context(Patched())
        self.enter_context(StateSpaceContext(space))
        self.enter_context(COMPOSITE_TRACER)
        COMPOSITE_TRACER.trace_caller()
        return space

standalone_statespace = _StandaloneStatespace()

class ExceptionFilter:
    analysis: CallAnalysis
    ignore: bool = False
    ignore_with_confirmation: bool = False
    user_exc: Optional[Tuple[Exception, traceback.StackSummary]] = None
    expected_exceptions: Tuple[Type[BaseException], ...]

    def __init__(
        self, expected_exceptions: FrozenSet[Type[BaseException]] = frozenset()
    ):
        self.expected_exceptions = (NotImplementedError,) + tuple(expected_exceptions)

    def has_user_exception(self) -> bool:
        return self.user_exc is not None

    def __enter__(self) -> "ExceptionFilter":
        if not is_tracing():
            raise CrosshairInternal("must be tracing during exception filter")
        return self

    def __exit__(self, exc_type, exc_value, tb) -> bool:
        with NoTracing():
            if isinstance(exc_value, (PostconditionFailed, IgnoreAttempt)):
                if isinstance(exc_value, PostconditionFailed):
                    # Postcondition : although this indicates a problem, it's with a
                    # subroutine; not this function.
                    # Usualy we want to ignore this because it will be surfaced more locally
                    # in the subroutine.
                    debug(
                        f"Ignoring based on internal failed post condition: {exc_value}"
                    )
                self.ignore = True
                self.analysis = CallAnalysis()
                return True
            if isinstance(exc_value, self.expected_exceptions):
                exc_type_name = type(exc_value).__name__
                debug(f"Hit expected exception: {exc_type_name}: {exc_value}")
                self.ignore = True
                self.analysis = CallAnalysis(VerificationStatus.CONFIRMED)
                return True
            if isinstance(exc_value, TypeError):
                exc_str = str(exc_value)
                if (
                    "SymbolicStr" in exc_str
                    or "SymbolicInt" in exc_str
                    or "SymbolicFloat" in exc_str
                    or "__hash__ method should return an integer" in exc_str
                    or "expected string or bytes-like object" in exc_str
                ):
                    # Ideally we'd attempt literal strings after encountering this.
                    # See https://github.com/pschanely/CrossHair/issues/8
                    debug("Proxy intolerace at: ", traceback.format_exc())
                    raise CrosshairUnsupported("Detected proxy intolerance: " + exc_str)
            if isinstance(
                exc_value, (UnexploredPath, CrosshairInternal, z3.Z3Exception)
            ):
                return False  # internal issue: re-raise
            if isinstance(exc_value, BaseException):
                # Most other issues are assumed to be user-facing exceptions:
                self.user_exc = (exc_value, traceback.extract_tb(sys.exc_info()[2]))
                self.analysis = CallAnalysis(VerificationStatus.REFUTED)
                return True  # suppress user-level exception
            return False  # re-raise resource and system issues

_T = TypeVar("_T")

from crosshair.tracers import NoTracing

def realize(value: _T) -> _T:
    with NoTracing():
        if hasattr(type(value), "__ch_realize__"):
            return value.__ch_realize__()  # type: ignore
        else:
            return value

_INSIDE_REALIZATION = DynamicScopeVar(bool, "inside_realization")

def inside_realization() -> bool:
    return _INSIDE_REALIZATION.get(default=False)

# TODO: some kind of comprehensive realization tests.
def deep_realize(value: _T) -> _T:
    with NoTracing():
        with _INSIDE_REALIZATION.open(True):
            try:
                return copy.deepcopy(value, {})
            except TypeError as exc:
                debug(f"abort realizing {type(value)} object: {type(exc)}: {exc}")
                return value

class CrossHairValue:
    def __deepcopy__(self, memo: Dict) -> object:
        if inside_realization() and hasattr(self, "__ch_realize__"):
            result = copy.deepcopy(self.__ch_realize__())  # type: ignore
        else:
            # Try to replicate the regular deepcopy:
            cls = self.__class__
            result = cls.__new__(cls)
            for k, v in self.__dict__.items():
                object.__setattr__(result, k, copy.deepcopy(v, memo))
        memo[id(self)] = result
        return result

def normalize_pytype(typ: Type) -> Type:
    if typing_inspect.is_typevar(typ):
        # we treat type vars in the most general way possible (the bound, or as 'object')
        bound = typing_inspect.get_bound(typ)
        if bound is not None:
            return normalize_pytype(bound)
        constraints = typing_inspect.get_constraints(typ)
        if constraints:
            raise CrosshairUnsupported
            # TODO: not easy; interpreting as a Union allows the type to be
            # instantiated differently in different places. So, this doesn't work:
            # return Union.__getitem__(tuple(map(normalize_pytype, constraints)))
        return object
    if typ is Any:
        # The distinction between any and object is for type checking, crosshair treats them the same
        return object
    if typ is Type:
        return type
    return typ

def origin_of(typ: Type) -> Type:
    if hasattr(typ, "__origin__"):
        return typ.__origin__
    return typ

def type_arg_of(typ: Type, index: int) -> Type:
    args = type_args_of(typ)
    return args[index] if index < len(args) else object

def type_args_of(typ: Type) -> Tuple[Type, ...]:
    if getattr(typ, "__args__", None):
        return typing_inspect.get_args(typ, evaluate=True)
    else:
        return ()

def python_type(o: object) -> Type:
    if is_tracing():
        raise CrosshairInternal("should not be tracing while getting pytype")
    if hasattr(type(o), "__ch_pytype__"):
        obj_type = o.__ch_pytype__()  # type: ignore
        if hasattr(obj_type, "__origin__"):
            obj_type = obj_type.__origin__
        return obj_type
    else:
        return type(o)

def with_realized_args(fn: Callable) -> Callable:
    def realizer(*a, **kw):
        a = map(realize, a)
        kw = {k: realize(v) for (k, v) in kw.items()}
        return fn(*a, **kw)

    functools.update_wrapper(realizer, fn)
    return realizer

_IMMUTABLE_TYPES = (int, float, complex, bool, tuple, frozenset, type(None))

def choose_type(space: StateSpace, from_type: Type) -> Type:
    subtypes = get_subclass_map()[from_type]
    # Note that this is written strangely to leverage the default
    # preference for false when forking:
    if not subtypes or not space.smt_fork(desc="choose_" + smtlib_typename(from_type)):
        return from_type
    for subtype in subtypes[:-1]:
        if not space.smt_fork(desc="choose_" + smtlib_typename(subtype)):
            return choose_type(space, subtype)
    return choose_type(space, subtypes[-1])

def get_constructor_signature(cls: Type) -> Optional[inspect.Signature]:
    # pydantic sets __signature__ on the class, so we look for that as well as on
    # __init__ (see https://github.com/samuelcolvin/pydantic/pull/1034)
    if hasattr(cls, "__signature__"):
        sig = resolve_signature(cls)
        if isinstance(sig, inspect.Signature):
            return sig
    new_fn = cls.__new__
    init_fn = cls.__init__
    if (
        new_fn is not object.__new__
        and
        # Some superclasses like Generic[T] define __new__ with typless (*a,**kw)
        # args. Skip if we don't have types on __new__.
        # TODO: merge the type signatures of __init__ and __new__, pulling the
        # most specific types from each.
        len(get_type_hints(new_fn)) > 0
    ):
        sig = resolve_signature(new_fn)
    elif init_fn is not object.__init__:
        sig = resolve_signature(init_fn)
    else:
        return inspect.Signature([])
    if isinstance(sig, inspect.Signature):
        # strip first argument
        newparams = list(sig.parameters.values())[1:]
        return sig.replace(parameters=newparams)
    return None

def proxy_for_class(typ: Type, varname: str) -> object:
    data_members = get_type_hints(typ)
    class_conditions = get_current_parser().get_class_conditions(typ)
    has_invariants = class_conditions is not None and bool(class_conditions.inv)

    # Special handling for some magical types:
    if issubclass(typ, tuple):
        tuple_args = {
            k: proxy_for_type(t, varname + "." + k) for (k, t) in data_members.items()
        }
        return typ(**tuple_args)  # type: ignore
    elif sys.version_info >= (3, 8) and type(typ) is typing._TypedDictMeta:  # type: ignore
        # Handling for TypedDict
        optional_keys = getattr(typ, "__optional_keys__", ())
        keys = (
            k
            for k in data_members.keys()
            if k not in optional_keys or context_statespace().smt_fork()
        )
        return {k: proxy_for_type(data_members[k], varname + "." + k) for k in keys}

    constructor_sig = get_constructor_signature(typ)
    if constructor_sig is None:
        raise CrosshairUnsupported(
            f"unable to create concrete instance of {typ} due to bad constructor"
        )
    args = gen_args(constructor_sig)
    try:
        with ResumedTracing():
            obj = WithEnforcement(typ)(*args.args, **args.kwargs)
    except (PreconditionFailed, PostconditionFailed):
        # preconditions can be invalidated when the __init__ method has preconditions.
        # postconditions can be invalidated when the class has invariants.
        raise IgnoreAttempt
    except BaseException as e:
        debug("Root-cause type construction traceback:", test_stack(e.__traceback__))
        raise CrosshairUnsupported(
            f"error constructing {name_of_type(typ)} instance: {name_of_type(type(e))}: {e}",
        ) from e

    debug("Proxy as a concrete instance of", name_of_type(typ))
    return obj

def register_patch(entity: Callable, patch_value: Callable):
    if entity in _PATCH_REGISTRATIONS:
        raise CrosshairInternal(f"Doubly registered patch: {entity}")
    _PATCH_REGISTRATIONS[entity] = patch_value

def register_opcode_patch(module: TracingModule) -> None:
    _OPCODE_PATCHES.append(module)

class SymbolicFactory:
    """
    A callable object that creates symbolic values.

    .. automethod:: __call__
    """

    def __init__(self, space: StateSpace, pytype: object, varname: str):
        self.space = space
        self.pytype: Any = pytype
        self.varname = varname

    @overload
    def __call__(
        self, typ: Callable[..., _T], suffix: str = "", allow_subtypes: bool = True
    ) -> _T:
        ...

    @overload
    def __call__(self, typ: Any, suffix: str = "", allow_subtypes: bool = True) -> Any:
        ...

    def __call__(self, typ, suffix: str = "", allow_subtypes: bool = True):
        """
        Create a new symbolic value.

        :param typ: The corresponding Python type for the returned symbolic.
        :type typ: type
        :param suffix: A descriptive suffix used to name variable(s) in the solver.
        :type suffix: str
        :param allow_subtypes: Whether it's ok to return a subtype of given type.
        :type allow_subtypes: bool
        :returns: A new symbolic value.
        """
        return proxy_for_type(
            typ,
            self.varname + suffix + self.space.uniq(),
            allow_subtypes=allow_subtypes,
        )

_SIMPLE_PROXIES: MutableMapping[object, Callable] = {}

SymbolicCreationCallback = Union[
    # Sadly Callable[] doesn't support variable arguments. Just enumerate:
    Callable[[SymbolicFactory], object],
    Callable[[SymbolicFactory, Type], object],
    Callable[[SymbolicFactory, Type, Type], object],
    Callable[[SymbolicFactory, Type, Type, Type], object],
    Callable[[SymbolicFactory, Type, Type, Type, Type], object],
]

def register_type(typ: Type, creator: SymbolicCreationCallback) -> None:
    """
    Register a custom creation function to create symbolic values for a type.

    :param typ: The Python type (or typing annotation) to handle.
    :param creator: A function that takes a :class:`SymbolicFactory` instance and
      returns a symbolic value. When creating a parameterized type (e.g. List[int]),
      type parameters will be given to `creator` as additional arguments following the
      factory.
    """
    assert typ is origin_of(
        typ
    ), f'Only origin types may be registered, not "{typ}": try "{origin_of(typ)}" instead.'
    if typ in _SIMPLE_PROXIES:
        raise CrosshairInternal(f'Duplicate type "{typ}" registered')
    _SIMPLE_PROXIES[typ] = creator

@overload
def proxy_for_type(
    typ: Callable[..., _T],
    varname: str,
    allow_subtypes: bool = True,
) -> _T:
    ...

@overload
def proxy_for_type(
    typ: Any,
    varname: str,
    allow_subtypes: bool = True,
) -> Any:
    ...

def proxy_for_type(
    typ: Any,
    varname: str,
    allow_subtypes: bool = False,
) -> Any:
    space = context_statespace()
    with NoTracing():
        typ = normalize_pytype(typ)
        origin = origin_of(typ)
        type_args = type_args_of(typ)
        # special cases
        if isinstance(typ, type) and issubclass(typ, enum.Enum):
            enum_values = list(typ)  # type:ignore
            if not enum_values:
                raise IgnoreAttempt("No values for enum")
            for enum_value in enum_values[:-1]:
                if space.smt_fork(desc="choose_enum_" + str(enum_value)):
                    return enum_value
            return enum_values[-1]
        # It's easy to forget to import crosshair.core_and_libs; check:
        assert _SIMPLE_PROXIES, "No proxy type registrations exist"
        proxy_factory = _SIMPLE_PROXIES.get(origin)
        if proxy_factory:
            recursive_proxy_factory = SymbolicFactory(space, typ, varname)
            return proxy_factory(recursive_proxy_factory, *type_args)
        if allow_subtypes and typ is not object:
            typ = choose_type(space, typ)
        return proxy_for_class(typ, varname)

def gen_args(sig: inspect.Signature) -> inspect.BoundArguments:
    if is_tracing():
        raise CrosshairInternal
    args = sig.bind_partial()
    space = context_statespace()
    for param in sig.parameters.values():
        smt_name = param.name + space.uniq()
        proxy_maker = lambda typ: proxy_for_type(typ, smt_name, allow_subtypes=True)
        has_annotation = param.annotation != inspect.Parameter.empty
        value: object
        if param.kind == inspect.Parameter.VAR_POSITIONAL:
            if has_annotation:
                varargs_type = List[param.annotation]  # type: ignore
                value = proxy_maker(varargs_type)
            else:
                value = proxy_maker(List[Any])
        elif param.kind == inspect.Parameter.VAR_KEYWORD:
            if has_annotation:
                varargs_type = Dict[str, param.annotation]  # type: ignore
                value = cast(dict, proxy_maker(varargs_type))
                # Using ** on a dict requires concrete string keys. Force
                # instiantiation of keys here:
                value = {k.__str__(): v for (k, v) in value.items()}
            else:
                value = proxy_maker(Dict[str, Any])
        else:
            is_self = param.name == "self"
            # Object parameters can be any valid subtype iff they are not the
            # class under test ("self").
            allow_subtypes = not is_self
            if has_annotation:
                value = proxy_for_type(param.annotation, smt_name, allow_subtypes)
            else:
                value = proxy_for_type(cast(type, Any), smt_name, allow_subtypes)
        debug("created proxy for", param.name, "as type:", name_of_type(type(value)))
        args.arguments[param.name] = value
    return args

def message_sort_key(m: AnalysisMessage) -> tuple:
    return (m.state, UNABLE_TO_REPR not in m.message, -len(m.message))

class MessageCollector:
    def __init__(self):
        self.by_pos = {}

    def extend(self, messages: Iterable[AnalysisMessage]) -> None:
        for message in messages:
            self.append(message)

    def append(self, message: AnalysisMessage) -> None:
        key = (message.filename, message.line, message.column)
        if key in self.by_pos:
            self.by_pos[key] = max(self.by_pos[key], message, key=message_sort_key)
        else:
            self.by_pos[key] = message

    def get(self) -> List[AnalysisMessage]:
        return [m for (k, m) in sorted(self.by_pos.items())]

class Checkable:
    def analyze(self) -> Iterable[AnalysisMessage]:
        raise NotImplementedError

@dataclass
class ConditionCheckable(Checkable):
    ctxfn: FunctionInfo
    options: AnalysisOptions
    conditions: Conditions

    def analyze(self) -> Iterable[AnalysisMessage]:
        options = self.options
        conditions = self.conditions
        debug('Analyzing postcondition: "', conditions.post[0].expr_source, '"')
        debug(
            "assuming preconditions: ",
            ",".join([p.expr_source for p in conditions.pre]),
        )
        options.deadline = time.monotonic() + options.per_condition_timeout

        with condition_parser(options.analysis_kind):
            analysis = analyze_calltree(options, conditions)

        (condition,) = conditions.post
        if analysis.verification_status is VerificationStatus.UNKNOWN:
            message = "Not confirmed."
            analysis.messages = [
                AnalysisMessage(
                    MessageType.CANNOT_CONFIRM,
                    message,
                    condition.filename,
                    condition.line,
                    0,
                    "",
                )
            ]
        elif analysis.verification_status is VerificationStatus.CONFIRMED:
            message = "Confirmed over all paths."
            analysis.messages = [
                AnalysisMessage(
                    MessageType.CONFIRMED,
                    message,
                    condition.filename,
                    condition.line,
                    0,
                    "",
                )
            ]

        return analysis.messages

class ClampedCheckable(Checkable):
    """
    Clamp messages for a class method to appear on the class itself.

    So, even if the method is defined on a superclass, or defined dynamically (via
    decorator etc), we report it on the class definition instead.
    """

    def __init__(self, checkable: Checkable, cls: type):
        self.checkable = checkable
        filename, start_line, _ = sourcelines(cls)
        self.cls_file = filename
        self.cls_start_line = start_line

    def analyze(self) -> Iterable[AnalysisMessage]:
        cls_file = self.cls_file
        ret = []
        for message in self.checkable.analyze():
            if not samefile(message.filename, cls_file):
                ret.append(
                    replace(message, filename=cls_file, line=self.cls_start_line)
                )
            else:
                ret.append(message)
        return ret

@dataclass
class SyntaxErrorCheckable(Checkable):
    messages: List[AnalysisMessage]

    def analyze(self) -> Iterable[AnalysisMessage]:
        return self.messages

def run_checkables(checkables: Iterable[Checkable]) -> List[AnalysisMessage]:
    collector = MessageCollector()
    for checkable in checkables:
        collector.extend(checkable.analyze())
    return collector.get()

def analyze_any(
    entity: Union[types.ModuleType, type, FunctionInfo], options: AnalysisOptionSet
) -> Iterable[Checkable]:
    if inspect.isclass(entity):
        yield from analyze_class(cast(Type, entity), options)
    elif isinstance(entity, FunctionInfo):
        yield from analyze_function(entity, options)
    elif inspect.ismodule(entity):
        yield from analyze_module(cast(types.ModuleType, entity), options)
    else:
        raise CrosshairInternal("Entity type not analyzable: " + str(type(entity)))

def analyze_module(
    module: types.ModuleType, options: AnalysisOptionSet
) -> Iterable[Checkable]:
    """Analyze the classes and functions defined in a module."""
    module_name = module.__name__
    for name, member in inspect.getmembers(module):
        if not (
            inspect.isclass(member)
            or inspect.isfunction(member)
            or inspect.ismethod(member)
        ):
            continue
        if member.__module__ != module_name:
            # Modules often have contents that are imported from elsewhere
            continue
        if inspect.isclass(member):
            yield from analyze_class(member, options)
        else:
            yield from analyze_function(FunctionInfo.from_module(module, name), options)

def analyze_class(
    cls: type, options: AnalysisOptionSet = AnalysisOptionSet()
) -> Iterable[Checkable]:
    debug("Analyzing class ", cls.__name__)
    analysis_kinds = DEFAULT_OPTIONS.overlay(options).analysis_kind
    with condition_parser(analysis_kinds) as parser:
        class_conditions = parser.get_class_conditions(cls)
        for method_name, conditions in class_conditions.methods.items():
            if method_name == "__init__":
                # Don't check invariants on __init__.
                # (too often this just requires turning the invariant into a very
                # similar precondition)
                filtered_post = [
                    c
                    for c in conditions.post
                    if c.condition_type != ConditionExprType.INVARIANT
                ]
                conditions = replace(conditions, post=filtered_post)
            if conditions.has_any():
                # Note the use of getattr_static to check superclass contracts on
                # functions that the subclass doesn't define.
                ctxfn = FunctionInfo(
                    cls, method_name, inspect.getattr_static(cls, method_name)
                )
                for checkable in analyze_function(ctxfn, options=options):
                    yield ClampedCheckable(checkable, cls)

def analyze_function(
    ctxfn: Union[FunctionInfo, types.FunctionType, Callable],
    options: AnalysisOptionSet = AnalysisOptionSet(),
) -> List[Checkable]:

    if not isinstance(ctxfn, FunctionInfo):
        ctxfn = FunctionInfo.from_fn(ctxfn)
    debug("Analyzing ", ctxfn.name)
    pair = ctxfn.get_callable()
    fn_options = collect_options(pair[0]) if pair else AnalysisOptionSet()
    full_options = DEFAULT_OPTIONS.overlay(fn_options).overlay(options)
    if not full_options.enabled:
        debug("Skipping", ctxfn.name, " because CrossHair is not enabled")
        return []

    with condition_parser(full_options.analysis_kind) as parser:
        if not isinstance(ctxfn.context, type):
            conditions = parser.get_fn_conditions(ctxfn)
        else:
            class_conditions = parser.get_class_conditions(ctxfn.context)
            conditions = class_conditions.methods.get(ctxfn.name)

    if conditions is None:
        debug("Skipping", ctxfn.name, " because it has no conditions")
        return []
    syntax_messages = list(conditions.syntax_messages())
    if syntax_messages:
        messages = [
            AnalysisMessage(
                MessageType.SYNTAX_ERR,
                syntax_message.message,
                syntax_message.filename,
                syntax_message.line_num,
                0,
                "",
            )
            for syntax_message in syntax_messages
        ]
        return [SyntaxErrorCheckable(messages)]
    return [
        ConditionCheckable(
            ctxfn, full_options, replace(conditions, post=[post_condition])
        )
        for post_condition in conditions.post
        if post_condition.evaluate is not None
    ]

class ShortCircuitingContext:
    engaged = False

    def __enter__(self):
        assert not self.engaged
        self.engaged = True

    def __exit__(self, exc_type, exc_value, tb):
        assert self.engaged
        self.engaged = False
        return False

    def make_interceptor(self, original: Callable) -> Callable:
        # TODO: calling from_fn is wrong here
        subconditions = get_current_parser().get_fn_conditions(
            FunctionInfo.from_fn(original)
        )
        original_name = original.__name__
        if subconditions is None:
            return original
        sig = subconditions.sig

        def _crosshair_wrapper(*a: object, **kw: Dict[str, object]) -> object:
            space = optional_context_statespace()
            if (not self.engaged) or (not space) or space.running_framework_code:
                debug("Not short-circuiting", original_name, "(not engaged)")
                return original(*a, **kw)

            with NoTracing():
                bound = sig.bind(*a, **kw)
                assert subconditions is not None
                return_type = consider_shortcircuit(original, sig, bound, subconditions)
                if return_type is None:
                    callinto_probability = 1.0
                else:
                    short_stats, callinto_stats = space.stats_lookahead()
                    if callinto_stats.unknown_pct < short_stats.unknown_pct:
                        callinto_probability = 1.0
                    else:
                        callinto_probability = 0.7

                debug("short circuit: call-into probability", callinto_probability)
                do_short_circuit = space.fork_parallel(
                    callinto_probability, desc=f"shortcircuit {original_name}"
                )
                # Statespace can pick either even with 0.0 or 1.0 probability:
                do_short_circuit &= return_type is not None
            if do_short_circuit:
                assert return_type is not None
                try:
                    self.engaged = False
                    debug(
                        "short circuit: Short circuiting over a call to ", original_name
                    )
                    return shortcircuit(original, sig, bound, return_type)
                finally:
                    self.engaged = True
            else:
                debug("short circuit: Not short circuiting", original_name)
                return original(*a, **kw)

        functools.update_wrapper(_crosshair_wrapper, original)
        return _crosshair_wrapper

@dataclass
class CallTreeAnalysis:
    messages: Sequence[AnalysisMessage]
    verification_status: VerificationStatus
    num_confirmed_paths: int = 0

def analyze_calltree(
    options: AnalysisOptions, conditions: Conditions
) -> CallTreeAnalysis:
    fn = conditions.fn
    debug("Begin analyze calltree ", fn.__name__)

    all_messages = MessageCollector()
    search_root = SinglePathNode(True)
    space_exhausted = False
    failing_precondition: Optional[ConditionExpr] = (
        conditions.pre[0] if conditions.pre else None
    )
    failing_precondition_reason: str = ""
    num_confirmed_paths = 0

    _ = get_subclass_map()  # ensure loaded
    short_circuit = ShortCircuitingContext()
    top_analysis: Optional[CallAnalysis] = None
    enforced_conditions = EnforcedConditions(
        interceptor=short_circuit.make_interceptor,
    )
    patched = Patched()
    # TODO clean up how encofrced conditions works here?
    with enforced_conditions, patched:
        for i in range(1, options.max_iterations + 1):
            start = time.monotonic()
            if start > options.deadline:
                debug("Exceeded condition timeout, stopping")
                break
            options.incr("num_paths")
            debug("Iteration ", i)
            space = StateSpace(
                execution_deadline=start + options.per_path_timeout,
                model_check_timeout=options.per_path_timeout / 2,
                search_root=search_root,
            )
            try:
                with StateSpaceContext(space), COMPOSITE_TRACER:
                    # The real work happens here!:
                    call_analysis = attempt_call(
                        conditions, fn, short_circuit, enforced_conditions
                    )
                if failing_precondition is not None:
                    cur_precondition = call_analysis.failing_precondition
                    if cur_precondition is None:
                        if call_analysis.verification_status is not None:
                            # We escaped the all the pre conditions on this try:
                            failing_precondition = None
                    elif (
                        cur_precondition.line == failing_precondition.line
                        and call_analysis.failing_precondition_reason
                    ):
                        failing_precondition_reason = (
                            call_analysis.failing_precondition_reason
                        )
                    elif cur_precondition.line > failing_precondition.line:
                        failing_precondition = cur_precondition
                        failing_precondition_reason = (
                            call_analysis.failing_precondition_reason
                        )

            except UnexploredPath:
                call_analysis = CallAnalysis(VerificationStatus.UNKNOWN)
            except IgnoreAttempt:
                call_analysis = CallAnalysis()
            status = call_analysis.verification_status
            if status == VerificationStatus.CONFIRMED:
                num_confirmed_paths += 1
            top_analysis, space_exhausted = space.bubble_status(call_analysis)
            debug("Path tree stats", search_root.stats())
            overall_status = top_analysis.verification_status if top_analysis else None
            debug(
                "Iter complete. Worst status found so far:",
                overall_status.name if overall_status else "None",
            )
            if space_exhausted or top_analysis == VerificationStatus.REFUTED:
                break
    top_analysis = search_root.child.get_result()
    if top_analysis.messages:
        all_messages.extend(
            replace(
                m, test_fn=fn.__qualname__, condition_src=conditions.post[0].expr_source
            )
            for m in top_analysis.messages
        )
    if top_analysis.verification_status is None:
        top_analysis.verification_status = VerificationStatus.UNKNOWN
    if failing_precondition:
        assert num_confirmed_paths == 0
        message = f"Unable to meet precondition"
        if failing_precondition_reason:
            message += f" (possibly because {failing_precondition_reason}?)"
        all_messages.extend(
            [
                AnalysisMessage(
                    MessageType.PRE_UNSAT,
                    message + ".",
                    failing_precondition.filename,
                    failing_precondition.line,
                    0,
                    "",
                )
            ]
        )
        top_analysis = CallAnalysis(VerificationStatus.REFUTED)

    assert top_analysis.verification_status is not None
    debug(
        ("Exhausted" if space_exhausted else "Aborted"),
        "calltree search with",
        top_analysis.verification_status.name,
        "and",
        len(all_messages.get()),
        "messages.",
        "Number of iterations: ",
        i,
    )
    return CallTreeAnalysis(
        messages=all_messages.get(),
        verification_status=top_analysis.verification_status,
        num_confirmed_paths=num_confirmed_paths,
    )

class UnEqual:
    pass

_UNEQUAL = UnEqual()

def deep_eq(old_val: object, new_val: object, visiting: Set[Tuple[int, int]]) -> bool:
    # TODO: test just about all of this
    if old_val is new_val:
        return True
    if type(old_val) != type(new_val):
        return False
    visit_key = (id(old_val), id(new_val))
    if visit_key in visiting:
        return True
    visiting.add(visit_key)
    try:
        with NoTracing():
            is_ch_value = isinstance(old_val, CrossHairValue)
        if is_ch_value:
            return old_val == new_val
        elif hasattr(old_val, "__dict__") and hasattr(new_val, "__dict__"):
            return deep_eq(old_val.__dict__, new_val.__dict__, visiting)
        elif isinstance(old_val, dict):
            assert isinstance(new_val, dict)
            for key in set(itertools.chain(old_val.keys(), *new_val.keys())):
                if (key in old_val) ^ (key in new_val):
                    return False
                if not deep_eq(
                    old_val.get(key, _UNEQUAL), new_val.get(key, _UNEQUAL), visiting
                ):
                    return False
            return True
        elif isinstance(old_val, Iterable):
            assert isinstance(new_val, Sized)
            if isinstance(old_val, Sized):
                if len(old_val) != len(new_val):
                    return False
            assert isinstance(new_val, Iterable)
            return all(
                deep_eq(o, n, visiting)
                for (o, n) in itertools.zip_longest(
                    old_val, new_val, fillvalue=_UNEQUAL
                )
            )
        elif type(old_val) is object:
            # Plain object instances are close enough to equal for our purposes
            return True
        else:
            # hopefully this is just ints, bools, etc
            return old_val == new_val
    finally:
        visiting.remove(visit_key)

class MessageGenerator:
    def __init__(self, fn: Callable):
        self.filename = ""
        if hasattr(fn, "__code__"):
            code_obj = fn.__code__
            self.filename = code_obj.co_filename
            self.start_lineno = code_obj.co_firstlineno
            _, _, lines = sourcelines(fn)
            self.end_lineno = self.start_lineno + len(lines)

    def make(
        self,
        message_type: MessageType,
        detail: str,
        suggested_filename: Optional[str],
        suggested_lineno: int,
        tb: str,
    ) -> AnalysisMessage:
        if (
            suggested_filename is not None
            and (os.path.abspath(suggested_filename) == os.path.abspath(self.filename))
            and (self.start_lineno <= suggested_lineno <= self.end_lineno)
        ):
            return AnalysisMessage(
                message_type, detail, suggested_filename, suggested_lineno, 0, tb
            )
        else:
            exprline = "<unknown>"
            if suggested_filename is not None:
                lines = linecache.getlines(suggested_filename)
                try:
                    exprline = lines[suggested_lineno - 1].strip()
                except IndexError:
                    pass
            detail = f'"{exprline}" yields {detail}'
            return AnalysisMessage(
                message_type, detail, self.filename, self.start_lineno, 0, tb
            )

def attempt_call(
    conditions: Conditions,
    fn: Callable,
    short_circuit: ShortCircuitingContext,
    enforced_conditions: EnforcedConditions,
    bound_args: Optional[BoundArguments] = None,
) -> CallAnalysis:
    assert fn is conditions.fn  # TODO: eliminate the explicit `fn` parameter?
    space = context_statespace()
    msg_gen = MessageGenerator(conditions.src_fn)
    with enforced_conditions.enabled_enforcement(), NoTracing():
        bound_args = gen_args(conditions.sig) if bound_args is None else bound_args

        # TODO: looks wrong(-ish) to guard this with NoTracing().
        # Copy on custom objects may require patched builtins. (datetime.timedelta is one such case)
        original_args = copy.deepcopy(bound_args)
    space.checkpoint()

    lcls: Mapping[str, object] = bound_args.arguments
    # In preconditions, __old__ exists but is just bound to the same args.
    # This lets people write class invariants using `__old__` to, for example,
    # demonstrate immutability.
    lcls = {"__old__": AttributeHolder(lcls), **lcls}
    expected_exceptions = conditions.raises
    for precondition in conditions.pre:
        if not precondition.evaluate:
            continue
        with ExceptionFilter(expected_exceptions) as efilter:
            with enforced_conditions.enabled_enforcement(), short_circuit:
                precondition_ok = prefer_true(precondition.evaluate(lcls))
            if not precondition_ok:
                debug("Failed to meet precondition", precondition.expr_source)
                return CallAnalysis(failing_precondition=precondition)
        if efilter.ignore:
            debug("Ignored exception in precondition.", efilter.analysis)
            return efilter.analysis
        elif efilter.user_exc is not None:
            (user_exc, tb) = efilter.user_exc
            debug(
                "Exception attempting to meet precondition",
                precondition.expr_source,
                ":",
                user_exc,
                tb.format(),
            )
            return CallAnalysis(
                failing_precondition=precondition,
                failing_precondition_reason=f'it raised "{repr(user_exc)} at {tb.format()[-1]}"',
            )

    with ExceptionFilter(expected_exceptions) as efilter:
        with enforced_conditions.enabled_enforcement(), short_circuit:
            assert not space.running_framework_code
            debug("Starting function body")
            __return__ = NoEnforce(fn)(*bound_args.args, **bound_args.kwargs)
        lcls = {
            **bound_args.arguments,
            "__return__": __return__,
            "_": __return__,
            "__old__": AttributeHolder(original_args.arguments),
            fn.__name__: fn,
        }

    if efilter.ignore:
        debug("Ignored exception in function.", efilter.analysis)
        return efilter.analysis
    elif efilter.user_exc is not None:
        (e, tb) = efilter.user_exc
        space.detach_path(e)
        detail = name_of_type(type(e)) + ": " + str(e)
        frame_filename, frame_lineno = frame_summary_for_fn(conditions.src_fn, tb)
        tb_desc = tb.format()
        detail += " " + conditions.format_counterexample(original_args)
        debug("exception while evaluating function body:", detail, tb_desc)
        return CallAnalysis(
            VerificationStatus.REFUTED,
            [
                msg_gen.make(
                    MessageType.EXEC_ERR,
                    detail,
                    frame_filename,
                    frame_lineno,
                    "".join(tb_desc),
                )
            ],
        )

    for argname, argval in bound_args.arguments.items():
        if (
            conditions.mutable_args is not None
            and argname not in conditions.mutable_args
        ):
            old_val, new_val = original_args.arguments[argname], argval
            # TODO: Do we really need custom equality here? Would love to drop that
            # `deep_eq` function.
            if not deep_eq(old_val, new_val, set()):
                space.detach_path()
                detail = 'Argument "{}" is not marked as mutable, but changed from {} to {}'.format(
                    argname, old_val, new_val
                )
                debug("Mutablity problem:", detail)
                return CallAnalysis(
                    VerificationStatus.REFUTED,
                    [msg_gen.make(MessageType.POST_ERR, detail, None, 0, "")],
                )

    (post_condition,) = conditions.post
    assert post_condition.evaluate is not None
    with ExceptionFilter(expected_exceptions) as efilter:
        # TODO: re-enable post-condition short circuiting. This will require refactoring how
        # enforced conditions and short curcuiting interact, so that post-conditions are
        # selectively run when, and only when, performing a short circuit.
        # with enforced_conditions.enabled_enforcement(), short_circuit:
        assert not space.running_framework_code
        debug("Starting postcondition")
        isok = bool(post_condition.evaluate(lcls))
    if efilter.ignore:
        debug("Ignored exception in postcondition.", efilter.analysis)
        return efilter.analysis
    elif efilter.user_exc is not None:
        (e, tb) = efilter.user_exc
        space.detach_path(e)
        detail = (
            repr(e) + " " + conditions.format_counterexample(original_args, __return__)
        )
        debug("exception while calling postcondition:", detail)
        debug("exception traceback:", test_stack(tb))
        failures = [
            msg_gen.make(
                MessageType.POST_ERR,
                detail,
                post_condition.filename,
                post_condition.line,
                "".join(tb.format()),
            )
        ]
        return CallAnalysis(VerificationStatus.REFUTED, failures)
    if isok:
        debug("Postcondition confirmed.")
        return CallAnalysis(VerificationStatus.CONFIRMED)
    else:
        space.detach_path()
        detail = "false " + conditions.format_counterexample(original_args, __return__)
        debug(detail)
        failures = [
            msg_gen.make(
                MessageType.POST_FAIL,
                detail,
                post_condition.filename,
                post_condition.line,
                "",
            )
        ]
        return CallAnalysis(VerificationStatus.REFUTED, failures)

# Objects of these types are known to always be *deeply* immutable:
_ATOMIC_IMMUTABLE_TYPES = (
    type(None),
    int,
    str,
    float,
    complex,
    types.FunctionType,
    types.BuiltinFunctionType,
    types.LambdaType,
    types.MethodType,
    types.BuiltinMethodType,
)

def _mutability_testing_hash(o: object) -> int:
    if isinstance(o, _ATOMIC_IMMUTABLE_TYPES):
        return 0
    if hasattr(o, "__ch_is_deeply_immutable__"):
        if o.__ch_is_deeply_immutable__():  # type: ignore
            return 0
        else:
            raise TypeError
    typ = type(o)
    if not hasattr(typ, "__hash__"):
        raise TypeError
    # We err on the side of mutability if this object is using the default hash:
    if typ.__hash__ is object.__hash__:
        raise TypeError
    return typ.__hash__(o)

def is_deeply_immutable(o: object) -> bool:
    with TracingOnly(PatchingModule({hash: _mutability_testing_hash})):
        # debug('entered patching context', COMPOSITE_TRACER.modules)
        try:
            hash(o)
            return True
        except TypeError:
            return False

def consider_shortcircuit(
    fn: Callable, sig: Signature, bound: BoundArguments, subconditions: Conditions
) -> Optional[type]:
    """
    Consider the feasibility of short-circuiting (skipping) a function with the given arguments.

    :return: The type of a symbolic value that could be returned by ``fn``.
    :return: None if a short-circuiting should not be attempted.
    """
    return_type = sig.return_annotation

    mutable_args = subconditions.mutable_args
    if mutable_args is None or len(mutable_args) > 0:
        # we don't deal with mutation inside the skipped function yet.
        debug("aborting shortcircuit: function has matuable args")
        return None

    # Deduce type vars if necessary
    if len(typing_inspect.get_parameters(return_type)) > 0 or typing_inspect.is_typevar(
        return_type
    ):

        typevar_bindings: typing.ChainMap[object, type] = collections.ChainMap()
        bound.apply_defaults()
        for param in sig.parameters.values():
            argval = bound.arguments[param.name]
            # We don't need all args to be symbolic, but we don't currently
            # short circuit in that case as a heuristic.
            if not isinstance(argval, CrossHairValue):
                debug("aborting shortcircuit:", param.name, "is not symbolic")
                return None
            value_type = python_type(argval)
            if not dynamic_typing.unify(value_type, param.annotation, typevar_bindings):
                debug("aborting shortcircuit", param.name, "fails unification")
                return None
        return_type = dynamic_typing.realize(sig.return_annotation, typevar_bindings)
    return return_type

def shortcircuit(
    fn: Callable, sig: Signature, bound: BoundArguments, return_type: Type
) -> object:
    space = context_statespace()
    debug("short circuit: Deduced return type was ", return_type)

    # Deep copy the arguments for reconciliation later.
    # (we know that this function won't mutate them, but not that others won't)
    argscopy = {}
    for name, val in bound.arguments.items():
        if is_deeply_immutable(val):
            argscopy[name] = val
        else:
            with NoTracing():  # TODO: decide how deep copies should work
                argscopy[name] = copy.deepcopy(val)
    bound_copy = BoundArguments(sig, argscopy)  # type: ignore

    retval = None
    if return_type is not type(None):
        # note that the enforcement wrapper ensures postconditions for us, so
        # we can just return a free variable here.
        retval = proxy_for_type(return_type, "proxyreturn" + space.uniq())

    def reconciled() -> bool:
        return retval == fn(*bound_copy.args, **bound_copy.kwargs)

    space.defer_assumption("Reconcile short circuit", reconciled)

    return retval


from flask.ext.assets import Environment, Bundle

css = Bundle(
    'libs/bootstrap/dist/css/bootstrap.css',
    output='build/css/common.css'
)

js = Bundle(
    'libs/jquery/dist/jquery.js',
    'libs/bootstrap/dist/js/bootstrap.js',
    'libs/angular/angular.js',
    'libs/angular-resource/angular-resource.js',
    'libs/angular-ui-router/release/angular-ui-router.js',
    'js/app.js',
    'js/controllers.js',
    'js/userService.js',
    output='build/js/common.js'
)

assets = Environment()

assets.register('js_all', js)
assets.register('css_all', css)


import time
from hashlib import sha1
from datetime import datetime, timedelta, time

from flask import Flask
from flask import request
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)

from models import Calendar, Event

def handle_calendar_post(calendar_title):
    # Validate the user input
    if len(calendar_title) >= 256 or len(calendar_title) < 4:
        return {'success': False, 'error_msg': 'API returned error'}

    calendar_hash = sha1(calendar_title + \
        str(int(time.time())).encode('utf-8') + \
        'my_s3cr3t_s4lt'.encode('utf-8')).hexdigest()

    c = Calendar(hash=calendar_hash, title=calendar_title)
    db.session.add(c)
    db.session.commit()

    print(calendar_hash)
    return {'success': True, 'hash': calendar_hash}

@app.route('/calendar', methods=['GET', 'POST'])
def calendar_handler():
    if request.method == 'POST':
        calendar_title = request.get_json()['calendar_title'].encode('utf-8')
        return handle_calendar_post(calendar_title)
    else: # GET method for getting a calendar
        calendar_hash = request.args.get('calendar_hash')
        c = Calendar.query.get({'hash': calendar_hash})
        if c is not None:
            return {'success': True, 'title': c.title}
        else:
            return {'success': False, 'error_msg': 'Calendar Not Found'}

@app.route('/events', methods=['GET', 'POST'])
def events_handler():
    if request.method == 'POST':
        pass
    else: # GET method for getting events
        calendar_hash = request.args.get('calendar_hash')
        selected_date = request.args.get('selected_date')

        c = Calendar.query.get({'hash': calendar_hash})
        requested_date = datetime.fromtimestamp(int(selected_date) / 1000.0)

        start_of_requested_date = datetime.combine(requested_date.date(), time())
        next_day_of_requested_date = start_of_requested_date + timedelta(days=1)

        events = Event.query.filter(Event.start_time.between(
            start_of_requested_date.date(),
            next_day_of_requested_date.date())).filter(
                Event.calendar_hash == c.hash).all()

        events_list = []
        for event in events:
            events_list.append({
                'title': event.title,
                'description': event.description,
                'start_time': event.start_time.timestamp()*1000,
                'end_time': event.end_time.timestamp()*1000
            })

        return {'success': True, 'events': events_list}


import os
import logging
import shutil
import re
import subprocess

import snapcraft
from snapcraft.plugins import autotools

logger = logging.getLogger(__name__)

def _populate_options(options, properties, schema):
    schema_properties = schema.get('properties', {})
    for key in schema_properties:
        attr_name = key.replace('-', '_')
        default_value = schema_properties[key].get('default')
        attr_value = properties.get(key, default_value)
        setattr(options, attr_name, attr_value)

class PhpPlugin(autotools.AutotoolsPlugin):

    @classmethod
    def schema(cls):
        schema = super().schema()
        schema['properties']['extensions'] = {
            'type': 'array',
            'minitems': 1,
            'uniqueItems': True,
            'default': [],
            'items': {
                'type': 'object',
                'properties': {
                    'source': {
                        'type': 'string'
                    },
                    'source-type': {
                        'type': 'string'
                    },
                    'source-branch': {
                        'type': 'string'
                    },
                    'source-subdir': {
                        'type': 'string'
                    },
                    'configflags': {
                        'type': 'array',
                        'minitems': 1,
                        'uniqueItems': True,
                        'items': {
                            'type': 'string',
                        },
                        'default': [],
                    }
                }
            }
        }

        return schema

    def __init__(self, name, options, project):
        super().__init__(name, options, project)

        self.extensions_directory = os.path.join(self.partdir, 'extensions')

        class Options():
            pass

        self.extensions = []

        schema = self.schema()['properties']['extensions']['items']

        for index, extension in enumerate(self.options.extensions):
            options = Options()
            _populate_options(options, extension, schema)
            options.extension_directory = os.path.join(
                self.extensions_directory, 'extension-{}'.format(index))
            self.extensions.append(options)

    def pull(self):
        super().pull()

        # Now pull extensions
        if self.extensions:
            logger.info('Pulling PHP extensions...')

        for extension in self.extensions:
            extension_source_directory = os.path.join(
                extension.extension_directory, 'src')
            os.makedirs(extension_source_directory)
            snapcraft.sources.get(extension_source_directory, None, extension)

    def clean_pull(self):
        super().clean_pull()

        if os.path.exists(self.extensions_directory):
            shutil.rmtree(self.extensions_directory)

    def build(self):
        super().build()

        if self.extensions:
            logger.info('Building PHP extensions...')

        for extension in self.extensions:
            extension_source_directory = os.path.join(
                extension.extension_directory, 'src')
            extension_build_directory = os.path.join(
                extension.extension_directory, 'build')

            if os.path.exists(extension_build_directory):
                shutil.rmtree(extension_build_directory)

            shutil.copytree(extension_source_directory, extension_build_directory)

            self.run(['{}/phpize'.format(os.path.join(self.installdir, 'bin'))],
                     cwd=extension_build_directory)
            self.run(['./configure'] + extension.configflags,
                     cwd=extension_build_directory)
            self.run(['make', '-j{}'.format(
                self.project.parallel_build_count)],
                cwd=extension_build_directory)
            self.run(['make', 'install'], cwd=extension_build_directory)