import numpy as np
import netket as nk
import os
import time

from src.util.models import build_model_netket
from src.objectives.max_cut import MaxCutEnergy
from src.objectives.spinglass import SpinGlassEnergy
from src.objectives.SKspinglass import SKSpinGlassEnergy

from src.util.helper import param_reader

###############################################################################
################################ NetKet #######################################
###############################################################################
def run_netket(cf, data, seed, params = np.array([])):
    # build objective
    if cf.pb_type == "maxcut":
        energy = MaxCutEnergy(cf)
        J = data # laplacian
        hamiltonian,graph,hilbert = energy.laplacian_to_hamiltonian(J)
    elif cf.pb_type == "spinglass":
        energy = SpinGlassEnergy(cf)
        J = data
        hamiltonian,graph,hilbert = energy.laplacian_to_hamiltonian(J)
    elif cf.pb_type == 'spinglass-SK':
        energy = SKSpinGlassEnergy(cf)
        J = data
        hamiltonian, graph, hilbert = energy.laplacian_to_hamiltonian(J)

    # build model
    # Model initializes with random parameters if no parameters are provided.
    model = build_model_netket(cf, hilbert)
    if params.size == 0:
        model.init_random_parameters(seed=seed, sigma=cf.param_init)
        # During meta-training, run_netket saves random initialization to parameter text file for the main file to use.
        if cf.metatrain:
            param_reader(cf, model.parameters)
    elif model.n_par != params.size:
        raise Exception('Parameter array size incompatible with model.')
    else:
        model.parameters = params

    # Exact Sampler is used for systems with less than 16 nodes.
    if int(np.sqrt(J.shape[0])) <= 3:
        sampler = nk.sampler.ExactSampler(machine=model)
    else:
        sampler = nk.sampler.MetropolisLocal(machine=model)

    # build optimizer
    if cf.optimizer == "adadelta":
        op = nk.optimizer.AdaDelta()
    elif cf.optimizer == "adagrad":
        op = nk.optimizer.AdaGrad(learning_rate=cf.learning_rate)
    elif cf.optimizer == "adamax":
        op = nk.optimizer.AdaMax(alpha=cf.learning_rate)
    elif cf.optimizer == "momentum":
        op = nk.optimizer.Momentum(learning_rate=cf.learning_rate)
    elif cf.optimizer == "rmsprop":
        op = nk.optimizer.RmsProp(learning_rate=cf.learning_rate)
    elif cf.optimizer == "sgd":
        op = nk.optimizer.Sgd(learning_rate=cf.learning_rate, decay_factor=cf.decay_factor)

    if cf.use_sr:
        method = "Sr"
    else:
        method = "Gd"

    # build algorithm
    gs = nk.variational.Vmc(
        hamiltonian=hamiltonian,
        sampler=sampler,
        method=method,
        optimizer=op,
        n_samples=cf.batch_size,
        use_iterative=cf.use_iterative,
        use_cholesky=cf.use_cholesky,
        diag_shift=0.1)

    # run
    start_time = time.time()
    if cf.metatrain and cf.metalearner == 'iMAML':
        # Early-stopping criterion for iMAML: gs.iter causes optimization to occur one step at a time.
        history = []
        for step in gs.iter(10,1):
            obs = gs.get_observable_stats()
            history.append(obs['Energy'].mean)
            if step >= 10:
                history.pop(0)

            print(obs['Energy'].mean)
        print(history)
        print(sum(history))

    else:
        gs.run(output_prefix=os.path.join(cf.dir,"result"), n_iter=cf.num_of_iterations, save_params_every=cf.num_of_iterations)

    end_time = time.time()
    # type(gs).__name__
    result = gs.get_observable_stats()
    params = model.parameters

    #Exact solver performs sanity check on small problem sizes (at most 16 nodes)
    if int(np.sqrt(J.shape[0])) <= 4:
        res = nk.exact.lanczos_ed(hamiltonian, first_n=1, compute_eigenvectors=False)
        exact_score = res.eigenvalues[0]
    else:
        exact_score = 'N/A'

    score = result['Energy']
    time_elapsed = end_time - start_time
    # exp_name,_,_ = (cf.dir).partition('-date')
    exp_name = cf.framework + str(cf.input_size)
    return exp_name, score, time_elapsed, exact_score, params
###############################################################################
###############################################################################
###############################################################################


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

'''
@File       : test.py
@Author     : HW Shen
@Date       : 2020/8/17
@Desc       :
第一题： 对几个硬盘按照内存大小排序
输入： 第一行为 硬盘数量，后面每行为 每个硬盘的大小 (单位是M,G,T的组合形势)， eg. 3M2G, 1024M, 5G12M, 2T15M
输出： 按从小到大的顺序输出 (注意同等大小的硬盘（eg.1024M 和 1G），需要保序输出）
'''

while True:
    try:
        comp_li = []
        for _ in range(int(input())):
            string = input()
            a = ""
            total_v = 0
            for ch in string:
                if ch.isdigit():
                   a += ch
                else:
                    if ch == "T":
                        total_v += int(a)*1024*1024
                    elif ch == "G":
                        total_v += int(a)*1024
                    elif ch == "M":
                        total_v += int(a)
                    a = ""
            comp_li.append([string, total_v])
        print("comp_li: ", comp_li)
        items = sorted(comp_li, key=lambda x: x[1])
        print("items:", items)
        for item in items:
            print(item[0])
    except:
        break

if __name__ == '__main__':
    print("1".isdigit())

    # import sys
    # # 读取第一行的n
    # n = int(sys.stdin.readline().strip())
    # ans = 0
    # for i in range(n):
    #     # 读取每一行
    #     line = sys.stdin.readline().strip()
    #     # 把每一行的数字分隔后转化成int列表
    #     values = list(map(int, line.split()))
    #     for v in values:
    #         ans += v
    # print(ans)


import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc

alerts = dbc.FormGroup([
    html.Br(),
    dbc.Alert(
        "Incident successfully classified !!!",
        id="alert_ok",
        dismissable=True,
        is_open=False,
    ),

    dbc.Alert(
        "Please provide Incident title and summary",
        id="alert_err",
        color="danger",
        dismissable=True,
        is_open=False,
    ),
])

title_input = dbc.FormGroup(
    [
        dbc.Label("Incident Title", html_for="txt_title"),
        dbc.Input(type="text", id="txt_title",autoComplete='off',
                  placeholder="Enter short description incident.", bs_size="sm"),
    ]
)

summary_input = dbc.FormGroup(
    [
        dbc.Label("Incident Summary", html_for="txt_summary"),
        dbc.Textarea(bs_size="sm", id='txt_summary',
                     placeholder="Enter detailed description about the incident", rows=12),
    ]
)

buttons = dbc.FormGroup([
    dbc.Button("Predict", id='btn_predict', outline=True,
               color="primary", className="mr-2"),
    dbc.Button("Reset", id='btn_reset', outline=True,
               color="secondary", className="mr-1", href='/predict', external_link=True),
])

model_list = dbc.FormGroup(
    [
        dbc.Label("Predict With", html_for="dd_model"),
        dcc.Dropdown(
            id="dd_model",
            options=[
                {"label": "All Models", "value": 0},
                {"label": "BERT", "value": 1},
                {"label": "RoBERTa", "value": 2},
                {"label": "DistilBERT", "value": 3},
                {"label": "Electra", "value": 4},
                {"label": "XLNet", "value": 5},
            ],
            value=1, searchable=False, clearable=False
        ),
    ]
)

content_area = dbc.FormGroup([html.Br(),], id="div_content")

form = dbc.Form([alerts, title_input, summary_input,
                 model_list, buttons, content_area])


import pandas as pd
from recipe_analyser.recipe import Recipe
from recipe_analyser.recipe_analyser import RecipeAnalyser

def main() -> None:
    """
    loads and parses the included recipes.csv file. Consider this an example.
    """
    recipes: list[Recipe] = load_recipes()
    analysed_recipes: list[Recipe] = analyse_recipes(recipes)
    save_recipes_to_csv(analysed_recipes)

def load_recipes() -> list[Recipe]:
    """
    Loads all recipes from the included recipes.csv file into a pandas
    dataframe. Each row is transformed into a Recipe of type TypedDict.

    Returns:
        list[Recipe]: List of all recipes in the recipes.csv file
    """
    recipe_df: pd.DataFrame = pd.read_csv("../recipes-nutriscore/recipes_test.csv")
    recipe_list: list[Recipe] = []

    # TODO: df.iterrows() should be avoided in favour of vectorization for
    #       traversing a dataframe due to performance issues. However,
    #       since the included recipes.csv file only has 248 rows, this
    #       doesn't matter. Switch to vectorization for bigger datasets.
    for index, row in recipe_df.iterrows():
        try:
            id: int = int(row[0])
            author: str = row[1]
            followers: int = int(row[2])
            date: str = row[3]
            media: str = row[4]
            likes: int = int(row[5])
            comments: int = int(row[6])
            ingredient_list: list[str] = __convert_tolist(row[7])
            servings: int = int(row[8])

            recipe: Recipe = Recipe(
                id=id,
                author=author,
                followers=followers,
                date=date,
                media=media,
                likes=likes,
                comments=comments,
                ingredient_list=ingredient_list,
                servings=servings,
            )
            recipe_list.append(recipe)
        except TypeError:
            print(f"TypeError occured at {index}")
            raise
    return recipe_list

def analyse_recipes(recipes: list[Recipe]) -> list[Recipe]:
    """
    Analyses nutrients of all recipes in a list

    Arguments:
        recipes {list[Recipe]} -- list of Recipes

    Returns:
        list[Recipe] -- List of analysed Recipes
    """
    analysed_recipes: list[Recipe] = []
    for recipe in recipes:
        recipe_analyser: RecipeAnalyser = RecipeAnalyser(recipe)
        analysed_recipe: Recipe = Recipe(
            id=recipe["id"],
            author=recipe["author"],
            followers=recipe["followers"],
            date=recipe["date"],
            media=recipe["media"],
            likes=recipe["likes"],
            comments=recipe["comments"],
            ingredient_list=recipe["ingredient_list"],
            servings=recipe["servings"],
            calories=recipe_analyser.get_calories(),
            sugar=recipe_analyser.get_sugar(),
            saturated_fat=recipe_analyser.get_saturated_fat(),
            sodium=recipe_analyser.get_sodium(),
            protein=recipe_analyser.get_protein(),
            fiber=recipe_analyser.get_fiber(),
            percentage_fvn=recipe_analyser.get_vfn(),
            healthiness_score=recipe_analyser.get_healthiness_score(),
            nutri_score=recipe_analyser.get_nutriscore(),
        )
        analysed_recipes.append(analysed_recipe)
    return analysed_recipes

