import argparse
import os
import yaml

from dataset import DataCollatorCTCWithPadding
from datasets import load_from_disk
from model import CLAP
from tqdm.auto import tqdm
from transformers import Wav2Vec2Processor, BertTokenizer, get_scheduler
from utils import to_device
import pandas as pd
import torch
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel
from torch.nn.utils import clip_grad_norm_
from torch.optim import AdamW
from torch.utils.data import DataLoader, DistributedSampler
from torch.distributed import init_process_group
from torch.cuda.amp import GradScaler, autocast

def train(rank, config, num_gpus):
    device = torch.device('cuda:{:d}'.format(config['train']['device_ids'][rank]))
    print("----------------------\n Device used: {}\n----------------------".format(device))
    train_dataset= load_from_disk(os.path.join(config['data_path'], 'train_dataset'))
    test_dataset = load_from_disk(os.path.join(config['data_path'], 'dev_dataset'))
    # unused_features = ['wav_path', 'id', 'transcr', 'emotions', 'token_count', 'label']
    # train_dataset= load_from_disk(os.path.join(config['data_path'], 'train_dataset')).remove_columns(unused_features)
    # test_dataset = load_from_disk(os.path.join(config['data_path'], 'test_dataset')).remove_columns(unused_features)
    processor = Wav2Vec2Processor.from_pretrained(config['audio_model']['ckpt'])
    tokenizer = BertTokenizer.from_pretrained(config['language_model']['ckpt'])
    if num_gpus > 1:
        init_process_group(backend = config["dist_config"]['dist_backend'],
                           init_method = config["dist_config"]['dist_url'],
                           world_size = config['dist_config']['world_size'] * num_gpus,
                           rank = rank)
        data_sampler = DistributedSampler(train_dataset)
    else:
        data_sampler = None
    # Note: It is suggested to map the dataset to cuda devices during training, not in data data_collator
    data_collator = DataCollatorCTCWithPadding(processor = processor,
                                               tokenizer = tokenizer,
                                               padding = True)
    train_loader = DataLoader(train_dataset,
                              batch_size = config['train']['batch_size'],
                              sampler = data_sampler,
                              shuffle = True,
                              collate_fn = data_collator)
    test_loader = DataLoader(test_dataset,
                             batch_size = config['train']['batch_size'],
                             shuffle=False,
                             collate_fn=data_collator)
    log_step = config['train']['log_step']
    eval_step = config['train']['eval_step']
    save_step = config['train']['save_step']
    grad_accum = config['train']['gradient_accumulation']
    save_path = config['save_path']
    model = CLAP(config).to(device)
    scaler = GradScaler()
    if num_gpus > 1:
        model = DistributedDataParallel(model,
                                        device_ids = [config['train']['device_ids'][rank]],
                                        find_unused_parameters=True)
    optimizer = AdamW(model.parameters(), lr = config['train']['learning_rate'])
    lr_scheduler = get_scheduler("linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=config['train']['total_step'])
    # lr_scheduler = StepLR(optimizer, step_size = config['train']['lr_decay_step'], gamma = config['train']['lr_decay_rate'])
    if rank == 0:
        num_param = sum(param.numel() for param in model.parameters())
        os.makedirs(os.path.join(config['save_path'], 'checkpoints'), exist_ok = True)
        os.makedirs(os.path.join(config['save_path'], 'logs'), exist_ok = True)
        os.makedirs(os.path.join(config['save_path'], 'results'), exist_ok = True)
        with open(os.path.join(config['save_path'], 'logs', 'train_log.txt'), 'w') as f:
            f.write('The number of parameters of the model is {:d}.\n'.format(num_param))
        with open(os.path.join(config['save_path'], 'logs', 'test_log.txt'), 'w') as f:
            f.write('Start evaluation:\n')
        outer_bar = tqdm(range(config['train']['total_step']))

    train = True
    model.train()
    epoch, step = 1, 1
    loss_train, accum_step = 0, 0
    max_mem = 0
    while train:
        if rank == 0:
            inner_bar = tqdm(total=len(train_loader), desc="Epoch {}".format(epoch), position=1)
        if num_gpus > 1:
            data_sampler.set_epoch(epoch)
        for batch in train_loader:
            if train == False:
                break
            batch = to_device(batch, device)
            try:
                with autocast():
                    outputs = model(input_ids = batch['input_ids'],
                                    l_attn_mask = batch['l_attn_mask'],
                                    input_values = batch['input_values'],
                                    a_attn_mask = batch['a_attn_mask'])
                    mem = torch.cuda.memory_allocated(device = device) / 1024**2
                    if mem>max_mem:
                        max_mem = mem
                    loss = outputs.loss
            except RuntimeError as e:
                print(e)
                print('Shape of wav inputs: ', batch['input_values'].shape)
                breakpoint()
            scaler.scale(loss / grad_accum).backward()
            if step % grad_accum == 0:
                # clip_grad_norm_(model.parameters(), 1)

                scaler.step(optimizer)
                scaler.update()
                # optimizer.step()
                lr_scheduler.step()
                optimizer.zero_grad()
                model.zero_grad()

            if rank == 0:
                loss_train += loss.item()
                accum_step += 1
                if step % log_step == 0:
                    mem = torch.cuda.memory_allocated(device = device) / 1024**2
                    message = f"Step: {step}, Training loss: {loss_train/(accum_step+1)}, Memory allocated/Cur max: {mem}/{max_mem}\n"
                    outer_bar.write(message, end='')
                    with open(os.path.join(config['save_path'], 'logs', 'train_log.txt'), 'a') as f:
                        f.write(message)
                    loss_train, accum_step = 0, 0
                if step % eval_step == 0 or step == 1:
                    model.eval()
                    eval_bar = tqdm(total=len(test_loader), desc="Evaluating", position=2)
                    with torch.no_grad():
                        loss_test = 0
                        # acc_h, acc_v = 0, 0
                        acc = 0
                        for batch in test_loader:
                            batch = to_device(batch, device)
                            outputs = model(input_ids = batch['input_ids'],
                                            l_attn_mask = batch['l_attn_mask'],
                                            input_values = batch['input_values'],
                                            a_attn_mask = batch['a_attn_mask'])
                            loss, logits = outputs.loss, outputs.logits
                            logits_v, logits_h = torch.argmax(logits, dim = 0), torch.argmax(logits, dim = 1)
                            labels = torch.arange(batch['input_ids'].shape[0]).to(device)

                            acc_h = torch.sum(logits_h == labels)
                            acc_v = torch.sum(logits_v == labels)
                            acc = torch.sum(acc_h == acc_v)
                            loss_test += loss.item()
                            eval_bar.update(1)
                    message = f"Step: {step}, Testing loss: {loss_test/len(test_loader)}"
                    message_acc = f", Testing accuracy: {acc/len(test_dataset)}\n"
                    outer_bar.write(message + message_acc, end='')
                    with open(os.path.join(config['save_path'], 'logs', 'test_log.txt'), 'a') as f:
                        f.write(message + message_acc)
                    model.train()
                if step % save_step == 0:
                    os.makedirs(f'{save_path}/checkpoints', exist_ok = True)
                    torch.save({'model': model.state_dict(),
                                'optimizer': optimizer.state_dict()},
                            os.path.join(save_path, 'checkpoints', f'CLAP-{step}.pth.tar'))
            if step == config['train']['total_step']:
                train = False
                break
            step += 1
            if rank == 0:
                outer_bar.update(1)
                inner_bar.update(1)
        epoch += 1

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--restore_step', type = int, required=False)
    config = yaml.load(open('config.yaml', 'r'), Loader=yaml.FullLoader)
    num_gpus = len(config['train']['device_ids'])
    if num_gpus > 1:

        mp.spawn(train, nprocs = num_gpus, args = (config, num_gpus))
    else:
        train(0, config, num_gpus)


from lyricsgenius import Genius
from secret_keys import *
from pathlib import Path
from tqdm import tqdm
import os

# this is the method used by the genius api to handle special characters, but it's applied to the whole string,
# so I cannot divide the files into subdirectories as I would, I keep this to apply it to titles instead
# I modified it to replace whitespaces with '_' because I like it more
def sanitize_filename(f):
    """Removes invalid characters from file name.
    Args:
        f (:obj:`str`): file name to sanitize.
    Returns:
        :obj:`str`: sanitized file name including only alphanumeric
        characters, spaces, dots or underlines.
    """
    keepchars = (" ", ".", "_")
    sanitized = "".join(c for c in f if c.isalnum() or c in keepchars).rstrip()
    return sanitized.replace(' ', '_')

artists_list = ['Aiello',
                'Ariete',
                'Bartolini',
                'Baustelle',
                'Brunori',
                'Calabi',
                'Calcutta',
                'I Cani',
                'Cannella',
                'Canova',
                'Carl Brave',
                'Ceri',
                'Chiello',
                'Cimini',
                'Clavdio',
                'Coez',
                'Colapesce',
                'Coma cose',
                'COMETE',
                'Cosmo',
                'Dente',
                'Dimartino',
                'Diodato',
                'Dutch Nazari',
                'Eugenio in via di Gioia',
                'Ex otago',
                'Fast Animals and slow kids',
                'Frah Quintale',
                'Franco 126',
                'Fulminacci',
                'Galeffi',
                'Gazzelle',
                'Ghemon',
                'Gio Evan',
                'Giovanni Truppi',
                'Giorgio Poi',
                'I miei migliori complimenti',
                'I segreti',
                'La municipal',
                'Legno',
                'Lemandorle',
                'Leo Pari',
                'Levante',
                'Lo Stato Sociale',
                'Officina della camomilla',
                'Luci della centrale elettrica',
                'Lucio Corsi',
                'Mameli',
                'Management',
                'Manfredi',
                'Mannarino',
                'Margherita Vicario',
                'Mecna',
                'Merlot',
                'Michele Merlo',
                'Mobrici',
                'Motta',
                'Mox',
                'Murubutu',
                'Myss keta',
                'Niccolò Fabi',
                'Peter White',
                'Pinguini tattici nucleari',
                'Pop x',
                'Postino',
                'Psicologi',
                'rovere',
                'Seltsam',
                'Scarda',
                'Scrima',
                'Subsonica',
                'Tananai',
                'Thegiornalisti',
                'The Zen circus',
                'Tre Allegri Ragazzi Morti',
                'Venerus',
                'Viito',
                'Willie peyote',
                'Labadessa',
                'Ministri']

lyrics_dir = Path('lyrics')
done = os.listdir(lyrics_dir)
for i in done:
    i = i.replace('_', ' ')
    artists_list.remove(i)

genius = Genius(access_token=CLIENT_ACCESS_TOKEN,
                response_format='plain',
                skip_non_songs=True,
                remove_section_headers=False,
                excluded_terms=["(Remix)", "(Live)"],
                verbose=False,
                retries=1)

artist_progr = tqdm(artists_list, total=len(artists_list))
for artist in artist_progr:
    artist_progr.set_description(f'Scraping all lyrics from {artist}...')
    subdir = artist.replace(' ', '_')
    artist_dir = lyrics_dir / subdir
    if not os.path.isdir(artist_dir):
        os.mkdir(artist_dir)
        genius_artist = genius.search_artist(artist_name=artist)
        songs = genius_artist.songs
        for song in tqdm(songs, desc=f'Scraping all lyrics from {artist}...', total=len(songs), leave=False):
            title = sanitize_filename(song.title) + '.txt'
            fname = artist_dir / title
            song.to_text(filename=fname, sanitize=False)


class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        a={}
        count=0
        for i in nums :
            if i in a:
                return [a[i],count]
            a[target-i]=count
            count+=1

sol = Solution()
print(sol.twoSum([2, 7, 11, 15],9))
print(sol.twoSum([3,3],6))


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 28 14:20:01 2018

@author: bsingh46

"""

import nltk
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
import numpy as np
import random
import pickle
from collections import Counter
import re
from nltk.corpus import stopwords
#import enchant

### lemmatizer can give a memory error - ran outta ram
lemmatizer=WordNetLemmatizer()
hm_lines=100000000

def clean_str(string):

    string = re.sub(r"[^A-Za-z]", " ", string)
    string = re.sub(r"\'s", " \'s", string)
    string = re.sub(r"\'ve", " \'ve", string)
    string = re.sub(r"n\'t", " n\'t", string)
    string = re.sub(r"\'re", " \'re", string)
    string = re.sub(r"\'d", " \'d", string)
    string = re.sub(r"\'ll", " \'ll", string)
    string = re.sub(r",", " , ", string)
    string = re.sub(r"!", " ! ", string)
    string = re.sub(r"\(", " \( ", string)
    string = re.sub(r"\)", " \) ", string)
    string = re.sub(r"\?", " \? ", string)
    string = re.sub(r"\s{2,}", " ", string)
    return string.strip().lower()

def create_lexicon(a, b):
    lexicon=[]
    for fi in [a, b]:
        with open(fi,'r',encoding="utf8",errors='ignore') as f:
            content=f.readlines()
            #print(content)
            for l in content[:hm_lines]:
                l=clean_str(l)
                all_words=word_tokenize(l)
                ##print(all_words)
                lexicon+=list(all_words)
    ## stemmer
    lexicon=[lemmatizer.lemmatize(i) for i in lexicon]
    #lexicon=[w for w in lexicon if w in nltk.corpus.words.words()]
    ## gives a dictionary of word occurence {'the':7,"you":9}
    #print(lexicon)
    word_counts=Counter(lexicon)
    temp=[]
    ## dont want sparse words and very common words such as articles
    for w in word_counts:
        if word_counts[w] > 5:
            temp.append(w)
    stopset = set(stopwords.words('english'))
    temp=[i for i in temp if i not in stopset]
    temp=[i for i in temp if len(i)>3]
    #temp=[w for w in temp if w in nltk.corpus.words.words()]
    #print(len(temp))
    #temp=[clean_str(i) for i in temp]
    #print(len(temp))
    #print(temp)
    with open('lexicon.pickle','wb') as f:
        pickle.dump(temp,f)
    return temp

## classifying feature sets
def sample_handling(sample,lexicon, classification):
    featureset=[]

    with open(sample,'r',encoding="utf8",errors='ignore') as f:
        content=f.readlines()
        for l in content[:hm_lines]:
            current_words=word_tokenize(l.lower())
            current_words=[lemmatizer.lemmatize(i) for i in current_words]
            ##print(current_words)
            features=np.zeros(len(lexicon))
            for word in current_words:
                if word.lower() in lexicon:
                    ##print(word)
                    index_value=lexicon.index(word.lower())
                    ##print(index_value)
                    features[index_value]+=1
            features=list(features)
            featureset.append([features,classification])
            ##[[[1 0 0 0 1 0 0 1],[0,1]],[[0 0 1 0 1 1],[1,0]]]
            ## [0,1] -> negative sentiment
            ## [1,0] -> positive sentiment

        return featureset

def create_feature_set_and_labels(a, b,test_size=0.1):
    lexicon=create_lexicon(a, b)
    features=[]
    features+=sample_handling('negative.txt',lexicon,[1,0])
    features+=sample_handling('positive.txt',lexicon,[0,1])

    ### we need to shuffle features for the neural network
    ##
    random.shuffle(features)
    features=np.array(features)
    testing_size=int(test_size*len(features))
    ###[[features,label],[features,label]]
    ### feature[:,0] -> gives us the 0th element that is the features from the above array
    train_x=list(features[:,0][:-testing_size])
    train_y=list(features[:,1][:-testing_size])

    test_x=list(features[:,0][-testing_size:])
    test_y=list(features[:,1][-testing_size:])

    return train_x,train_y,test_x, test_y

##aes=551 ; crime=51 (); sde=3179; traffic=701; walk=148 (500)
if __name__=='__main__':
    train_x,train_y,test_x, test_y=create_feature_set_and_labels('negative.txt','positive.txt')
    with open('sentiment_set.pickle','wb') as f:
        pickle.dump([train_x,train_y,test_x,test_y],f)
        ### why we use pickle https://pythontips.com/2013/08/02/what-is-pickle-in-python/

# =============================================================================
# import string
# s = '... some string with punctuation ...'
# s = s.translate(None, string.punctuation)
#
# import enchant
# d = enchant.Dict("en_US")
# d.check("Hello")
# d.check("Helo")
# d.suggest("Helo")
# =============================================================================


inputt = list(map(int, input().split()))
dicta = dict()
tic = 0
for i in inputt:
    dictkey = list(dicta.keys())
    for j in range(len(dictkey)):
        if i == dictkey[j]:
            dicta[dictkey[j]] += 1
            tic = 1
            break
    if tic == 0:
        dicta[i] = 1
    tic = 0
dictkey = list(dicta.keys())
if len(dictkey) == 3:
    result = max(inputt) * 100
elif len(dictkey) == 2:
    temp = 0
    for i in dictkey:
        if temp < dicta[i]:
            temp = dicta[i]
            r = i
    result = 1000 + 100*r
else:
    result = 10000 + inputt[0] * 1000
print(result)


from pyb import SPI, Pin, UART, LED, delay

CS = Pin("PE3", Pin.OUT_PP)
SPI_1 = SPI(
    1,  # PA5, PA6, PA7
    SPI.MASTER,
    baudrate=50000,
    polarity=0,
    phase=0,
    # firstbit=SPI.MSB,
    # crc=None,
)

uart = UART(2, 115200)
led_1 = pyb.LED(1)
led_2 = pyb.LED(2)
led_3 = pyb.LED(3)
led_4 = pyb.LED(4)

def read_reg(addr):
    CS.low()
    SPI_1.send(addr | 0x80)  # 0x80 pour mettre le R/W à 1
    tab_values = SPI_1.recv(1)  # je lis une liste de 1 octet
    CS.high()
    return tab_values[0]

def write_reg(addr, value):
    CS.low()
    SPI_1.send(addr | 0x00)  # write
    SPI_1.send(value)
    CS.high()

def convert_value(high, low):
    value = (high << 8) | low
    if value & (1 << 15):
        # negative number
        value = value - (1 << 16)
    return value

def read_acceleration(base_addr):
    low = read_reg(base_addr)
    high = read_reg(base_addr + 1)
    return convert_value(high, low)

addr_who_am_i = 0x0F
# uart.write(read_reg(addr_who_am_i))
addr_ctrl_reg1 = 0x20
write_reg(addr_ctrl_reg1, 0x77)
while True:
    x_accel = read_acceleration(0x29)
    y_accel = read_acceleration(0x2B)
    z_accel = read_acceleration(0x2D)
    value = str("Value X : {}, Value Y : {}, Value Z : {} \n".format(x_accel, y_accel, z_accel))
    uart.write(value)
    print("{:20}, {:20}, {:20}".format(x_accel, y_accel, z_accel))

    if x_accel < 247:
        led_3.on()
        led_4.off()

    elif x_accel == 247:
        led_4.on()
        led_3.on()
        delay(100)
        led_4.off()
        led_3.off()

    else:
        led_4.on()
        led_3.off()

    if y_accel < 7:
        led_2.on()
        led_1.off()

    elif y_accel == 7:
        led_2.on()
        led_1.on()
        delay(100)
        led_2.off()
        led_1.off()

    else:
        led_1.on()
        led_2.off()


import numpy as np

def map_feature(x1, x2):
    degree = 6

    x1 = x1.reshape((x1.size, 1))
    x2 = x2.reshape((x2.size, 1))
    result = np.ones(x1[:, 0].shape)

    for i in range(1, degree + 1):
        for j in range(0, i + 1):
            result = np.c_[result, (x1**(i-j)) * (x2**j)]

    return result


from cv2 import rotate
import numpy as np
import cv2 as cv

img = cv.imread('C:/Users/DELL/Pictures/Screenshots/CM.png')
def translation(im , x , y):
    translation_matrix = np.float32([[1,0,x], [0,1,y]])
    dimensions = (im.shape[1], im.shape[0])
    return cv.warpAffine(im, translation_matrix, dimensions)

#cv.imshow('Rotated Conor', translation(img, 100 , -50))
ro = rotate(img , -90)
flipped = cv.flip(img , 0)
cv.imshow('Flipper', flipped)
cv.waitKey(0)


from manimlib.imports import *

class Linear(GraphScene):

    CONFIG = {
    "x_min": -5,
    "x_max": 5,
    "y_min": -5,
    "y_max": 5,
    "graph_origin": ORIGIN,
    "x_labeled_nums": list(range(-5, 6)),
    "y_labeled_nums": list(range(-5, 6)),
    "x_axis_width": 7,
    "y_axis_height": 7,
    }

    def construct(self):

        XTD = self.x_axis_width/(self.x_max- self.x_min)
        YTD = self.y_axis_height/(self.y_max- self.y_min)

        self.setup_axes(animate = True)
        heading = TextMobject(r"$T(x,y) = T(x+2y,x-y)$")
        heading.move_to(UP*3+LEFT*4)
        heading.scale(0.7)
        self.play(Write(heading))
        self.wait()

        before = TextMobject("Before Linear Transformation")
        before.set_color(ORANGE)
        before.move_to(3*UP+4*RIGHT)
        before.scale(0.75)
        dot1 = Dot().shift(self.graph_origin+1*XTD*RIGHT+1*YTD*UP)
        dot2 = Dot().shift(self.graph_origin+2*XTD*RIGHT+1*YTD*UP)
        dot1.set_color(ORANGE)
        dot2.set_color(ORANGE)
        p1 = TextMobject(r"$P_1$")
        p1.scale(0.75)
        p1.set_color(ORANGE)
        p1.move_to(self.graph_origin+1*XTD*RIGHT+1.5*YTD*UP)
        p2 = TextMobject(r"$P_2$")
        p2.set_color(ORANGE)
        p2.scale(0.75)
        p2.move_to(self.graph_origin+2*XTD*RIGHT+1.5*YTD*UP)

        after = TextMobject("After applying Linear Transformation")
        after.set_color(YELLOW)
        after.move_to(3*UP+4.5*RIGHT)
        after.scale(0.5)
        dot3 = Dot().shift(self.graph_origin+3*XTD*RIGHT+0*YTD*UP)
        dot4 = Dot().shift(self.graph_origin+4*XTD*RIGHT+1*YTD*UP)
        dot3.set_color(YELLOW)
        dot4.set_color(YELLOW)
        p3 = TextMobject(r"$T(P_1)$")
        p3.scale(0.7)
        p3.set_color(YELLOW)
        p3.move_to(self.graph_origin+3*XTD*RIGHT-1.1*YTD*UP)
        p4 = TextMobject(r"$T(P_2)$")
        p4.scale(0.7)
        p4.set_color(YELLOW)
        p4.move_to(self.graph_origin+4*XTD*RIGHT+1.5*YTD*UP)

        self.play(Write(before), ShowCreation(dot1), ShowCreation(dot2),Write(p1), Write(p2))
        self.wait(3)
        self.play(Transform(before,after), Transform(dot1,dot3), Transform(dot2,dot4), Transform(p2,p4), Transform(p1,p3))
        self.wait(3)

class withgrid(LinearTransformationScene):
    def construct(self):

        heading = TextMobject(r"Now, imagine this happening for all the points")
        heading.scale(0.5)
        heading.move_to(UP*2.5+LEFT*4)
        self.play(Write(heading))
        self.wait()

        before = TextMobject("Before Linear Transformation")
        before.set_color(ORANGE)
        before.move_to(3.5*UP+4*RIGHT)
        before.scale(0.75)
        dot1 = Dot().shift(1*RIGHT+1*UP)
        dot2 = Dot().shift(2*RIGHT+1*UP)
        dot1.set_color(ORANGE)
        dot2.set_color(ORANGE)

        dot1_c = Dot(radius = 0.05).shift(1*RIGHT+1*UP)
        dot2_c = Dot(radius = 0.05).shift(2*RIGHT+1*UP)
        dot1_c.set_color(YELLOW)
        dot2_c.set_color(YELLOW)
        self.add_transformable_mobject(dot1_c)
        self.add_transformable_mobject(dot2_c)

        p1 = TextMobject(r"$P_1$")
        p1.scale(0.75)
        p1.set_color(ORANGE)
        p1.move_to(1*RIGHT+1.5*UP)
        p2 = TextMobject(r"$P_2$")
        p2.scale(0.75)
        p2.set_color(ORANGE)
        p2.move_to(2*RIGHT+1.5*UP)

        after = TextMobject("After applying Linear Transformation")
        after.set_color(YELLOW)
        after.move_to(3.5*UP+3.5*RIGHT)
        after.scale(0.75)
        dot3 = Dot().shift(3*RIGHT+0*UP)
        dot4 = Dot().shift(4*RIGHT+1*UP)
        dot3.set_color(YELLOW)
        dot4.set_color(YELLOW)
        p3 = TextMobject(r"$T(P_1)$")
        p3.scale(0.75)
        p3.set_color(YELLOW)
        p3.move_to(3*RIGHT-0.6*UP)
        p4 = TextMobject(r"$T(P_2)$")
        p4.scale(0.75)
        p4.set_color(YELLOW)
        p4.move_to(4*RIGHT+1.5*UP)

        self.play(Write(before), ShowCreation(dot1), ShowCreation(dot2),Write(p1), Write(p2))
        self.wait(3)
        matrix = [[1,2],[1,-1]]
        dot1.set_color(GREY)
        dot2.set_color(GREY)
        self.play(FadeIn(dot1),FadeIn(dot2))
        self.apply_matrix(matrix)
        self.play(Transform(before,after), Transform(p2,p4), Transform(p1,p3))
        self.play(Transform(before,after))
        self.wait(3)

        ending = TextMobject(r"$T(\left[\begin{array}{c}x \\ y\end{array}\right]) = \left[\begin{array}{c} x+2y \\ x-y\end{array}\right]$")
        ending.move_to(UP*2+LEFT*4)
        self.play(Transform(heading,ending))
        self.wait()

from manimlib.imports import *
class ThreeDExplanation(ThreeDScene):

    def construct(self):

        text = TextMobject(r"$T(x,y) = (x+y,x-y,x+2y)$")
        text.scale(0.75)
        text.move_to(UP*2.5+LEFT*4)
        text.move_to(-UP*3+LEFT*4)
        self.add_fixed_in_frame_mobjects(text)
        self.play(Write(text))
        self.wait()

        before = TextMobject("Before Linear Transformation")
        self.add_fixed_in_frame_mobjects(before)
        before.set_color(ORANGE)
        before.move_to(3.5*UP+4*RIGHT)
        before.scale(0.75)

        p1 = TextMobject(r"$P_1$")
        p2 = TextMobject(r"$P_2$")
        p3 = TextMobject(r"$P_3$")
        p1.scale(0.75)
        p2.scale(0.75)
        p3.scale(0.75)
        dot1 = Dot().shift(1*RIGHT+1*UP)
        dot2 = Dot().shift(2*RIGHT+1*UP)
        dot3 = Dot().shift(1*RIGHT+1*DOWN)
        dot1.set_color(ORANGE)
        dot2.set_color(ORANGE)
        dot3.set_color(ORANGE)
        self.play(ShowCreation(before))

        p1.move_to(1*RIGHT+1*UP+[0,0,0.5])
        p2.move_to(2*RIGHT+1*UP+[0,0,0.5])
        p3.move_to(1*RIGHT-1*UP+[0,0,0.5])

        dot1_c = Dot(radius = 0.05).shift(1*RIGHT+1*UP)
        dot2_c = Dot(radius = 0.05).shift(0*RIGHT+2*UP)
        dot3_c = Dot(radius = 0.05).shift(1*RIGHT-1*UP)
        dot1_c.set_color(YELLOW)
        dot2_c.set_color(YELLOW)
        dot3_c.set_color(YELLOW)

        axes = ThreeDAxes(x_min = -7,x_max=7,y_min=-4,y_max=4,z_min=-4,z_max=4)
        self.play(ShowCreation(axes))
        self.move_camera(distance = 100, phi=30*DEGREES,theta=45*DEGREES,run_time=3)

        self.begin_ambient_camera_rotation(rate=0.3)
        self.wait(1)
        self.stop_ambient_camera_rotation()

        plane = NumberPlane()
        self.add_fixed_orientation_mobjects(p1)
        self.add_fixed_orientation_mobjects(p2)
        self.add_fixed_orientation_mobjects(p3)
        self.play(ShowCreation(dot1),ShowCreation(dot3),ShowCreation(dot2),ShowCreation(plane))

        self.play(FadeOut(before))
        after = TextMobject("After applying Linear Transformation")
        self.add_fixed_in_frame_mobjects(after)
        after.set_color(YELLOW)
        after.move_to(3.5*UP+3.5*RIGHT)
        after.scale(0.75)

        self.play(FadeOut(p1),FadeOut(p2),FadeOut(p3))
        matrix = [[1,1],[1,-1],[2,1]]
        self.play(FadeOut(dot1),FadeOut(dot2),FadeOut(dot3),ApplyMethod(plane.apply_matrix,matrix),ApplyMethod(dot1_c.apply_matrix,matrix),ApplyMethod(dot3_c.apply_matrix,matrix),ApplyMethod(dot2_c.apply_matrix,matrix))

        p4 = TextMobject(r"$T(P_1)$")
        p5 = TextMobject(r"$T(P_2)$")
        p6 = TextMobject(r"$T(P_3)$")
        p4.scale(0.75)
        p5.scale(0.75)
        p6.scale(0.75)
        p4.move_to(2*RIGHT+0*UP+[0,0,3.5])
        p5.move_to(2*RIGHT-2*UP+[0,0,2.5])
        p6.move_to(0*RIGHT+2*UP+[0,0,1.5])
        self.add_fixed_orientation_mobjects(p5)
        self.add_fixed_orientation_mobjects(p4)
        self.add_fixed_orientation_mobjects(p6)

        self.begin_ambient_camera_rotation(rate=0.3)
        self.wait(3)
        self.stop_ambient_camera_rotation()

        ending = TextMobject(r"$T(\left[\begin{array}{c}x \\ y\end{array}\right])$ = ",r"$\left[\begin{array}{c} x+y \\ x-y\\ x+2y \end{array}\right]$") #\begin{array}{c} x+y \\ x-y -- \\ x+2y -- \end{array}\right]$")
        ending.scale(0.75)
        ending.move_to(-UP*3+LEFT*4)
        self.add_fixed_in_frame_mobjects(ending)
        self.play(FadeOut(text),Write(ending))

        self.play(FadeOut(plane))
        self.wait(2)

        self.begin_ambient_camera_rotation(rate=0.3)
        self.wait(8)
        self.stop_ambient_camera_rotation()


from monolith.compat import unicode
from monolith.cli.base import BaseCommand

COMPLETION_TEMPLATE = '''
# %(prog_name)s bash completion start
_%(prog_name)s_completion()
{
    COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
                   COMP_CWORD=$COMP_CWORD \\
                   %(ENV_VAR_NAME)s=1 $1 ) )
}
complete -o default -F _%(prog_name)s_completion %(prog_name)s
# %(prog_name)s bash completion end

'''

class CompletionCommand(BaseCommand):
    help = ''.join((
        'Prints out shell snippet that once evaluated would allow '
        'this command utility to use completion abilities.',
    ))
    template = COMPLETION_TEMPLATE

    def get_env_var_name(self):
        return '_'.join((self.prog_name.upper(), 'AUTO_COMPLETE'))

    def get_completion_snippet(self):
        return self.template % {'prog_name': self.prog_name,
            'ENV_VAR_NAME': self.get_env_var_name()}

    def handle(self, namespace):
        self.stdout.write(unicode(self.get_completion_snippet()))

    def post_register(self, manager):
        manager.completion = True
        manager.completion_env_var_name = self.get_env_var_name()