def save_recipes_to_csv(recipes: list[Recipe]) -> None:
    df = pd.DataFrame.from_dict(recipes)
    df.to_csv("analysed_recipes.csv", index=False)

def __convert_tolist(string) -> list[str]:
    """
    Helpter function that converts a string to a list of strings by splitting the
    string when a comma is encountered
    """
    li: list[str] = list(string.split(","))
    return li

if __name__ == "__main__":
    main()


"""

Script for training and evaluating a model

"""
from cmath import nan
import os
import loss_fns
import croptype_models
import datetime
import torch
# import datasets
import metrics
import util
import numpy as np
import pickle

from torch import autograd

from constants import *
from tqdm import tqdm
from torch import autograd
import visualize

from sustainbench import get_dataset
from sustainbench.common.data_loaders import get_train_loader
import torchvision.transforms as transforms
import torch.nn.functional as F

def evaluate_split(model, model_name, split_loader, device, loss_weight, weight_scale, gamma, num_classes, country,
                   var_length):
    total_loss = 0
    total_pixels = 0
    total_cm = np.zeros((num_classes, num_classes)).astype(int)
    loss_fn = loss_fns.get_loss_fn(model_name)
    for inputs, targets, cloudmasks, hres_inputs in split_loader:
        with torch.set_grad_enabled(False):
            if not var_length:
                inputs.to(device)
            else:
                for sat in inputs:
                    if "length" not in sat:
                        inputs[sat].to(device)
            targets.to(device)
            hres_inputs.to(device)
            if hres_inputs is not None: hres_inputs.to(device)

            preds = model(inputs, hres_inputs) if model_name in MULTI_RES_MODELS else model(inputs)
            batch_loss, batch_cm, _, num_pixels, confidence = evaluate(model_name, preds, targets, country,
                                                                       loss_fn=loss_fn, reduction="sum",
                                                                       loss_weight=loss_weight,
                                                                       weight_scale=weight_scale, gamma=gamma)
            total_loss += batch_loss.item()
            total_pixels += num_pixels
            total_cm += batch_cm

    f1_avg = metrics.get_f1score(total_cm, avg=True)
    acc_avg = sum([total_cm[i][i] for i in range(num_classes)]) / np.sum(total_cm)
    return total_loss / total_pixels, f1_avg, acc_avg

def evaluate(model_name, preds, labels, country, loss_fn=None, reduction=None, loss_weight=None, weight_scale=None,
             gamma=None):
    """ Evalautes loss and metrics for predictions vs labels.

    Args:
        preds - (tensor) model predictions
        labels - (npy array / tensor) ground truth labels
        loss_fn - (function) function that takes preds and labels and outputs some loss metric
        reduction - (str) "avg" or "sum", where "avg" calculates the average accuracy for each batch
                                          where "sum" tracks total correct and total pixels separately
        loss_weight - (bool) whether we use weighted loss function or not

    Returns:
        loss - (float) the loss the model incurs
        cm - (nparray) confusion matrix given preds and labels
        accuracy - (float) given "avg" reduction, returns accuracy
        total_correct - (int) given "sum" reduction, gives total correct pixels
        num_pixels - (int) given "sum" reduction, gives total number of valid pixels
    """
    cm = metrics.get_cm(preds, labels, country, model_name)

    if model_name in NON_DL_MODELS:
        accuracy = metrics.get_accuracy(model_name, preds, labels, reduction=reduction)
        return None, cm, accuracy, None
    elif model_name in DL_MODELS:
        if reduction == "avg":
            loss, confidence = loss_fn(labels, preds, reduction, country, loss_weight, weight_scale)

            accuracy = metrics.get_accuracy(model_name, labels, model_name, reduction=reduction)
            return loss, cm, accuracy, confidence
        elif reduction == "sum":
            loss, confidence, _ = loss_fn(labels, preds, reduction, country, loss_weight, weight_scale)
            total_correct, num_pixels = metrics.get_accuracy(model_name, preds, labels, reduction=reduction)
            return loss, cm, total_correct, num_pixels, confidence
        else:
            raise ValueError(f"reduction: `{reduction}` not supported")

def predict_dl_model(model, model_name, dataloaders, args):
    splits = ['train', 'val'] if not args.eval_on_test else ['test']
    predictions = []

    if args.clip_val:
        clip_val = sum(p.numel() for p in model.parameters() if p.requires_grad) // 20000
        print('clip value: ', clip_val)

    # set up information lists for visdom
    vis_logger = visualize.VisdomLogger(args.env_name, model_name, args.country, splits)
    loss_fn = loss_fns.get_loss_fn(model_name)
    optimizer = loss_fns.get_optimizer(model.parameters(), args.optimizer, args.lr, args.momentum, args.weight_decay)
    best_val_f1 = 0

    for i in range(args.epochs if not args.eval_on_test else 1):
        print('Epoch: {}'.format(i))

        vis_logger.reset_epoch_data()

        for split in ['train', 'val'] if not args.eval_on_test else ['test']:
            train_data = dataloaders.get_subset(split)

            train_loader = get_train_loader('standard', train_data, args.batch_size)
            model.train() if split == ['train'] else model.eval()
            nclass = len(CM_LABELS[args.country]) + 1
            # for inputs, targets, cloudmasks, hres_inputs in tqdm(dl):
            for inputs, targets, cloudmasks in tqdm(train_loader):

                targets[targets > 4] = 0
                targets = F.one_hot(targets.to(torch.int64), num_classes=nclass)
                mask = torch.arange(1, 5)  # tensor([1, 2, 3, 4])

                targets = torch.index_select(targets, 3, mask)

                targets = targets.permute(0, 3, 1, 2)
                cloudmasks = None

                with torch.set_grad_enabled(True):
                    if not args.var_length:
                        # inputs.to(args.device)
                        for a in inputs:
                            inputs[a].to(args.device)

                        # if hres_inputs is not None: hres_inputs.to(args.device)
                    else:
                        for sat in inputs:
                            if "length" not in sat:
                                inputs[sat].to(args.device)
                    targets.to(args.device)

                    # ## For 2nd command(unet3d) uncommnet later commands
                    inputs=torch.cat( (inputs['s1'],inputs['s2'],inputs['planet']), dim=1)
                    inputs=inputs.permute(0,1,4,2,3)  #torch.Size([2, 17, 64, 64, 256]) After permute torch.Size([2, 17, 256, 64, 64])
                    inputs=inputs.float()
                    inputs=inputs.cuda()

                    # For 3rd command() uncommnet later commands
                    # for a in inputs:
                    #     inputs[a] = inputs[a].permute(0, 4, 1, 2, 3)  ##  batch, timestamps, bands, rows, columns
                    #     inputs[a] = inputs[a].float().cuda()

                    # # ## For 4th command() uncommnet later commands
                    # inputs=torch.cat( (inputs['s1'],inputs['s2'],inputs['planet']), dim=1)
                    # inputs=inputs.permute(0,4,1,2,3)
                    # inputs=inputs.float().cuda()

                    preds = model(inputs)
                    predictions.append(preds)
                    loss, cm_cur, total_correct, num_pixels, confidence = evaluate(model_name, preds, targets,
                                                                                args.country, loss_fn=loss_fn,
                                                                                reduction="sum",
                                                                                loss_weight=args.loss_weight,
                                                                                weight_scale=args.weight_scale,
                                                                                gamma=args.gamma)

                    if split == 'train' and loss is not None:  # TODO: not sure if we need this check?
                        # If there are valid pixels, update weights
                        optimizer.zero_grad()
                        # with autograd.detect_anomaly():
                        loss.backward()
                        if args.clip_val:
                            # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs.
                            torch.nn.utils.clip_grad_norm_(model.parameters(), clip_val)
                        optimizer.step()
                        total_norm = 0
                        for p in model.parameters():
                            if p.grad is not None:
                                param_norm = p.grad.data.norm(2)
                                total_norm += param_norm.item() ** 2
                        gradnorm = total_norm ** (1. / 2)
                        # gradnorm = torch.norm(list(model.parameters())[0].grad).detach().cpu() / torch.prod(torch.tensor(list(model.parameters())[0].shape), dtype=torch.float32)

                        # vis_logger.update_progress('train', 'gradnorm', gradnorm)
                        loss = loss.cpu()
                        with torch.no_grad():
                            vis_logger.update_progress('train', 'gradnorm', gradnorm)

                    if cm_cur is not None:  # TODO: not sure if we need this check?
                        # If there are valid pixels, update metrics
                        with torch.no_grad():
                            vis_logger.update_epoch_all(split, cm_cur, loss, total_correct, num_pixels)
                        # vis_logger.update_epoch_all(split, cm_cur, loss, total_correct, num_pixels)

                # with torch.no_grad():
                #     vis_logger.record_batch(inputs, cloudmasks, targets.float(), preds, confidence,
                #                         NUM_CLASSES[args.country], split,
                #                         args.include_doy, args.use_s1, args.use_s2,
                #                         model_name, args.time_slice, var_length=args.var_length)

                # del loss  # adding this as the gpu usage was increasing with time
                # del preds

            if split in ['test']:
                vis_logger.record_epoch(split, i, args.country, save=False,
                                        save_dir=os.path.join(args.save_dir, args.name + "_best_dir"))
            else:
                vis_logger.record_epoch(split, i, args.country)

            if split == 'val':
                val_f1 = metrics.get_f1score(vis_logger.epoch_data['val_cm'], avg=True)
                print("val at epoch=", i, val_f1)

                if val_f1 > best_val_f1:
                    torch.save(model.state_dict(), os.path.join(args.save_dir, args.name + "_best"))
                    best_val_f1 = val_f1
                    print("best till now saved at epoch", i, args.name + "_best")

                    # """  # commenting the next lines to check now if it is working or not
                    if args.save_best:
                        # TODO: Ideally, this would save any batch except the last one so that the saved images
                        #  are not only the remainder from the last batch
                        vis_logger.record_batch(inputs, cloudmasks, targets.float(), preds, confidence,
                                                NUM_CLASSES[args.country], split,
                                                args.include_doy, args.use_s1, args.use_s2,
                                                model_name, args.time_slice, save=True, var_length=args.var_length,
                                                save_dir=os.path.join(args.save_dir, args.name + "_best_dir"))

                        vis_logger.record_epoch(split, i, args.country, save=True,
                                                save_dir=os.path.join(args.save_dir, args.name + "_best_dir"))

                        vis_logger.record_epoch('train', i, args.country, save=True,
                                                save_dir=os.path.join(args.save_dir, args.name + "_best_dir"))

    return predictions

def predict(model, model_name, args=None, dataloaders=None, X=None, y=None):
    """ Trains the model on the inputs

    Args:
        model - trainable model
        model_name - (str) name of the model
        args - (argparse object) args parsed in from main; used only for DL models
        dataloaders - (dict of dataloaders) used only for DL models
        X - (npy arr) data for non-dl models
        y - (npy arr) labels for non-dl models
    """
    if dataloaders is None and model_name in DL_MODELS: raise ValueError("DATA GENERATOR IS NONE")
    if args is None and model_name in DL_MODELS: raise ValueError("Args is NONE")

    if model_name in NON_DL_MODELS:
        predictions = train_non_dl_model(model, model_name, dataloaders, args, X, y)
    elif model_name in DL_MODELS:
        predictions = predict_dl_model(model, model_name, dataloaders, args)
    else:
        raise ValueError(f"Unsupported model name: {model_name}")

    return predictions

def main(args):
    if args.seed is not None:
        if args.device == 'cuda':
            use_cuda = True
        elif args.device == 'cpu':
            use_cuda = False
        util.random_seed(seed_value=args.seed, use_cuda=use_cuda)

    # load in data generator

    dataset = get_dataset(dataset='africa_crop_type_mapping', split_scheme="cauvery", resize_planet=True,
                          normalize=True, calculate_bands=True, root_dir=args.path_to_cauvery_images)

    dataloaders = dataset

    # load in model
    model = croptype_models.get_model(**vars(args))
    if args.model_name in DL_MODELS:
        print('Total trainable model parameters: {}'.format(
            sum(p.numel() for p in model.parameters() if p.requires_grad)))

    if args.model_path is not None:
        model.load_state_dict(torch.load(args.model_path))
        # model.load_state_dict(torch.load("PATH/TO/MODEL"))

    if args.model_name in DL_MODELS and args.device == 'cuda' and torch.cuda.is_available():
        model.to(args.device)

    if args.name is None:
        args.name = str(datetime.datetime.now()) + "_" + args.model_name

    if not os.path.exists(args.save_dir):
        os.mkdir(args.save_dir)

    print("Starting to predict")
    return predict(model, args.model_name, args, dataloaders=dataloaders)

if __name__ == "__main__":
    # parse args
    parser = util.get_train_parser()

    main(parser.parse_args())


#!/usr/bin/env python
import smbus
import sys

I2C_BUS = 1
DEBUG = False

install_mode = False

if len(sys.argv) > 1:
    if "--install" in sys.argv:
        install_mode = True

try:
    bus = smbus.SMBus(I2C_BUS)
except IOError:
    print("Unable to access /dev/i2c-{}, please ensure i2c is enabled!".format(I2C_BUS))
    sys.exit()

def check_chip_id(i2c_addr, chip_ids):
    if len(chip_ids) == 0:
        return True

    for register in chip_ids:
        value, size, negate_match = chip_ids[register]
        reg, reg_size = register
        if reg_size == 1:
            if size == 1:
                read = bus.read_byte_data(i2c_addr, reg)
            elif size == 2:
                read = bus.read_word_data(i2c_addr, reg)
            else:
                raise RuntimeError("Unsupported Chip ID size: {} byte(s)".format(size))

            if negate_match and value != read:
                return True
            elif value == read:
                return True

        else:
            # TODO: Support 16-bit registers
            raise RuntimeError("Unsupported register size: {} byte(s)".format(size))

    return False

def get_device(line):
    parts=[x.strip() for x in line.split(":")]

    i2c_addr = int(parts[0][0:4], 16)

    chip_ids = {}

    if(len(parts[0]) > 4):
        register_map = parts[0][5:-1].split(',')
        for mapping in register_map:
            if '!=' in mapping:
                register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('!=')]
                chip_ids[register] = value[0], value[1], True
            else:
                register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('=')]
                chip_ids[register] = value[0], value[1], False

    return i2c_addr, parts[1], parts[2], parts[3], chip_ids

devices = [get_device(line) for line in open("breakouts.config").read().strip().split("\n")]

addresses = set([device[0] for device in devices])

def identify(find_i2c_addr):
    try:
        bus.read_byte_data(find_i2c_addr, 0x00)
    except IOError as e:
        pass

    for i2c_addr, library, module, name, chip_ids in devices:
        if i2c_addr == find_i2c_addr and check_chip_id(i2c_addr, chip_ids):
            installed = True
            try:
                __import__(module)
            except ImportError:
                installed = False
            return installed, library, name

    return None, None, None

found_addr = []
found_devices = {}

for i2c_addr in addresses:
    try:
        bus.read_byte_data(i2c_addr, 0x00)
        if DEBUG: print("Found device on: {:02x}".format(i2c_addr))
        found_addr.append(i2c_addr)
        installed, library, name = identify(i2c_addr)
        if installed is None:
            continue
        if name not in found_devices:
            found_devices[name] = [installed, library, [i2c_addr]]
        else:
            found_devices[name][2].append(i2c_addr)

    except IOError as e:
        if DEBUG: print("IOError reading: {:02x}".format(i2c_addr))
        continue

for name in found_devices:
    installed, library, i2c_addresses = found_devices[name]
    format_string = ""
    if install_mode:
        format_string = "{name}|{library}|{installed}"
    else:
        format_string = "{i2c_addresses}: {name} ({library} {installed})"

    print(format_string.format(
        i2c_addresses = ",".join(["0x{:02x}".format(i2c_addr) for i2c_addr in i2c_addresses]),
            name = name,
            library = library,
            installed = "installed" if installed else "required"
        ))


#!/usr/bin/env python3

### Advent of Code - 2016 - Day 9

import sys
import requests
import re
import math
import itertools
import functools
import os

from aoc_utils import *

# read input data file as one long string and as an array of lines
inputfile = 'input'
if len(sys.argv) == 2:
    inputfile = sys.argv[1]
if not os.path.exists(inputfile):
    print(RED+f"Input file {inputfile} not found!"+CLEAR)
    quit()
input = open(inputfile,'r').read().rstrip()
input_lines = [line.strip() for line in input.split('\n')]
print(DBLUE+f"Input <{inputfile}>, num lines: {len(input_lines)}"+CLEAR)

p1 = 0
p2 = 0

uncomp = ''
comp = input
need_to_decomp = True

i = 0
while i < len(comp):
    #print(uncomp)
    if comp[i] != '(':
        uncomp += comp[i]
        i += 1
        continue
    m = re.match(r"\((\d+)x(\d+)\)", comp[i:])
    if m:
        skip = len(m.group(0))
        c = int(m.group(1))
        r = int(m.group(2))
        #print(f"skip {skip}, c {c}, r {r}")
        data = comp[i+skip:i+skip+c]
        #print(f"data {data}")
        for _ in range(r):
            uncomp += data
        i += skip+c

#print(uncomp)
part1(len(uncomp))

comp = input
uncomp = (0, comp)  # uncompressed bytes, followed by compressed data

def uncompress(s):
    m = re.search(r"\((\d+)x(\d+)\)",s)
    if not m:
        return len(s) # end of string, no more repeated patterns
    length = int(m.group(1))
    times = int(m.group(2))
    start = m.start() + len(m.group())
    count = uncompress(s[start:start+length])

    #       prefix of AxB          B * recurse    uncompress postfix of AxB
    return (len(s[:m.start()]) + times*count + uncompress(s[start+length:]))

part2(uncompress(comp))
quit()

i = 0
while i < len(comp):
    #print(uncomp)
    if comp[i] != '(':
        uncomp[0] += 1
        i += 1
        continue
    m = re.match(r"\((\d+)x(\d+)\)", comp[i:])
    if m:
        skip = len(m.group(0))
        c = int(m.group(1))
        r = int(m.group(2))
        print(f"skip {skip}, c {c}, r {r}")
        data = comp[i+skip:i+skip+c]
        print(f"data {data}")
        for _ in range(r):
            uncomp += data
        i += skip+c

part2(uncomp[0])


# Built-in
import os
import sys
import imp
import json
import shutil
import getpass
import datetime
import traceback
import subprocess
from functools import partial

# pymel
import pymel.core as pm
from pymel.core import datatypes

# mgear
import mgear
from .. import attribute, dag, vector, pyqt, skin
from ... import string
from ...vendor.Qt import QtCore, QtWidgets, QtGui

from . import guideUI as guui
from . import customStepUI as csui

# pyside
from maya.app.general.mayaMixin import MayaQDockWidget
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

GUIDE_UI_WINDOW_NAME = "guide_UI_window"
GUIDE_DOCK_NAME = "Guide_Components"

TYPE = "mgear_guide_root"

MGEAR_SHIFTER_CUSTOMSTEP_KEY = "MGEAR_SHIFTER_CUSTOMSTEP_PATH"

class Main(object):
    """The main guide class

    Provide the methods to add parameters, set parameter values,
    create property...

    Attributes:
        paramNames (list): List of parameter name cause it's actually important
        to keep them sorted.
        paramDefs (dict): Dictionary of parameter definition.
        values (dict): Dictionary of options values.
        valid (bool): We will check a few things and make sure the guide we are
            loading is up to date. If parameters or object are missing a
            warning message will be display and the guide should be updated.

    """

    def __init__(self):

        self.paramNames = []
        self.paramDefs = {}
        self.values = {}
        self.valid = True

    def addPropertyParamenters(self, parent):
        """Add attributes from the parameter definition list

        Arguments:
            parent (dagNode): The object to add the attributes.

        Returns:
            dagNode: parent with the attributes.

        """

        for scriptName in self.paramNames:
            paramDef = self.paramDefs[scriptName]
            paramDef.create(parent)

        return parent

    def setParamDefValue(self, scriptName, value):
        """Set the value of parameter with matching scriptname.

        Arguments:
            scriptName (str): Scriptname of the parameter to edit.
            value (variant): New value.

        Returns:
            bool: False if the parameter wasn't found.

        """

        if scriptName not in self.paramDefs.keys():
            mgear.log("Can't find parameter definition for : " + scriptName,
                      mgear.sev_warning)
            return False

        self.paramDefs[scriptName].value = value
        self.values[scriptName] = value

        return True

    def setParamDefValuesFromProperty(self, node):
        """Set the parameter definition values from the attributes of an object

        Arguments:
            node (dagNode): The object with the attributes.
        """

        for scriptName, paramDef in self.paramDefs.items():
            if not pm.attributeQuery(scriptName, node=node, exists=True):
                mgear.log("Can't find parameter '%s' in %s" %
                          (scriptName, node), mgear.sev_warning)
                self.valid = False
            else:
                cnx = pm.listConnections(
                    node + "." + scriptName,
                    destination=False, source=True)
                if cnx:
                    paramDef.value = None
                    self.values[scriptName] = cnx[0]
                else:
                    paramDef.value = pm.getAttr(node + "." + scriptName)
                    self.values[scriptName] = pm.getAttr(
                        node + "." + scriptName)

    def addColorParam(self, scriptName, value=False):
        """Add color paramenter to the paramenter definition Dictionary.

        Arguments:
            scriptName (str): The name of the color parameter.
            value (Variant): The default color value.

        Returns:
            paramDef: The newly create paramenter definition.
        """

        paramDef = attribute.colorParamDef(scriptName, value)
        self.paramDefs[scriptName] = paramDef
        self.paramNames.append(scriptName)

        return paramDef

    def addParam(self, scriptName, valueType, value,
                 minimum=None, maximum=None, keyable=False,
                 readable=True, storable=True, writable=True,
                 niceName=None, shortName=None):
        """Add paramenter to the paramenter definition Dictionary.

        Arguments:
            scriptName (str): Parameter scriptname.
            valueType (str): The Attribute Type. Exp: 'string', 'bool',
                'long', etc..
            value (float or int): Default parameter value.
            niceName (str): Parameter niceName.
            shortName (str): Parameter shortName.
            minimum (float or int): mininum value.
            maximum (float or int): maximum value.
            keyable (boo): If true is keyable
            readable (boo): If true is readable
            storable (boo): If true is storable
            writable (boo): If true is writable

        Returns:
            paramDef: The newly create paramenter definition.

        """
        paramDef = attribute.ParamDef2(scriptName, valueType, value, niceName,
                                       shortName, minimum, maximum, keyable,
                                       readable, storable, writable)
        self.paramDefs[scriptName] = paramDef
        self.values[scriptName] = value
        self.paramNames.append(scriptName)

        return paramDef

    def addFCurveParam(self, scriptName, keys, interpolation=0):
        """Add FCurve paramenter to the paramenter definition Dictionary.

        Arguments:
            scriptName (str): Attribute fullName.
            keys (list): The keyframes to define the function curve.
            interpolation (int): the curve interpolation.

        Returns:
            paramDef: The newly create paramenter definition.

        """
        paramDef = attribute.FCurveParamDef(scriptName, keys, interpolation)
        self.paramDefs[scriptName] = paramDef
        self.values[scriptName] = None
        self.paramNames.append(scriptName)

        return paramDef

    def addEnumParam(self, scriptName, enum, value=False):
        """Add FCurve paramenter to the paramenter definition Dictionary.

        Arguments:
            scriptName (str): Attribute fullName
            enum (list of str): The list of elements in the enumerate control.
            value (int): The default value.

        Returns:
            paramDef: The newly create paramenter definition.

        """
        paramDef = attribute.enumParamDef(scriptName, enum, value)
        self.paramDefs[scriptName] = paramDef
        self.values[scriptName] = value
        self.paramNames.append(scriptName)

        return paramDef

##########################################################
# RIG GUIDE
##########################################################

class Rig(Main):
    """Rig guide class.

    This is the class for complete rig guide definition.

        * It contains the component guide in correct hierarchy order and the
            options to generate the rig.
        * Provide the methods to add more component, import/export guide.

    Attributes:
        paramNames (list): List of parameter name cause it's actually important
            to keep them sorted.
        paramDefs (dict): Dictionary of parameter definition.
        values (dict): Dictionary of options values.
        valid (bool): We will check a few things and make sure the guide we are
            loading is up to date. If parameters or object are missing a
            warning message will be display and the guide should be updated.
        controllers (dict): Dictionary of controllers.
        components (dict): Dictionary of component. Keys are the component
            fullname (ie. 'arm_L0')
        componentsIndex (list): List of component name sorted by order
            creation (hierarchy order)
        parents (list): List of the parent of each component, in same order
            as self.components
    """

    def __init__(self):

        # Parameters names, definition and values.
        self.paramNames = []
        self.paramDefs = {}
        self.values = {}
        self.valid = True

        self.controllers = {}
        self.components = {}  # Keys are the component fullname (ie. 'arm_L0')
        self.componentsIndex = []
        self.parents = []

        self.addParameters()

    def addParameters(self):
        """Parameters for rig options.

        Add more parameter to the parameter definition list.

        """
        # --------------------------------------------------
        # Main Tab
        self.pRigName = self.addParam("rig_name", "string", "rig")
        self.pMode = self.addEnumParam("mode", ["Final", "WIP"], 0)
        self.pStep = self.addEnumParam(
            "step",
            ["All Steps", "Objects", "Properties",
                "Operators", "Connect", "Joints", "Finalize"],
            6)
        self.pIsModel = self.addParam("ismodel", "bool", True)
        self.pClassicChannelNames = self.addParam(
            "classicChannelNames",
            "bool",
            True)
        self.pProxyChannels = self.addParam("proxyChannels", "bool", False)
        self.pWorldCtl = self.addParam("worldCtl", "bool", False)

        # --------------------------------------------------
        # skin
        self.pSkin = self.addParam("importSkin", "bool", False)
        self.pSkinPackPath = self.addParam("skin", "string", "")

        # --------------------------------------------------
        # Colors

        # Index color
        self.pLColorIndexfk = self.addParam("L_color_fk", "long", 6, 0, 31)
        self.pLColorIndexik = self.addParam("L_color_ik", "long", 18, 0, 31)
        self.pRColorIndexfk = self.addParam("R_color_fk", "long", 23, 0, 31)
        self.pRColorIndexik = self.addParam("R_color_ik", "long", 14, 0, 31)
        self.pCColorIndexfk = self.addParam("C_color_fk", "long", 13, 0, 31)
        self.pCColorIndexik = self.addParam("C_color_ik", "long", 17, 0, 31)

        # RGB colors for Maya 2015 and up
        # self.pLColorfk = self.addColorParam("L_RGB_fk", [0, 1, 0])
        # self.pLColorik = self.addColorParam("L_RGB_ik", [0, .5, 0])
        # self.pRColorfk = self.addColorParam("R_RGB_fk", [0, 0, 1])
        # self.pRColorik = self.addColorParam("R_RGB_ik", [0, 0, .6])
        # self.pCColorfk = self.addColorParam("C_RGB_fk", [1, 0, 0])
        # self.pCColorik = self.addColorParam("C_RGB_ik", [.6, 0, 0])

        # --------------------------------------------------
        # Settings
        self.pJointRig = self.addParam("joint_rig", "bool", True)
        self.pSynoptic = self.addParam("synoptic", "string", "")

        self.pDoPreCustomStep = self.addParam("doPreCustomStep", "bool", False)
        self.pDoPostCustomStep = self.addParam("doPostCustomStep",
                                               "bool", False)
        self.pPreCustomStep = self.addParam("preCustomStep", "string", "")
        self.pPostCustomStep = self.addParam("postCustomStep", "string", "")

        # --------------------------------------------------
        # Comments
        self.pComments = self.addParam("comments", "string", "")
        self.pUser = self.addParam("user", "string", getpass.getuser())
        self.pDate = self.addParam(
            "date", "string", str(datetime.datetime.now()))
        self.pMayaVersion = self.addParam(
            "maya_version", "string",
            str(pm.mel.eval("getApplicationVersionAsFloat")))
        self.pGearVersion = self.addParam(
            "gear_version", "string", mgear.getVersion())

    def setFromSelection(self):
        """Set the guide hierarchy from selection."""
        selection = pm.ls(selection=True)
        if not selection:
            mgear.log(
                "Select one or more guide root or a guide model",
                mgear.sev_error)
            self.valid = False
            return False

        for node in selection:
            self.setFromHierarchy(node, node.hasAttr("ismodel"))

        return True

    def setFromHierarchy(self, root, branch=True):
        """Set the guide from given hierarchy.

        Arguments:
            root (dagNode): The root of the hierarchy to parse.
            branch (bool): True to parse children components.

        """
        startTime = datetime.datetime.now()
        # Start
        mgear.log("Checking guide")

        # Get the model and the root
        self.model = root.getParent(generations=-1)
        while True:
            if root.hasAttr("comp_type") or self.model == root:
                break
            root = root.getParent()
            mgear.log(root)

        # ---------------------------------------------------
        # First check and set the options
        mgear.log("Get options")
        self.setParamDefValuesFromProperty(self.model)

        # ---------------------------------------------------
        # Get the controllers
        mgear.log("Get controllers")
        self.controllers_org = dag.findChild(self.model, "controllers_org")
        if self.controllers_org:
            for child in self.controllers_org.getChildren():
                self.controllers[child.name().split("|")[-1]] = child

        # ---------------------------------------------------
        # Components
        mgear.log("Get components")
        self.findComponentRecursive(root, branch)
        endTime = datetime.datetime.now()
        finalTime = endTime - startTime
        mgear.log("Find recursive in  [ " + str(finalTime) + " ]")
        # Parenting
        if self.valid:
            for name in self.componentsIndex:
                mgear.log("Get parenting for: " + name)
                # TODO: In the future should use connections to retrive this
                # data
                # We try the fastes aproach, will fail if is not the top node
                try:
                    # search for his parent
                    compParent = self.components[name].root.getParent()
                    if compParent and compParent.hasAttr("isGearGuide"):
                        pName = "_".join(compParent.name().split("_")[:2])
                        pLocal = "_".join(compParent.name().split("_")[2:])

                        pComp = self.components[pName]
                        self.components[name].parentComponent = pComp
                        self.components[name].parentLocalName = pLocal
                # This will scan the hierachy in reverse. It is much slower
                except KeyError:
                    # search children and set him as parent
                    compParent = self.components[name]
                    # for localName, element in compParent.getObjects(
                    #         self.model, False).items():
                    # NOTE: getObjects3 is an experimental function
                    for localName, element in compParent.getObjects3(
                            self.model).items():
                        for name in self.componentsIndex:
                            compChild = self.components[name]
                            compChild_parent = compChild.root.getParent()
                            if (element is not None
                                    and element == compChild_parent):
                                compChild.parentComponent = compParent
                                compChild.parentLocalName = localName

            # More option values
            self.addOptionsValues()

        # End
        if not self.valid:
            mgear.log("The guide doesn't seem to be up to date."
                      "Check logged messages and update the guide.",
                      mgear.sev_warning)

        endTime = datetime.datetime.now()
        finalTime = endTime - startTime
        mgear.log("Guide loaded from hierarchy in  [ " + str(finalTime) + " ]")

    def addOptionsValues(self):
        """Gather or change some options values according to some others.

        Note:
            For the moment only gets the rig size to adapt size of object to
            the scale of the character

        """
        # Get rig size to adapt size of object to the scale of the character
        maximum = 1
        v = datatypes.Vector()
        for comp in self.components.values():
            for pos in comp.apos:
                d = vector.getDistance(v, pos)
                maximum = max(d, maximum)

        self.values["size"] = max(maximum * .05, .1)

    def findComponentRecursive(self, node, branch=True):
        """Finds components by recursive search.

        Arguments:
            node (dagNode): Object frome where start the search.
            branch (bool): If True search recursive all the children.
        """

        if node.hasAttr("comp_type"):
            comp_type = node.getAttr("comp_type")
            comp_guide = self.getComponentGuide(comp_type)

            if comp_guide:
                comp_guide.setFromHierarchy(node)
                mgear.log(comp_guide.fullName + " (" + comp_type + ")")
                if not comp_guide.valid:
                    self.valid = False

                self.componentsIndex.append(comp_guide.fullName)
                self.components[comp_guide.fullName] = comp_guide

        if branch:
            for child in node.getChildren(type="transform"):
                self.findComponentRecursive(child)

    def getComponentGuide(self, comp_type):
        """Get the componet guide python object

        ie. Finds the guide.py of the component.

        Arguments:
            comp_type (str): The component type.

        Returns:
            The component guide instance class.
        """

        # Check component type
        '''
        path = os.path.join(basepath, comp_type, "guide.py")
        if not os.path.exists(path):
            mgear.log("Can't find guide definition for : " + comp_type + ".\n"+
                path, mgear.sev_error)
            return False
        '''

        # Import module and get class
        import mgear.maya.shifter as shifter
        module = shifter.importComponentGuide(comp_type)

        ComponentGuide = getattr(module, "Guide")

        return ComponentGuide()

    # =====================================================
    # DRAW

    def initialHierarchy(self):
        """Create the initial rig guide hierarchy (model, options...)"""
        self.model = pm.group(n="guide", em=True, w=True)

        # Options
        self.options = self.addPropertyParamenters(self.model)

        # the basic org nulls (Maya groups)
        self.controllers_org = pm.group(
            n="controllers_org",
            em=True,
            p=self.model)
        self.controllers_org.attr('visibility').set(0)

    def drawNewComponent(self, parent, comp_type):
        """Add a new component to the guide.

        Arguments:
            parent (dagNode): Parent of this new component guide.
            compType (str): Type of component to add.

        """
        comp_guide = self.getComponentGuide(comp_type)

        if not comp_guide:
            mgear.log("Not component guide of type: " + comp_type +
                      " have been found.", mgear.sev_error)
            return
        if parent is None:
            self.initialHierarchy()
            parent = self.model
        else:
            parent_root = parent
            while True:
                if parent_root.hasAttr("ismodel"):
                    break

                if parent_root.hasAttr("comp_type"):
                    parent_type = parent_root.attr("comp_type").get()
                    parent_side = parent_root.attr("comp_side").get()
                    parent_uihost = parent_root.attr("ui_host").get()
                    parent_ctlGrp = parent_root.attr("ctlGrp").get()

                    if parent_type in comp_guide.connectors:
                        comp_guide.setParamDefValue("connector", parent_type)

                    comp_guide.setParamDefValue("comp_side", parent_side)
                    comp_guide.setParamDefValue("ui_host", parent_uihost)
                    comp_guide.setParamDefValue("ctlGrp", parent_ctlGrp)

                    break

                parent_root = parent_root.getParent()

        comp_guide.drawFromUI(parent)

    def drawUpdate(self, oldRoot, parent=None):

        # Initial hierarchy
        if parent is None:
            self.initialHierarchy()
            parent = self.model
            newParentName = parent.name()

        # controls shape
        try:
            pm.delete(pm.PyNode(newParentName + "|controllers_org"))
            oldRootName = oldRoot.name().split("|")[0] + "|controllers_org"
            pm.parent(oldRootName, newParentName)
        except TypeError:
            pm.displayError("The guide don't have controllers_org")

        # Components
        for name in self.componentsIndex:
            comp_guide = self.components[name]
            oldParentName = comp_guide.root.getParent().name()

            try:
                parent = pm.PyNode(oldParentName.replace(
                    oldParentName.split("|")[0], newParentName))
            except TypeError:
                pm.displayWarning("No parent for the guide")
                parent = self.model

            comp_guide.draw(parent)

    def update(self, sel, force=False):
        """Update the guide if a parameter is missing"""

        if pm.attributeQuery("ismodel", node=sel, ex=True):
            self.model = sel
        else:
            pm.displayWarning("select the top guide node")
            return

        name = self.model.name()
        self.setFromHierarchy(self.model, True)
        if self.valid and not force:
            pm.displayInfo("The Guide is updated")
            return

        pm.rename(self.model, name + "_old")
        deleteLater = self.model
        self.drawUpdate(deleteLater)
        pm.rename(self.model, name)
        pm.displayInfo("The guide %s have been updated" % name)
        pm.delete(deleteLater)

    def duplicate(self, root, symmetrize=False):
        """Duplicate the guide hierarchy

        Note:
            Indeed this method is not duplicating.
            What it is doing is parse the compoment guide,
            and creating an new one base on the current selection.

        Warning:
            Don't use the default Maya's duplicate tool to duplicate a
            Shifter's guide.

        Arguments:
            root (dagNode): The guide root to duplicate.
            symmetrize (bool): If True, duplicate symmetrical in X axis.
            The guide have to be "Left" or "Right".

        """
        if not pm.attributeQuery("comp_type", node=root, ex=True):
            mgear.log("Select a component root to duplicate", mgear.sev_error)
            return

        self.setFromHierarchy(root)
        for name in self.componentsIndex:
            comp_guide = self.components[name]
            if symmetrize:
                if not comp_guide.symmetrize():
                    return

        # Draw
        if pm.attributeQuery("ismodel", node=root, ex=True):
            self.draw()

        else:

            for name in self.componentsIndex:
                comp_guide = self.components[name]

                if comp_guide.parentComponent is None:
                    parent = comp_guide.root.getParent()
                    if symmetrize:
                        parent = dag.findChild(
                            self.model,
                            string.convertRLName(
                                comp_guide.root.getParent().name()))
                        if not parent:
                            parent = comp_guide.root.getParent()

                    else:
                        parent = comp_guide.root.getParent()

                else:
                    parent = dag.findChild(
                        self.model,
                        comp_guide.parentComponent.getName(
                            comp_guide.parentLocalName))
                    if not parent:
                        mgear.log(
                            "Unable to find parent (%s.%s) for guide %s" %
                            (comp_guide.parentComponent.getFullName,
                                comp_guide.parentLocalName,
                                comp_guide.getFullName))
                        parent = self.model

                # Reset the root so we force the draw to duplicate
                comp_guide.root = None

                comp_guide.setIndex(self.model)

                comp_guide.draw(parent)

        pm.select(self.components[self.componentsIndex[0]].root)

    def updateProperties(self, root, newName, newSide, newIndex):
        """Update the Properties of the component.

        Arguments:
            root (dagNode): Root of the component.
            newName (str): New name of the component
            newSide (str): New side of the component
            newIndex (str): New index of the component
        """

        if not pm.attributeQuery("comp_type", node=root, ex=True):
            mgear.log("Select a root to edit properties", mgear.sev_error)
            return
        self.setFromHierarchy(root, False)
        name = "_".join(root.name().split("|")[-1].split("_")[0:2])
        comp_guide = self.components[name]
        comp_guide.rename(root, newName, newSide, newIndex)

class HelperSlots(object):

    def updateHostUI(self, lEdit, targetAttr):
        oType = pm.nodetypes.Transform

        oSel = pm.selected()
        if oSel:
            if isinstance(oSel[0], oType) and oSel[0].hasAttr("isGearGuide"):
                lEdit.setText(oSel[0].name())
                self.root.attr(targetAttr).set(lEdit.text())
            else:
                pm.displayWarning("The selected element is not a "
                                  "valid object or not from a guide")
        else:
            pm.displayWarning("Please select first the object.")

    def updateLineEdit(self, lEdit, targetAttr):
        name = string.removeInvalidCharacter(lEdit.text())
        self.root.attr(targetAttr).set(name)

    def addItem2listWidget(self, listWidget, targetAttr=None):

        items = pm.selected()
        itemsList = [i.text() for i in listWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        # Quick clean the first empty item
        if itemsList and not itemsList[0]:
            listWidget.takeItem(0)

        for item in items:
            if item.name() not in itemsList:
                if item.hasAttr("isGearGuide"):
                    listWidget.addItem(item.name())

                else:
                    pm.displayWarning(
                        "The object: %s, is not a valid"
                        " reference, Please select only guide componet"
                        " roots and guide locators." % item.name())
            else:
                pm.displayWarning("The object: %s, is already in the list." %
                                  item.name())

        if targetAttr:
            self.updateListAttr(listWidget, targetAttr)

    def removeSelectedFromListWidget(self, listWidget, targetAttr=None):
        for item in listWidget.selectedItems():
            listWidget.takeItem(listWidget.row(item))
        if targetAttr:
            self.updateListAttr(listWidget, targetAttr)

    def moveFromListWidget2ListWidget(self, sourceListWidget, targetListWidget,
                                      targetAttrListWidget, targetAttr=None):
        # Quick clean the first empty item
        itemsList = [i.text() for i in targetAttrListWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        if itemsList and not itemsList[0]:
            targetAttrListWidget.takeItem(0)

        for item in sourceListWidget.selectedItems():
            targetListWidget.addItem(item.text())
            sourceListWidget.takeItem(sourceListWidget.row(item))

        if targetAttr:
            self.updateListAttr(targetAttrListWidget, targetAttr)

    def copyFromListWidget(self, sourceListWidget, targetListWidget,
                           targetAttr=None):
        targetListWidget.clear()
        itemsList = [i.text() for i in sourceListWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        for item in itemsList:
            targetListWidget.addItem(item)
        if targetAttr:
            self.updateListAttr(sourceListWidget, targetAttr)

    def updateListAttr(self, sourceListWidget, targetAttr):
        """Update the string attribute with values separated by commas"""
        newValue = ",".join([i.text() for i in sourceListWidget.findItems(
            "", QtCore.Qt.MatchContains)])
        self.root.attr(targetAttr).set(newValue)

    def updateComponentName(self):

        newName = self.mainSettingsTab.name_lineEdit.text()
        # remove invalid characters in the name and update
        newName = string.removeInvalidCharacter(newName)
        self.mainSettingsTab.name_lineEdit.setText(newName)
        sideSet = ["C", "L", "R"]
        sideIndex = self.mainSettingsTab.side_comboBox.currentIndex()
        newSide = sideSet[sideIndex]
        newIndex = self.mainSettingsTab.componentIndex_spinBox.value()
        guide = Rig()
        guide.updateProperties(self.root, newName, newSide, newIndex)
        pm.select(self.root, r=True)
        # sync index
        self.mainSettingsTab.componentIndex_spinBox.setValue(
            self.root.attr("comp_index").get())

    def updateConnector(self, sourceWidget, itemsList, *args):
        self.root.attr("connector").set(itemsList[sourceWidget.currentIndex()])

    def populateCheck(self, targetWidget, sourceAttr, *args):
        if self.root.attr(sourceAttr).get():
            targetWidget.setCheckState(QtCore.Qt.Checked)
        else:
            targetWidget.setCheckState(QtCore.Qt.Unchecked)

    def updateCheck(self, sourceWidget, targetAttr, *args):
        self.root.attr(targetAttr).set(sourceWidget.isChecked())

    def updateSpinBox(self, sourceWidget, targetAttr, *args):
        self.root.attr(targetAttr).set(sourceWidget.value())
        return True

    def updateSlider(self, sourceWidget, targetAttr, *args):
        self.root.attr(targetAttr).set(float(sourceWidget.value()) / 100)

    def updateComboBox(self, sourceWidget, targetAttr, *args):
        self.root.attr(targetAttr).set(sourceWidget.currentIndex())

    def updateControlShape(self, sourceWidget, ctlList, targetAttr, *args):
        curIndx = sourceWidget.currentIndex()
        self.root.attr(targetAttr).set(ctlList[curIndx])

    def setProfile(self):
        pm.select(self.root, r=True)
        pm.runtime.GraphEditor()

    def close_settings(self):
        self.close()
        pyqt.deleteInstances(self, MayaQDockWidget)

    def editFile(self, widgetList):
        try:
            filepath = widgetList.selectedItems()[0].text().split("|")[-1][1:]
            if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
                editPath = os.path.join(
                    os.environ.get(
                        MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""), filepath)
            else:
                editPath = filepath
            if filepath:
                if sys.platform.startswith('darwin'):
                    subprocess.call(('open', editPath))
                elif os.name == 'nt':
                    os.startfile(editPath)
                elif os.name == 'posix':
                    subprocess.call(('xdg-open', editPath))
            else:
                pm.displayWarning("Please select one item from the list")
        except Exception:
            pm.displayError("The step can't be find or does't exists")

    @classmethod
    def runStep(self, stepPath, customStepDic):
        try:
            with pm.UndoChunk():
                pm.displayInfo(
                    "EXEC: Executing custom step: %s" % stepPath)
                fileName = os.path.split(stepPath)[1].split(".")[0]
                if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
                    runPath = os.path.join(
                        os.environ.get(
                            MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""), stepPath)
                else:
                    runPath = stepPath
                customStep = imp.load_source(fileName, runPath)
                if hasattr(customStep, "CustomShifterStep"):
                    cs = customStep.CustomShifterStep()
                    cs.run(customStepDic)
                    customStepDic[cs.name] = cs
                    pm.displayInfo(
                        "SUCCEED: Custom Shifter Step Class: %s. "
                        "Succeed!!" % stepPath)
                else:
                    pm.displayInfo(
                        "SUCCEED: Custom Step simple script: %s. "
                        "Succeed!!" % stepPath)

        except Exception as ex:
            template = "An exception of type {0} occured. "
            "Arguments:\n{1!r}"
            message = template.format(type(ex).__name__, ex.args)
            pm.displayError(message)
            pm.displayError(traceback.format_exc())
            cont = pm.confirmBox(
                "FAIL: Custom Step Fail",
                "The step:%s has failed. Continue with next step?" %
                stepPath + "\n\n" + message + "\n\n" +
                traceback.format_exc(),
                "Continue", "Stop Build", "Try Again!")
            if cont == "Stop Build":
                # stop Build
                return True
            elif cont == "Try Again!":
                try:  # just in case there is nothing to undo
                    pm.undo()
                except Exception:
                    pass
                pm.displayInfo("Trying again! : {}".format(stepPath))
                inception = self.runStep(stepPath, customStepDic)
                if inception:  # stops build from the recursion loop.
                    return True
            else:
                return False

    def runManualStep(self, widgetList):
        selItems = widgetList.selectedItems()
        for item in selItems:
            self.runStep(item.text().split("|")[-1][1:], customStepDic={})

class GuideSettingsTab(QtWidgets.QDialog, guui.Ui_Form):

    def __init__(self, parent=None):
        super(guideSettingsTab, self).__init__(parent)
        self.setupUi(self)

class CustomStepTab(QtWidgets.QDialog, csui.Ui_Form):

    def __init__(self, parent=None):
        super(customStepTab, self).__init__(parent)
        self.setupUi(self)

class GuideSettings(MayaQWidgetDockableMixin, QtWidgets.QDialog, HelperSlots):
    # valueChanged = QtCore.Signal(int)
    greenBrush = QtGui.QBrush()
    greenBrush.setColor('#179e83')
    redBrush = QtGui.QBrush()
    redBrush.setColor('#9b2d22')
    whiteBrush = QtGui.QBrush()
    whiteBrush.setColor('#ffffff')
    whiteDownBrush = QtGui.QBrush()
    whiteDownBrush.setColor('#E2E2E2')
    orangeBrush = QtGui.QBrush()
    orangeBrush.setColor('#e67e22')

    def __init__(self, parent=None):
        self.toolName = TYPE
        # Delete old instances of the componet settings window.
        pyqt.deleteInstances(self, MayaQDockWidget)
        # super(self.__class__, self).__init__(parent=parent)
        super(guideSettings, self).__init__()
        # the inspectSettings function set the current selection to the
        # component root before open the settings dialog
        self.root = pm.selected()[0]

        self.guideSettingsTab = guideSettingsTab()
        self.customStepTab = customStepTab()

        self.setup_SettingWindow()
        self.create_controls()
        self.populate_controls()
        self.create_layout()
        self.create_connections()

        self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)

    def setup_SettingWindow(self):
        self.mayaMainWindow = pyqt.maya_main_window()

        self.setObjectName(self.toolName)
        self.setWindowFlags(QtCore.Qt.Window)
        self.setWindowTitle(TYPE)
        self.resize(500, 615)

    def create_controls(self):
        """Create the controls for the component base"""
        self.tabs = QtWidgets.QTabWidget()
        self.tabs.setObjectName("settings_tab")

        # Close Button
        self.close_button = QtWidgets.QPushButton("Close")

    def populate_controls(self):
        """Populate the controls values
            from the custom attributes of the component.

        """
        # populate tab
        self.tabs.insertTab(0, self.guideSettingsTab, "Guide Settings")
        self.tabs.insertTab(1, self.customStepTab, "Custom Steps")

        # populate main settings
        self.guideSettingsTab.rigName_lineEdit.setText(
            self.root.attr("rig_name").get())
        self.guideSettingsTab.mode_comboBox.setCurrentIndex(
            self.root.attr("mode").get())
        self.guideSettingsTab.step_comboBox.setCurrentIndex(
            self.root.attr("step").get())
        self.populateCheck(
            self.guideSettingsTab.proxyChannels_checkBox, "proxyChannels")

        self.populateCheck(self.guideSettingsTab.worldCtl_checkBox, "worldCtl")

        self.populateCheck(
            self.guideSettingsTab.classicChannelNames_checkBox,
            "classicChannelNames")
        self.populateCheck(
            self.guideSettingsTab.importSkin_checkBox, "importSkin")
        self.guideSettingsTab.skin_lineEdit.setText(
            self.root.attr("skin").get())
        self.populateCheck(
            self.guideSettingsTab.jointRig_checkBox, "joint_rig")
        self.populateAvailableSynopticTabs()

        for item in self.root.attr("synoptic").get().split(","):
            self.guideSettingsTab.rigTabs_listWidget.addItem(item)

        self.guideSettingsTab.L_color_fk_spinBox.setValue(
            self.root.attr("L_color_fk").get())
        self.guideSettingsTab.L_color_ik_spinBox.setValue(
            self.root.attr("L_color_ik").get())
        self.guideSettingsTab.C_color_fk_spinBox.setValue(
            self.root.attr("C_color_fk").get())
        self.guideSettingsTab.C_color_ik_spinBox.setValue(
            self.root.attr("C_color_ik").get())
        self.guideSettingsTab.R_color_fk_spinBox.setValue(
            self.root.attr("R_color_fk").get())
        self.guideSettingsTab.R_color_ik_spinBox.setValue(
            self.root.attr("R_color_ik").get())

        # pupulate custom steps sttings
        self.populateCheck(
            self.customStepTab.preCustomStep_checkBox, "doPreCustomStep")
        for item in self.root.attr("preCustomStep").get().split(","):
            self.customStepTab.preCustomStep_listWidget.addItem(item)
        self.refreshStatusColor(self.customStepTab.preCustomStep_listWidget)

        self.populateCheck(
            self.customStepTab.postCustomStep_checkBox, "doPostCustomStep")
        for item in self.root.attr("postCustomStep").get().split(","):
            self.customStepTab.postCustomStep_listWidget.addItem(item)
        self.refreshStatusColor(self.customStepTab.postCustomStep_listWidget)

    def create_layout(self):
        """
        Create the layout for the component base settings

        """
        self.settings_layout = QtWidgets.QVBoxLayout()
        self.settings_layout.addWidget(self.tabs)
        self.settings_layout.addWidget(self.close_button)

        self.setLayout(self.settings_layout)

    def create_connections(self):
        """Create the slots connections to the controls functions"""
        self.close_button.clicked.connect(self.close_settings)

        # Setting Tab
        tap = self.guideSettingsTab
        tap.rigName_lineEdit.editingFinished.connect(
            partial(self.updateLineEdit,
                    tap.rigName_lineEdit,
                    "rig_name"))
        tap.mode_comboBox.currentIndexChanged.connect(
            partial(self.updateComboBox,
                    tap.mode_comboBox,
                    "mode"))
        tap.step_comboBox.currentIndexChanged.connect(
            partial(self.updateComboBox,
                    tap.step_comboBox,
                    "step"))
        tap.proxyChannels_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    tap.proxyChannels_checkBox,
                    "proxyChannels"))
        tap.worldCtl_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    tap.worldCtl_checkBox,
                    "worldCtl"))
        tap.classicChannelNames_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    tap.classicChannelNames_checkBox,
                    "classicChannelNames"))
        tap.importSkin_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    tap.importSkin_checkBox,
                    "importSkin"))
        tap.jointRig_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    tap.jointRig_checkBox,
                    "joint_rig"))
        tap.addTab_pushButton.clicked.connect(
            partial(self.moveFromListWidget2ListWidget,
                    tap.available_listWidget,
                    tap.rigTabs_listWidget,
                    tap.rigTabs_listWidget,
                    "synoptic"))
        tap.removeTab_pushButton.clicked.connect(
            partial(self.moveFromListWidget2ListWidget,
                    tap.rigTabs_listWidget,
                    tap.available_listWidget,
                    tap.rigTabs_listWidget,
                    "synoptic"))
        tap.loadSkinPath_pushButton.clicked.connect(
            self.skinLoad)
        tap.rigTabs_listWidget.installEventFilter(self)

        tap.L_color_fk_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.L_color_fk_spinBox,
                    "L_color_fk"))
        tap.L_color_ik_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.L_color_ik_spinBox,
                    "L_color_ik"))
        tap.C_color_fk_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.C_color_fk_spinBox,
                    "C_color_fk"))
        tap.C_color_ik_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.C_color_ik_spinBox,
                    "C_color_ik"))
        tap.R_color_fk_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.R_color_fk_spinBox,
                    "R_color_fk"))
        tap.R_color_ik_spinBox.valueChanged.connect(
            partial(self.updateSpinBox,
                    tap.R_color_ik_spinBox,
                    "R_color_ik"))

        # custom Step Tab
        csTap = self.customStepTab
        csTap.preCustomStep_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    csTap.preCustomStep_checkBox,
                    "doPreCustomStep"))
        csTap.preCustomStepAdd_pushButton.clicked.connect(
            self.addCustomStep)
        csTap.preCustomStepNew_pushButton.clicked.connect(
            self.newCustomStep)
        csTap.preCustomStepDuplicate_pushButton.clicked.connect(
            self.duplicateCustomStep)
        csTap.preCustomStepExport_pushButton.clicked.connect(
            self.exportCustomStep)
        csTap.preCustomStepImport_pushButton.clicked.connect(
            self.importCustomStep)
        csTap.preCustomStepRemove_pushButton.clicked.connect(
            partial(self.removeSelectedFromListWidget,
                    csTap.preCustomStep_listWidget,
                    "preCustomStep"))
        csTap.preCustomStep_listWidget.installEventFilter(self)
        csTap.preCustomStepRun_pushButton.clicked.connect(
            partial(self.runManualStep,
                    csTap.preCustomStep_listWidget))
        csTap.preCustomStepEdit_pushButton.clicked.connect(
            partial(self.editFile,
                    csTap.preCustomStep_listWidget))

        csTap.postCustomStep_checkBox.stateChanged.connect(
            partial(self.updateCheck,
                    csTap.postCustomStep_checkBox,
                    "doPostCustomStep"))
        csTap.postCustomStepAdd_pushButton.clicked.connect(
            partial(self.addCustomStep, False))
        csTap.postCustomStepNew_pushButton.clicked.connect(
            partial(self.newCustomStep, False))
        csTap.postCustomStepDuplicate_pushButton.clicked.connect(
            partial(self.duplicateCustomStep, False))
        csTap.postCustomStepExport_pushButton.clicked.connect(
            partial(self.exportCustomStep, False))
        csTap.postCustomStepImport_pushButton.clicked.connect(
            partial(self.importCustomStep, False))
        csTap.postCustomStepRemove_pushButton.clicked.connect(
            partial(self.removeSelectedFromListWidget,
                    csTap.postCustomStep_listWidget,
                    "postCustomStep"))
        csTap.postCustomStep_listWidget.installEventFilter(self)
        csTap.postCustomStepRun_pushButton.clicked.connect(
            partial(self.runManualStep,
                    csTap.postCustomStep_listWidget))
        csTap.postCustomStepEdit_pushButton.clicked.connect(
            partial(self.editFile,
                    csTap.postCustomStep_listWidget))

        # right click menus
        csTap.preCustomStep_listWidget.setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        csTap.preCustomStep_listWidget.customContextMenuRequested.connect(
            self.preCustomStepMenu)
        csTap.postCustomStep_listWidget.setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        csTap.postCustomStep_listWidget.customContextMenuRequested.connect(
            self.postCustomStepMenu)

        # search hightlight
        csTap.preSearch_lineEdit.textChanged.connect(
            self.preHighlightSearch)
        csTap.postSearch_lineEdit.textChanged.connect(
            self.postHighlightSearch)

    def eventFilter(self, sender, event):
        if event.type() == QtCore.QEvent.ChildRemoved:
            if sender == self.guideSettingsTab.rigTabs_listWidget:
                self.updateListAttr(sender, "synoptic")
            elif sender == self.customStepTab.preCustomStep_listWidget:
                self.updateListAttr(sender, "preCustomStep")
            elif sender == self.customStepTab.postCustomStep_listWidget:
                self.updateListAttr(sender, "postCustomStep")
            return True
        else:
            return QtWidgets.QDialog.eventFilter(self, sender, event)

    # Slots ########################################################

    def populateAvailableSynopticTabs(self):

        import mgear.maya.shifter as shifter
        defPath = os.environ.get("MGEAR_SYNOPTIC_PATH", None)
        if not defPath or not os.path.isdir(defPath):
            defPath = shifter.SYNOPTIC_PATH

        tabsDirectories = [name for name in os.listdir(defPath) if
                           os.path.isdir(os.path.join(defPath, name))]
        # Quick clean the first empty item
        if tabsDirectories and not tabsDirectories[0]:
            self.guideSettingsTab.available_listWidget.takeItem(0)

        itemsList = self.root.attr("synoptic").get().split(",")
        for tab in sorted(tabsDirectories):
            if tab not in itemsList:
                self.guideSettingsTab.available_listWidget.addItem(tab)

    def skinLoad(self, *args):
        startDir = self.root.attr("skin").get()
        filePath = pm.fileDialog2(
            dialogStyle=2,
            fileMode=1,
            startingDirectory=startDir,
            okc="Apply",
            fileFilter='mGear skin (*%s)' % skin.FILE_EXT)
        if not filePath:
            return
        if not isinstance(filePath, basestring):
            filePath = filePath[0]

        self.root.attr("skin").set(filePath)
        self.guideSettingsTab.skin_lineEdit.setText(filePath)

    def addCustomStep(self, pre=True, *args):
        """Add a new custom step

        Arguments:
            pre (bool, optional): If true adds the steps to the pre step list
            *args: Maya's Dummy

        Returns:
            None: None
        """

        if pre:
            stepAttr = "preCustomStep"
            stepWidget = self.customStepTab.preCustomStep_listWidget
        else:
            stepAttr = "postCustomStep"
            stepWidget = self.customStepTab.postCustomStep_listWidget

        # Check if we have a custom env for the custom steps initial folder
        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            startDir = os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, "")
        else:
            startDir = self.root.attr(stepAttr).get()

        filePath = pm.fileDialog2(
            dialogStyle=2,
            fileMode=1,
            startingDirectory=startDir,
            okc="Add",
            fileFilter='Custom Step .py (*.py)')
        if not filePath:
            return
        if not isinstance(filePath, basestring):
            filePath = filePath[0]

        # Quick clean the first empty item
        itemsList = [i.text() for i in stepWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        if itemsList and not itemsList[0]:
            stepWidget.takeItem(0)

        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            filePath = os.path.abspath(filePath)
            baseReplace = os.path.abspath(os.environ.get(
                MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""))
            filePath = filePath.replace(baseReplace, "")[1:]

        fileName = os.path.split(filePath)[1].split(".")[0]
        stepWidget.addItem(fileName + " | " + filePath)
        self.updateListAttr(stepWidget, stepAttr)

    def newCustomStep(self, pre=True, *args):
        """Creates a new custom step

        Arguments:
            pre (bool, optional): If true adds the steps to the pre step list
            *args: Maya's Dummy

        Returns:
            None: None
        """

        if pre:
            stepAttr = "preCustomStep"
            stepWidget = self.customStepTab.preCustomStep_listWidget
        else:
            stepAttr = "postCustomStep"
            stepWidget = self.customStepTab.postCustomStep_listWidget

        # Check if we have a custom env for the custom steps initial folder
        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            startDir = os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, "")
        else:
            startDir = self.root.attr(stepAttr).get()

        filePath = pm.fileDialog2(
            dialogStyle=2,
            fileMode=0,
            startingDirectory=startDir,
            okc="New",
            fileFilter='Custom Step .py (*.py)')
        if not filePath:
            return
        if not isinstance(filePath, basestring):
            filePath = filePath[0]

        n, e = os.path.splitext(filePath)
        stepName = os.path.split(n)[-1]
        # raw custome step string
        rawString = r'''
import mgear.maya.shifter.customStep as cstp

class CustomShifterStep(cstp.customShifterMainStep):
    def __init__(self):
        self.name = "%s"

    def run(self, stepDict):
        """Run method.

            i.e:  stepDict["mgearRun"].global_ctl  gets the global_ctl from
                    shifter rig on post step
            i.e:  stepDict["otherCustomStepName"].ctlMesh  gets the ctlMesh
                    from a previous custom step called "otherCustomStepName"
        Arguments:
            stepDict (dict): Dictionary containing the objects from
                the previous steps

        Returns:
            None: None
        """
        return''' % stepName
        f = open(filePath, 'w')
        f.write(rawString + "\n")
        f.close()

        # Quick clean the first empty item
        itemsList = [i.text() for i in stepWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        if itemsList and not itemsList[0]:
            stepWidget.takeItem(0)

        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            filePath = os.path.abspath(filePath)
            baseReplace = os.path.abspath(os.environ.get(
                MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""))
            filePath = filePath.replace(baseReplace, "")[1:]

        fileName = os.path.split(filePath)[1].split(".")[0]
        stepWidget.addItem(fileName + " | " + filePath)
        self.updateListAttr(stepWidget, stepAttr)

    def duplicateCustomStep(self, pre=True, *args):
        """Duplicate the selected step

        Arguments:
            pre (bool, optional): If true adds the steps to the pre step list
            *args: Maya's Dummy

        Returns:
            None: None
        """

        if pre:
            stepAttr = "preCustomStep"
            stepWidget = self.customStepTab.preCustomStep_listWidget
        else:
            stepAttr = "postCustomStep"
            stepWidget = self.customStepTab.postCustomStep_listWidget

        # Check if we have a custom env for the custom steps initial folder
        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            startDir = os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, "")
        else:
            startDir = self.root.attr(stepAttr).get()

        if stepWidget.selectedItems():
            sourcePath = stepWidget.selectedItems()[0].text().split(
                "|")[-1][1:]

        filePath = pm.fileDialog2(
            dialogStyle=2,
            fileMode=0,
            startingDirectory=startDir,
            okc="New",
            fileFilter='Custom Step .py (*.py)')
        if not filePath:
            return
        if not isinstance(filePath, basestring):
            filePath = filePath[0]

        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            sourcePath = os.path.join(startDir, sourcePath)
        shutil.copy(sourcePath, filePath)

        # Quick clean the first empty item
        itemsList = [i.text() for i in stepWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        if itemsList and not itemsList[0]:
            stepWidget.takeItem(0)

        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            filePath = os.path.abspath(filePath)
            baseReplace = os.path.abspath(os.environ.get(
                MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""))
            filePath = filePath.replace(baseReplace, "")[1:]

        fileName = os.path.split(filePath)[1].split(".")[0]
        stepWidget.addItem(fileName + " | " + filePath)
        self.updateListAttr(stepWidget, stepAttr)

    def exportCustomStep(self, pre=True, *args):
        """Export custom steps to a json file

        Arguments:
            pre (bool, optional): If true takes the steps from the
                pre step list
            *args: Maya's Dummy

        Returns:
            None: None

        """

        if pre:
            stepWidget = self.customStepTab.preCustomStep_listWidget
        else:
            stepWidget = self.customStepTab.postCustomStep_listWidget

        # Quick clean the first empty item
        itemsList = [i.text() for i in stepWidget.findItems(
            "", QtCore.Qt.MatchContains)]
        if itemsList and not itemsList[0]:
            stepWidget.takeItem(0)

        # Check if we have a custom env for the custom steps initial folder
        if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
            startDir = os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, "")
            itemsList = [os.path.join(startDir, i.text().split("|")[-1][1:])
                         for i in stepWidget.findItems(
                         "", QtCore.Qt.MatchContains)]
        else:
            itemsList = [i.text().split("|")[-1][1:]
                         for i in stepWidget.findItems(
                         "", QtCore.Qt.MatchContains)]
            if itemsList:
                startDir = os.path.split(itemsList[-1])[0]
            else:
                pm.displayWarning("No custom steps to export.")
                return

        stepsDict = {}
        stepsDict["itemsList"] = itemsList
        for item in itemsList:
            step = open(item, "r")
            data = step.read()
            stepsDict[item] = data
            step.close()

        data_string = json.dumps(stepsDict, indent=4, sort_keys=True)
        filePath = pm.fileDialog2(
            dialogStyle=2,
            fileMode=0,
            startingDirectory=startDir,
            fileFilter='Shifter Custom Steps .scs (*%s)' % ".scs")
        if not filePath:
            return
        if not isinstance(filePath, basestring):
            filePath = filePath[0]
        f = open(filePath, 'w')
        f.write(data_string)
        f.close()

    def importCustomStep(self, pre=True, *args):
        """Import custom steps from a json file

        Arguments:
            pre (bool, optional): If true import to pre steps list
            *args: Maya's Dummy

        Returns:
            None: None

        """

        if pre:
            stepAttr = "preCustomStep"
            stepWidget = self.customStepTab.preCustomStep_listWidget
        else:
            stepAttr = "postCustomStep"
            stepWidget = self.customStepTab.postCustomStep_listWidget

        # option import only paths or unpack steps
        option = pm.confirmDialog(
            title='Shifter Custom Step Import Style',
            message='Do you want to import only the path or'
                    ' unpack and import?',
            button=['Only Path', 'Unpack', 'Cancel'],
            defaultButton='Only Path',
            cancelButton='Cancel',
            dismissString='Cancel')

        if option in ['Only Path', 'Unpack']:
            if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
                startDir = os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, "")
            else:
                startDir = pm.workspace(q=True, rootDirectory=True)

            filePath = pm.fileDialog2(
                dialogStyle=2,
                fileMode=1,
                startingDirectory=startDir,
                fileFilter='Shifter Custom Steps .scs (*%s)' % ".scs")
            if not filePath:
                return
            if not isinstance(filePath, basestring):
                filePath = filePath[0]
            stepDict = json.load(open(filePath))
            stepsList = []

        if option == 'Only Path':
            for item in stepDict["itemsList"]:
                stepsList.append(item)

        elif option == 'Unpack':
            unPackDir = pm.fileDialog2(
                dialogStyle=2,
                fileMode=2,
                startingDirectory=startDir)
            if not filePath:
                return
            if not isinstance(unPackDir, basestring):
                unPackDir = unPackDir[0]

            for item in stepDict["itemsList"]:
                fileName = os.path.split(item)[1]
                fileNewPath = os.path.join(unPackDir, fileName)
                stepsList.append(fileNewPath)
                f = open(fileNewPath, 'w')
                f.write(stepDict[item])
                f.close()

        if option in ['Only Path', 'Unpack']:

            for item in stepsList:
                # Quick clean the first empty item
                itemsList = [i.text() for i in stepWidget.findItems(
                    "", QtCore.Qt.MatchContains)]
                if itemsList and not itemsList[0]:
                    stepWidget.takeItem(0)

                if os.environ.get(MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""):
                    item = os.path.abspath(item)
                    baseReplace = os.path.abspath(os.environ.get(
                        MGEAR_SHIFTER_CUSTOMSTEP_KEY, ""))
                    item = item.replace(baseReplace, "")[1:]

                fileName = os.path.split(item)[1].split(".")[0]
                stepWidget.addItem(fileName + " | " + item)
                self.updateListAttr(stepWidget, stepAttr)

    def _customStepMenu(self, cs_listWidget, stepAttr, QPos):
        "right click context menu for custom step"
        currentSelection = cs_listWidget.currentItem()
        if currentSelection is None:
            return
        self.csMenu = QtWidgets.QMenu()
        parentPosition = cs_listWidget.mapToGlobal(QtCore.QPoint(0, 0))
        menu_item_01 = self.csMenu.addAction("Toggle Custom Step")
        self.csMenu.addSeparator()
        menu_item_02 = self.csMenu.addAction("Turn OFF Selected")
        menu_item_03 = self.csMenu.addAction("Turn ON Selected")
        self.csMenu.addSeparator()
        menu_item_04 = self.csMenu.addAction("Turn OFF All")
        menu_item_05 = self.csMenu.addAction("Turn ON All")

        menu_item_01.triggered.connect(partial(self.toggleStatusCustomStep,
                                               cs_listWidget,
                                               stepAttr))
        menu_item_02.triggered.connect(partial(self.setStatusCustomStep,
                                               cs_listWidget,
                                               stepAttr,
                                               False))
        menu_item_03.triggered.connect(partial(self.setStatusCustomStep,
                                               cs_listWidget,
                                               stepAttr,
                                               True))
        menu_item_04.triggered.connect(partial(self.setStatusCustomStep,
                                               cs_listWidget,
                                               stepAttr,
                                               False,
                                               False))
        menu_item_05.triggered.connect(partial(self.setStatusCustomStep,
                                               cs_listWidget,
                                               stepAttr,
                                               True,
                                               False))

        self.csMenu.move(parentPosition + QPos)
        self.csMenu.show()

    def preCustomStepMenu(self, QPos):
        self._customStepMenu(self.customStepTab.preCustomStep_listWidget,
                             "preCustomStep",
                             QPos)

    def postCustomStepMenu(self, QPos):
        self._customStepMenu(self.customStepTab.postCustomStep_listWidget,
                             "postCustomStep",
                             QPos)

    def toggleStatusCustomStep(self, cs_listWidget, stepAttr):
        items = cs_listWidget.selectedItems()
        for item in items:
            if item.text().startswith("*"):
                item.setText(item.text()[1:])
                item.setForeground(self.whiteDownBrush)
            else:
                item.setText("*" + item.text())
                item.setForeground(self.redBrush)

        self.updateListAttr(cs_listWidget, stepAttr)

    def setStatusCustomStep(
            self, cs_listWidget, stepAttr, status=True, selected=True):
        if selected:
            items = cs_listWidget.selectedItems()
        else:
            items = self.getAllItems(cs_listWidget)
        for item in items:
            off = item.text().startswith("*")
            if status and off:
                item.setText(item.text()[1:])
            elif not status and not off:
                item.setText("*" + item.text())
            self.setStatusColor(item)
        self.updateListAttr(cs_listWidget, stepAttr)

    def getAllItems(self, cs_listWidget):
        return [cs_listWidget.item(i) for i in range(cs_listWidget.count())]

    def setStatusColor(self, item):
        if item.text().startswith("*"):
            item.setForeground(self.redBrush)
        else:
            item.setForeground(self.whiteDownBrush)

    def refreshStatusColor(self, cs_listWidget):
        items = self.getAllItems(cs_listWidget)
        for i in items:
            self.setStatusColor(i)

    # Highligter filter
    def _highlightSearch(self, cs_listWidget, searchText):
        items = self.getAllItems(cs_listWidget)
        for i in items:
            if searchText and searchText.lower() in i.text().lower():
                i.setBackground(QtGui.QColor(128, 128, 128, 255))
            else:
                i.setBackground(QtGui.QColor(255, 255, 255, 0))

    def preHighlightSearch(self):
        searchText = self.customStepTab.preSearch_lineEdit.text()
        self._highlightSearch(self.customStepTab.preCustomStep_listWidget,
                              searchText)

    def postHighlightSearch(self):
        searchText = self.customStepTab.postSearch_lineEdit.text()
        self._highlightSearch(self.customStepTab.postCustomStep_listWidget,
                              searchText)

# Backwards compatibility aliases
MainGuide = Main
RigGuide = Rig
helperSlots = HelperSlots
guideSettingsTab = GuideSettingsTab
customStepTab = CustomStepTab
guideSettings = GuideSettings


from setuptools import setup, find_packages

requirements = [
    "numpy",
    # needed until pycircstat is updated
    "scipy <= 1.2.1",
    "pandas",
    "matplotlib",
    "seaborn",
    "pycircstat",
    "nose",
    "decorator",
    "xlrd",
    "imlib >= 0.0.22",
    "packaging",
    "fancylog >= 0.0.8",
    "movement",
    "spikey >= 0.0.5",
    "vedo",
]

setup(
    name="opendirection",
    version="0.1.0",
    description="Analysis of spiking activity in an open field",
    packages=find_packages(),
    include_package_data=True,
    install_requires=requirements,
    extras_require={
        "dev": [
            "sphinx",
            "recommonmark",
            "sphinx_rtd_theme",
            "pydoc-markdown",
            "black",
            "pytest-cov",
            "pytest",
            "gitpython",
            "coverage>=5.0.3",
        ]
    },
    entry_points={
        "console_scripts": [
            "opendirection = opendirection.main:main",
            "opendirection_batch = opendirection.batch:main",
            "opendirection_multiexp = opendirection.multiexp:main",
            "gen_velo_profile = opendirection.utils.generate_velo_profile:main",
        ]
    },
    url="https://github.com/adamltyson/opendirection",
    author="Adam Tyson",
    author_email="adam.tyson@ucl.ac.uk",
)


# -*- coding: utf-8 -*-
# @Time    : 2023/8/13 21:07
# @Author  : chenxi
# @FileName: 7.py
# @Software: PyCharm
# from typing import Annotated

from typing_extensions import Annotated
import uvicorn
from fastapi import FastAPI, Path, Body
from pydantic import BaseModel
from typing import Union

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None

class User(BaseModel):
    username: str
    fill_name: Union[str, None] = None

@app.put("/items/{item_id}")
async def update_item(
    # item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
    # q: str | None = None,
    # item: Item | None = None,
    # Body() 注解的参数会自动解析为请求体Json
    item_id: int,
    item: Annotated[Item, Body(embed=True)]
):
    results = {"item_id": item_id, "item": item}

    return results

if __name__ == "__main__":
    uvicorn.run("7:app", host="127.0.0.1", port=8000)