import re
from sympy import lambdify, symbols, MatrixSymbol, Matrix, diff, srepr
from sympy.parsing.sympy_parser import (parse_expr,standard_transformations, implicit_application)
transformations = standard_transformations + (implicit_application,)
import numpy as np
from system_of_equation_solver.__common_parameters__ import LOGGER_NAME, DTYPE, CONVERGENCE_THRESH, MAX_ITER, l_key
import system_of_equation_solver.__logger__
from system_of_equation_solver.__paths__ import root_path_obj
import logging
logger = logging.getLogger(LOGGER_NAME)

import json
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go

from itertools import product

class SysEqnSolver(object):
    """
    --> takes path of func text as input
    --> find zeros of that function via NKM if initial conditions are satisfied
    """
    def __init__(self,
                 func_str_path
                 #mode = params['mode'],
                 #vec_norm = params['vec_norm']
        ):

        self._layout = go.Layout(
                title='{}'.format(func_str_path.split('/')[-1]),
            showlegend=True,
            xaxis=dict(
                title='x Axis',
                titlefont=dict(
                    family='Courier New, monospace',
                    size=18,
                    color='#7f7f7f'
                )
            ),
            yaxis=dict(
                title='y Axis',
                titlefont=dict(
                    family='Courier New, monospace',
                    size=18,
                    color='#7f7f7f'
                )
            )
        )

        with open(root_path_obj.joinpath('__params__.json'), 'r') as f:
            params_dict = json.load(f)

        self._params = params_dict

        if self._params['mode'] == 'NKM':
            logger.info('The current mode is Newton Kantorovich')
        elif self._params['vec_norm'] == 'MNKM':
            logger.info('The current mode is Modifoed Newton Kantorovich')
        else:
            logger.error('Mode value should be "NKM" or "MNKM"')
            return

        func = []

        self._func_path = func_str_path
        try:
            with open(self._func_path, 'r') as file_obj:
                lines = [line.strip() for line in file_obj.readlines()]
                func = lines[1:]
                self._syms = symbols(lines[0])
        except:
            logger.exception('file at path could not be read')

        del lines

        self._F = Matrix([parse_expr(row, transformations=transformations) for row in func])
        logger.debug('F{} = {}'.format(self._syms, self._F))

        self._DF = Matrix([[diff(func, sym) for sym in self._syms] for func in self._F])
        logger.debug('DF{} = {}'.format(self._syms, self._DF))

        self._x_conv = None
        self._x0 = None
        self._steps = 0

        self._vec_shape = (len(self._syms), 1)

        self._f_vals = []
        self._f_norms = []
        self._all_x = []

        del func

    def make_array(self, *x):
        return np.array(x, dtype=DTYPE).reshape(self._vec_shape)

    def initialize_x_conv(self, *args):
        self._x_conv = self.make_array(*args)
        self._x0 = self.make_array(*args)
        self._steps = 0

        self._f_vals = []
        self._f_norms = []
        self._all_x = []

        logger.debug('initial value of x = {}'.format(self._x_conv))

    def operator_norm(self, numerical_matrix):
        """
        --> the corresponding norm for an operator defined from l_inf to l_inf
        :param numerical_matrix:
        :return:
        """
        norm = None

        if numerical_matrix is None:
            return norm

        try:
            norm = np.linalg.norm(numerical_matrix, l_key[self._params['vec_norm']])
            logger.debug('operator norm = {}'.format(norm))
        except:
            logger.exception('operator norm could not be calculated')

        return norm

    def vector_norm(self, numerical_vector):
        """
        --> l_inf norm
        :param numerical_vector:
        :return:

        """
        norm = None

        if numerical_vector is None:
            return norm

        try:
            norm = np.linalg.norm(numerical_vector, l_key[self._params['vec_norm']])
            logger.debug('vector norm = {}'.format(norm))
        except:
            logger.exception('vector norn could not be calculated')

        return norm

    def calc_numeric_F(self, *args):
        f = None

        try:
            f = lambdify(self._syms, self._F, 'numpy')(*args).reshape(self._vec_shape).astype(DTYPE)
            logger.debug('F{} = {}'.format(args, f))
        except:
            logger.exception('F = {}, could not be calculated at {}'.format(self._F, args))

        return f

    def numpify(self):
        return lambdify(self._syms, self._F, 'numpy')

    def calc_numeric_DF(self, *args):
        df = None

        try:
            df = lambdify(self._syms, self._DF, 'numpy')(*args).astype(DTYPE)
            logger.debug('DF{} = {}'.format(args, df))
        except:
            logger.exception('DF = {}, could not be calculated at {}'.format(self._DF, args))

        return df

    def calc_numeric_DF_inv(self, *args):
        df_inv = None

        try:
            df = lambdify(self._syms, self._DF, 'numpy')(*args).astype(DTYPE)

            if df is not None:
                df_inv = np.linalg.inv(df)
                logger.debug('DF_inv{} = {}'.format(args, df_inv))
        except:
            logger.exception('DF_inv = {}, could not be calculated at {}'.format(self._DF, args))

        return df_inv

    def calc_F_lip(self):
        with open(self._func_path, 'r') as file_obj:
            text = file_obj.read()
            for sym in text.split('\n')[0].split(' '):
                text = re.sub(r'[{}]'.format(sym), '{}_1'.format(sym), text)

            lines = [line.strip() for line in text.split('\n')]
            func1 = lines[1:]
            syms1 = symbols(lines[0])

        del lines, text

        _F1 = Matrix([parse_expr(row, transformations=transformations) for row in func1])
        _DF1 = Matrix([[diff(func, sym) for sym in syms1] for func in _F1])

        return self._DF - _DF1

    def calc_centre_F_lip(self):
        with open(self._func_path, 'r') as file_obj:
            text = file_obj.read()
            for sym in text.split('\n')[0].split(' '):
                text = re.sub(r'[{}]'.format(sym), '{}_1'.format(sym), text)

            lines = [line.strip() for line in text.split('\n')]
            func1 = lines[1:]
            syms1 = symbols(lines[0])

        del lines, text

        _F1 = Matrix([parse_expr(row, transformations=transformations) for row in func1])
        _DF1 = Matrix([[diff(func, sym) for sym in syms1] for func in _F1])

        _temp = self._x0.reshape((self._vec_shape[0],)).tolist()

        df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        m = Matrix(df_x0_inv) * (self._DF - _DF1)
        return m
        # for row_idx in range(m.rows):
        #     for expr in m.row(row_idx):
        #         coeffs = expr.as_coefficients_dict()
        #

    def calc_centre0_F_lip(self):
        _temp = self._x0.reshape((self._vec_shape[0],)).tolist()

        df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        return Matrix(df_x0_inv) * self._DF - Matrix.eye(self._vec_shape[0])

    def calculate_eta0(self):
        _temp = self._x0.reshape((self._vec_shape[0],)).tolist()

        df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        f_x0 = self.calc_numeric_F(*_temp)
        logger.debug('F{} = {}'.format(_temp, f_x0))

        if df_x0_inv is None or f_x0 is None:
            return None

        eta0 = self.vector_norm(np.matmul(df_x0_inv, f_x0))
        logger.debug('eta0 = {}'.format(eta0))

        return eta0

    def calculate_initial_conditions(self, l):

        if self._x_conv is None:
            logger.error('initialize x_conv before calculating initial conditions')
            return None

        logger.debug('calculating initial conditions')

        _temp = self._x0.reshape((self._vec_shape[0],)).tolist()
        df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        f_x0 = self.calc_numeric_F(*_temp)
        logger.debug('F{} = {}'.format(_temp, f_x0))

        if df_x0_inv is None or f_x0 is None:
            return None

        eta0 = self.vector_norm(np.matmul(df_x0_inv, f_x0))
        logger.debug('eta0 = {}'.format(eta0))

        if eta0 is None:
            return None

        h = l * eta0 * 0.5
        logger.debug('h = {}'.format(h))

        if h > 0.5:
            logger.error('h must be less than 0.5 but is {}'.format(round(h, 2)))
            return None

        r0 = np.multiply(1 - np.sqrt(1 - 2 * h), eta0) / h
        logger.debug('r0 = {}'.format(r0))

        return {
            'eta0': eta0,
            'h': h,
            'r0': r0
        }

    def find_convergence_points_in_region(self, l):

        axis_points = [np.linspace(axis[0], axis[1], num=self._params['grid_size']) for axis in self._params['region']]
        grid_points = np.array(np.meshgrid(*axis_points))
        print(grid_points.shape)
        convergent_points = []
        for grid_idx in product(*[range(grid_points.shape[i]) for i in range(1,len(grid_points.shape))]):
            cell_coord = [slice(None)] + list(grid_idx)
            point = grid_points[tuple(cell_coord)].tolist()
            self.initialize_x_conv(*point)
            init_cond = self.calculate_initial_conditions(l)
            if init_cond is not None:
                flag_point_good = True
                for i in range(len(point)):
                    if point[i] - init_cond['r0'] < self._params['region'][i][0] and \
                        point[i] - init_cond['r0'] > self._params['region'][i][1]:
                        flag_point_good = False
                        break
                if flag_point_good:
                    convergent_points.append(point)
        return convergent_points

    def update_x_conv(self, f_prev = None):
        logger.debug('updating x_conv')

        _temp = self._x_conv.reshape((self._vec_shape[0],)).tolist()

        df_x0_inv = None

        if self._params['mode'] == 'NKM':
            df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        elif self._params['mode'] == 'MNKM':
            _x0 = self._x0.reshape((self._vec_shape[0],)).tolist()
            df_x0_inv = self.calc_numeric_DF_inv(*_x0)

        if df_x0_inv is None:
            logger.error('The update step calculation has failed')
            return

        if f_prev is None:
            f_x0 = self.calc_numeric_F(*_temp)
        else:
            f_x0 = f_prev

        self._x_conv -= np.matmul(df_x0_inv, f_x0)
        self._steps += 1

    def simulate(self, l):
        logger.debug('simulating {}'.format(self._params['mode']))

        init = self.calculate_initial_conditions(l)

        if init is None:
            logger.error('initial conditions could not be calculated for {}'.format(self._params['mode']))
            return

        while self._steps < MAX_ITER:
            logger.debug('calculating f norm at updated x')
            _temp = self._x_conv.reshape((self._vec_shape[0],)).tolist()
            f_updated = self.calc_numeric_F(*_temp)
            f_norm = self.vector_norm(f_updated)

            self._f_norms.append(f_norm)
            self._f_vals.append(f_updated)
            self._all_x.append(_temp)

            if f_updated is None:
                logger.error('method could not converge since f can not be calculated at the updated x')
                break

            if f_norm <= CONVERGENCE_THRESH:
                logger.info('{} converged succesfully in {} iterations'.format(self._params['mode'], self._steps))
                break

            self.update_x_conv(f_prev=f_updated)

    def plot(self):
        data = [
                go.Scatter(x=list(range(len(self._f_norms))), y=self._f_norms, connectgaps=True, mode = 'lines+markers+text',
                           line = dict(color='red', width = 2,), marker=dict(color='red', size=15, opacity=1),
                          text = [str(np.round(x, 2).tolist()) for x in self._all_x]),
            ]
        fig = dict(data=data, layout=self._layout)
        return fig

class SysEqnSolverOLD(SysEqnSolver):
    def calculate_initial_conditions(self, l):

        if self._x_conv is None:
            logger.error('initialize x_conv before calculating initial conditions')
            return None

        logger.debug('calculating initial conditions')

        _temp = self._x0.reshape((self._vec_shape[0],)).tolist()
        df_x0_inv = self.calc_numeric_DF_inv(*_temp)

        f_x0 = self.calc_numeric_F(*_temp)
        logger.debug('F{} = {}'.format(_temp, f_x0))

        if df_x0_inv is None or f_x0 is None:
            return None

        b0 = self.operator_norm(df_x0_inv)
        logger.debug('b0 = {}'.format(b0))

        f_x0 = self.calc_numeric_F(*_temp)
        logger.debug('F{} = {}'.format(_temp, f_x0))

        eta0 = self.vector_norm(np.matmul(df_x0_inv, f_x0))
        logger.debug('eta0 = {}'.format(eta0))

        if b0 is None or eta0 is None:
            return None

        h = l * b0 * eta0
        logger.debug('h = {}'.format(h))

        if h > 0.5:
            logger.error('h must be less than 0.5 but is {}'.format(round(h, 2)))
            return None

        r0 = np.multiply(1 - np.sqrt(1 - 2 * h), eta0) / h
        logger.debug('r0 = {}'.format(r0))

        return {
            'b0': b0,
            'eta0': eta0,
            'h': h,
            'r0': r0
        }


from PyQt6.QtWidgets import QWidget, QGridLayout, QLabel, QVBoxLayout
from PyQt6.QtWidgets import QGridLayout, QLabel
from PyQt6.QtGui import QPixmap
import os
import numpy as np
import random

class SentenceWidget(QWidget):
    def __init__(self, data_size=None, max_sentences=5):
        super().__init__()

        self.sentences = None
        self.selected_sentences = []
        self.selected_points = []
        self.max_sentences = max_sentences
        self.data_size = data_size

        self.layout = QVBoxLayout()

        header_label = QLabel("Sample Sentences")
        self.layout.addWidget(header_label)

        self.bullet_points = []
        for _ in range(self.max_sentences):
            bullet_point = QLabel("")
            bullet_point.setWordWrap(True)
            self.layout.addWidget(bullet_point)
            self.bullet_points.append(bullet_point)

        self.setLayout(self.layout)

    def update_sentences(self, sentences):
        self.sentences = sentences
        self.set_selected_points(list(range(len(self.sentences))))

    def update(self):
        """Update the widget with the current sentences"""
        sentence = "test sentence"
        for i, sentence in enumerate(self.selected_sentences):
            self.bullet_points[i].setText(f"• {sentence}")
        if len(self.selected_sentences) < self.max_sentences:
            for i in range(len(self.selected_sentences), self.max_sentences):
                self.bullet_points[i].setText("")

    def set_selected_points(self, selected_points):
        """Method that sets the selected sentences and updates the widget"""
        if self.sentences is None:
            return
        if selected_points==[]:
            #self.selected_sentences = random.sample(self.sentences, min(len(self.sentences), self.max_sentences))
            self.selected_points = []
            self.selected_sentences = []
        else:
            self.selected_points = selected_points
            self.selected_sentences = random.sample(self.sentences.iloc[self.selected_points].tolist(),min(len(self.sentences.iloc[self.selected_points]),self.max_sentences))

        self.update()


#!/usr/bin/env python
#coding:utf-8
import pika,time

credentials = pika.PlainCredentials('admin', 'admin')
#链接rabbit服务器（localhost是本机，如果是其他服务器请修改为ip地址）
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='192.168.1.210', port=5672, credentials=credentials, virtual_host='/'))
channel = connection.channel()

channel.queue_declare(queue='hello')

channel.basic_publish(exchange='',
                      routing_key='hello',
                      body='Hello World!')
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(" [x] 发送消息：  当前时间是： %s" % now)
connection.close()


# -*- coding: utf-8 -*-
import openpyxl
import os
import copy
import csv

all_file = os.listdir()
all_program = []
all_computers = []
clear_list = []
ignor_list = []

#  функция загркузки xlsx файла
def load_xlsx(file):
    xls = file.split('.')[-1]
    if xls == 'xlsx':
        wb = openpyxl.load_workbook(file)
        sheet = wb['Лист1']
        return sheet

# ищем все программы по всех файлах и составляем список всех программ
for file in all_file:
    sheet = load_xlsx(file)
    if sheet is not None:
        for i in range(1, 200):
            prog_name = sheet.cell(row=i, column=1).value
            if prog_name is None:
                break
            if not prog_name in all_program:
                all_program.append(prog_name)

# пишем список всех программ в текстовый файл
if os.path.isfile('all_prog.txt') is not True:
    with open('all_prog.txt', 'a', encoding='utf-8') as f_obj:
        for proga in all_program:
            string = f'{proga}\n'
            f_obj.write(string)

# Подгружаем в память список игнорируемых программ
with open('ignor_list.txt', 'r', encoding='utf-8') as ignor:
    for proga in ignor:
        proga = proga.split('\n')[0]
        ignor_list.append(proga)

# чистим список с программами
for ignor in ignor_list:
    if ignor in all_program:
        index = all_program.index(ignor)
        all_program.pop(index)

# сортируем список по алфавиту
all_program.sort()
all_program.insert(0, 'Комп')

# генерис список, который потом будем заоплнять программами в будущей таблице
clear_list = [x*0 for x in range(len(all_program))]

# сверяем программы из каждого файла со списком всех программ
for file in all_file:
    sheet = load_xlsx(file)
    if sheet is not None:
        buffer = copy.deepcopy(clear_list)
        for i in range(1, 200):
            prog_name = sheet.cell(row=i, column=1).value
            if prog_name is None:
                break
            for index, count in enumerate(all_program):
                if prog_name == count or buffer[index] == 1:
                    buffer[index] = 1
                else:
                    buffer[index] = 0
        buffer[0] = file
        all_computers.append(buffer)
        buffer = clear_list

# записываем результат в отчет в формате csv
result = "result.csv"
data = [[1, 1, 1], [2,2, 2], [3, 3, 3]]
with open(result, "w", newline='') as csv_file:
    writer = csv.writer(csv_file, delimiter=',')
    for index, comp in enumerate(all_computers):
        if index == 0:
            writer.writerow(all_program)
        writer.writerow(comp)


from event import Event
import boto3
import os

if os.environ.get('AWS_EXECUTION_ENV'):
    from aws_xray_sdk.core import xray_recorder
    from aws_xray_sdk.core import patch_all

    patch_all()

ssm = boto3.client('ssm',region_name='us-east-2')

signature_public_key_parameter = ssm.get_parameter(
    Name='/external/accounts/dev/sso_signature_public_key',
    WithDecryption=True
)
encryption_private_key_parameter = ssm.get_parameter(
    Name='/external/accounts/dev/sso_encryption_private_key',
    WithDecryption=True
)
sso_cookie_name_parameter = ssm.get_parameter(
    Name='/external/accounts/dev/sso_cookie_name',
    WithDecryption=True
)

from oxauth import Strategy2

strategy = Strategy2(
    signature_public_key=signature_public_key_parameter['Parameter']['Value'],
    signature_algorithm="RS256",
    encryption_private_key=encryption_private_key_parameter['Parameter']['Value'],
    encryption_algorithm="A256GCM",
    encryption_method="dir"
)

class Output:
    def __init__(self):
        self.set_user_uuid(None)

    def set_user_uuid(self, value):
        self.__user_uuid = value

    def set_country_code(self, value):
        self.__country_code = value

    def to_dict(self):
        return {
            'general': {
                'user_uuid': self.__user_uuid or 'not logged in',
                'country_code': self.__country_code
            },
            'reading_progress': {
                'a_book_uuid': 'a_page_uuid'
            },
            'accounts': {
                'faculty_status': 'confirmed_faculty (dummy value for now)'
            },
            'diagnostics': {
                'region': os.environ.get('AWS_REGION'),
                'hostname': os.environ.get('HOSTNAME'),
                'lambda_event_body': os.environ.get('AWS_LAMBDA_EVENT_BODY'),
                'aws_execution_env': os.environ.get('AWS_EXECUTION_ENV'),
                'lambda_task_root': os.environ.get('LAMBDA_TASK_ROOT'),
            }
        }

def lambda_handler(event, context):
    event = Event(event)
    request = event.request()

    sso_cookie = request.parsedCookies().get(sso_cookie_name_parameter['Parameter']['Value'])

    output = Output()

    if sso_cookie != None:
        payload = strategy.decrypt(sso_cookie)
        output.set_user_uuid(payload.user_uuid)

    output.set_country_code(request.country_code())

    return render_diagnostic(output.to_dict())

def render_diagnostic(something):
    content = f"""
        <html>
            <head>
                <meta name="robots" content="noindex">
                <title>Me Spike diagnostics</title>
            </head>
            <body>
                {something}
            </body>
        </html>
        """

    response = {
        'status': '200',
        'headers': {
            'x-robots-tag': [{
                'key': 'X-Robots-Tag',
                'value': 'noindex'
            }]
        },
        'body': content
    }

    return response


from django.urls import path, include
from . import views
from .api import PostResource, UserResource

app_name = 'blog'

urlpatterns = [
    path('', views.index, name='index'),
    path('posts/<id>/', views.detail, name='detail'),
    path('create_post/', views.create_post, name='create_post'),
    path('posts/<id>/edit', views.edit_post, name='edit'),
    path('posts/<id>/delete', views.delete_post, name='delete'),
    path('posts/<id>/like', views.like_post, name='like'),
    path('posts/<id>/like_index', views.like_post_index, name='like_index'),
    path('api/posts/', include(PostResource.urls())),
    path('api/users/', include(UserResource.urls())),

]


# To run: python issue_gen.py --root_directory=<parent directory of cfn-lint>
import argparse
import ast
import json
import os
from pathlib import Path
from textwrap import dedent
from typing import Dict, List, Optional, Union
from urllib.parse import unquote, urlparse

def concat_binop(binop: ast.AST) -> str:
    """
    Recursively concatenate binary operation nodes into a single string.

    Args:
        binop (ast.AST): Binary operation node.

    Returns:
        str: Concatenated string.
    """
    if isinstance(binop, ast.BinOp):
        return concat_binop(binop.left) + concat_binop(binop.right)
    if isinstance(binop, ast.Constant):
        return binop.value
    return ""

def extract_class_attributes(node: ast.ClassDef) -> Dict[str, Union[str, List[str]]]:
    """
    Extract class attributes from a ClassDef node in an abstract syntax tree.

    Args:
        node (ast.ClassDef): ClassDef node.

    Returns:
        Dict[str, Union[str, List[str]]]: Extracted class attributes.
    """
    class_data = {}
    for item in node.body:
        if isinstance(item, ast.Assign):
            for target in item.targets:
                if target.id in ["id", "shortdesc", "description", "source_url"]:
                    class_data[target.id] = concat_binop(item.value)
                elif target.id == "tags":
                    class_data["tags"] = [
                        concat_binop(element) for element in item.value.elts
                    ]
    return class_data

def extract_attributes_from_code(code: str) -> Dict[str, Union[str, List[str]]]:
    """
    Extract attributes from Python code.

    Args:
        code (str): Python code as a string.

    Returns:
        Dict[str, Union[str, List[str]]]: Extracted attributes.
    """
    class_data = {}
    tree = ast.parse(code)
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef):
            class_data.update(extract_class_attributes(node))
    return class_data

def extract_page_name(url: str) -> Optional[str]:
    """
    Extract the page name from a URL.

    Args:
        url (str): Input URL.

    Returns:
        Optional[str]: Extracted page name or None if not found.
    """
    parsed_url = urlparse(url)
    path_segments = parsed_url.path.strip("/").split("/")
    if path_segments:
        last_segment = os.path.splitext(path_segments[-1])[0]
        page_name = unquote(last_segment.replace("-", " ")).title()
        return page_name.replace("Cfn", "CloudFormation").replace(
            "Cloudformation", "CloudFormation"
        )
    return None

def build_toml(issue: Dict[str, Union[str, List[str]]]) -> str:
    """
    Build a TOML string from issue data.

    Args:
        issue (Dict[str, Union[str, List[str]]]): Issue data.

    Returns:
        str: TOML string.
    """
    title = issue["shortdesc"]
    description = issue["description"]
    source_url = issue.get("source_url", "")
    tags = issue.get("tags", [])

    content = f"""\
    title = {json.dumps(title.capitalize())}
    verbose_name = "{issue["id"]}"
    severity = "major"
    category = "antipattern"
    weight = 70
    tags = [{", ".join([json.dumps(tag) for tag in tags])}]
    description = '''
    {description}

    ### References:
    [{extract_page_name(source_url)}]({source_url})
    '''
    """
    return dedent(content)

def write_to_file(issue: Dict[str, Union[str, List[str]]]) -> None:
    """
    Write issue data to a TOML file.

    Args:
        issue (Dict[str, Union[str, List[str]]]): Issue data.
    """
    file_name = f"./issues/CFLIN-{issue['id']}.toml"
    with open(file_name, "w") as file:
        file.write(build_toml(issue))

def extract_attributes_from_directory(
    directory: str,
) -> List[Dict[str, Union[str, List[str]]]]:
    """
    Extract attributes from Python files in a directory.

    Args:
        directory (str): Root directory to search for Python files.

    Returns:
        List[Dict[str, Union[str, List[str]]]]: List of extracted attributes.
    """
    all_classes_data = []
    for root, _, files in os.walk(directory):
        for file in files:
            if file.endswith(".py"):
                file_path = os.path.join(root, file)
                with open(file_path, "r", encoding="utf-8") as f:
                    try:
                        code = f.read()
                        class_data = extract_attributes_from_code(code)
                        if class_data:
                            all_classes_data.append(class_data)
                    except Exception as e:
                        print(f"Error parsing file {file}: {e}")
    return all_classes_data

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Extract attributes from Python files in a given directory."
    )
    parser.add_argument(
        "--root_directory", help="Root directory of the cfn-lint repository."
    )
    args = parser.parse_args()

    base = Path(args.root_directory)
    path = Path("cfn-lint/src/cfnlint/rules")
    rules_directory = base / path

    attributes_list = extract_attributes_from_directory(rules_directory)
    for attributes in attributes_list:
        write_to_file(attributes)


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

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

import scrapy
from scrapy import Field

class BaseParent(scrapy.Item):
    base = Field() #上面的基本信息
    threat = Field() # 威胁情报检测
    IP = Field()  #IP反查，IP分析
    child_domains = Field() #子域名
    whois = Field() # Whois
    certificates = Field() # 数字证书
    uuid = Field()
    keyword = Field()

# 基本信息
class Base(scrapy.Item):
    ip = Field() # ip地址
    address = Field() # 地理位置
    asn = Field() # ASN
    tags = Field() # 标签
    domain_provider = Field() # 域名服务商
    domain_server = Field() # 域名服务器
    alex_rank = Field() # Alex 排名

# 威胁情报
class Threat(scrapy.Item):
    threat_test = Field()  # 威胁情报检测
    sample = Field() # 威胁情报样本

# 威胁情报检测
class Test(scrapy.Item):
    source = Field() # 情报源
    find_time = Field() # 发现时间
    type = Field() # 情报类型

# 相关样本
class Sample(scrapy.Item):
    SHA256 = Field()
    family = Field() # 病毒家族
    type = Field() # 病毒类型
    rate = Field() # 病毒检出率
    analysis_time = Field() # 分析时间
# IP反查，IP分析
class Ip(scrapy.Item):
    his_domains = Field() # 历史域名记录 History
    domains = Field() # 指向同一IP的域名列表
    his_ana = Field() #历史解析

# 历史解析记录
class Hisdomain(scrapy.Item):
    time = Field()
    domain = Field()

# 历史解析记录
class Hisana(scrapy.Item):
    time = Field()
    ips = Field()
    country = Field()
    province = Field()
#  Whois
class Whois(scrapy.Item):
    cu_registy = Field() # 当前注册信息
    his_registy = Field() # 历史注册信息

# 当前注册信息
class CRegisty(scrapy.Item):
    registrar = Field()
    institution = Field()
    email = Field()
    address = Field()
    phone = Field()
    re_time = Field()
    over_time = Field()
    update_time = Field()
    provider = Field()
    server = Field()

# 历史注册信息
class HRegisty(scrapy.Item):
    date = Field()
    message = Field()


import logging
import time
from abc import ABC, abstractmethod
from logging import Logger
from typing import Any, Optional

from robot_interface.models.exceptions.robot_exceptions import (
    RobotRetrieveInspectionException,
)
from roslibpy import Message, Ros
from roslibpy import Topic as RosTopic

from isar_turtlebot.settings import settings

class TopicInterface(ABC):
    @abstractmethod
    def publish(self, message: Any) -> None:
        pass

    @abstractmethod
    def get_value(self) -> Optional[Any]:
        pass

class ImageTopicInterface(ABC):
    @abstractmethod
    def get_image(self) -> bytes:
        pass

class Topic(TopicInterface):
    def __init__(
        self,
        client: Ros,
        name: str,
        message_type: str,
        throttle_rate: int = 0,
        queue_size: int = 100,
        queue_length: int = 0,
        log_callbacks: bool = False,
    ) -> None:
        self.name: str = name
        self.topic: RosTopic = RosTopic(
            ros=client,
            name=name,
            message_type=message_type,
            throttle_rate=throttle_rate,
            queue_size=queue_size,
            queue_length=queue_length,
        )

        self.log_callbacks: bool = log_callbacks
        if self.log_callbacks:
            self.logger: Logger = logging.getLogger("turtlebot_bridge")

        self.value: Optional[Any] = None

        self.subscribe()

    def publish(self, message: Any) -> None:
        self.topic.publish(Message(message))

    def get_value(self) -> Optional[Any]:
        return self.value

    def on_message(self, message: dict) -> None:
        self.value = message
        if self.log_callbacks:
            self.logger.debug(f"Updated value for topic {self.name}")

    def subscribe(self) -> None:
        self.topic.subscribe(self.on_message)

class ImageTopic(ImageTopicInterface):
    def __init__(
        self,
        client: Ros,
        name: str,
        message_type: str,
        throttle_rate: int = 0,
        queue_size: int = 100,
        queue_length: int = 0,
        log_callbacks: bool = False,
        get_image_timeout: float = settings.GET_IMAGE_TIMEOUT,
    ) -> None:
        self.name: str = name
        self.topic: RosTopic = RosTopic(
            ros=client,
            name=name,
            message_type=message_type,
            throttle_rate=throttle_rate,
            queue_size=queue_size,
            queue_length=queue_length,
        )
        self.log_callbacks: bool = log_callbacks
        if self.log_callbacks:
            self.logger: Logger = logging.getLogger("turtlebot_bridge")

        self.image: Optional[bytes] = None
        self.take_image: bool = False
        self.get_image_timeout: float = get_image_timeout

        self.subscribe()

    def publish(self, message: Any) -> None:
        self.topic.publish(Message(message))

    def subscribe(self) -> None:
        self.topic.subscribe(self.on_image)

    def on_image(self, message: dict) -> None:
        if self.take_image:
            self.image = message["data"].encode("ascii")
        else:
            self.image = None

        if self.log_callbacks:
            self.logger.debug(f"Updated value for topic {self.name}")

    def get_image(self) -> Optional[bytes]:
        self.take_image = True
        start_time = time.time()
        while not self.image:
            time.sleep(0.1)
            execution_time: float = time.time() - start_time
            if execution_time > self.get_image_timeout:
                error_description: str = "Timed our while fetching the image"
                self.logger.error(error_description)
                raise RobotRetrieveInspectionException(
                    error_description=error_description
                )

        self.take_image = False
        return self.image


from OpenGL.GL import (
    glBindTexture,
    GL_TEXTURE_2D,
    glTexImage2D,
    GL_RGBA,
    GL_UNSIGNED_BYTE,
    glGenTextures,
    glTexParameteri,
    GL_TEXTURE_MIN_FILTER,
    GL_NEAREST,
    GL_CLAMP_TO_EDGE,
    GL_TEXTURE_MAG_FILTER,
    GL_TEXTURE_WRAP_S,
    GL_TEXTURE_WRAP_T,
)
from typing import Generator, Any, Tuple, Dict, Optional
import struct
import hashlib
import os
import json
from PIL import Image
import numpy
import glob
import logging

from minecraft_model_reader.api.resource_pack.base import BaseResourcePackManager
from minecraft_model_reader import BlockMesh
import PyMCTranslate
from amulet.api.block import Block

from amulet_map_editor.api.opengl import textureatlas

log = logging.getLogger(__name__)

class OpenGLResourcePack:
    """This class will take a minecraft_model_reader resource pack and
    load all of the textures into a texture atlas."""

    _translator: PyMCTranslate.Version
    _block_models: Dict[Block, BlockMesh]
    _texture_bounds: Dict[Any, Tuple[float, float, float, float]]
    _image: Optional[numpy.ndarray]
    _image_width: int
    _image_height: int
    _gl_textures: Dict[str, int]

    def __init__(
        self, resource_pack: BaseResourcePackManager, translator: PyMCTranslate.Version
    ):
        self._resource_pack = resource_pack
        self._translator = translator
        self._block_models: Dict[Block, BlockMesh] = {}

        self._texture_bounds: Dict[str, Tuple[float, float, float, float]] = {}
        self._image: Optional[Image.Image] = None
        self._image_width: int = 0
        self._image_height: int = 0

        self._gl_textures: Dict[str, int] = {}

    def get_atlas_id(self, context_id: str) -> int:
        """Get the opengl texture id of the atlas for a given context."""
        if context_id not in self._gl_textures:
            if self._image is None:
                raise Exception(
                    "OpenGLResourcePack.setup() needs to be run before accessing a texture."
                )
            self._setup_texture(context_id)
        return self._gl_textures[context_id]

    def get_texture_path(self, namespace: Optional[str], relative_path: str):
        """Get the absolute path of the image from the relative components.
        Useful for getting the id of textures for hard coded textures not connected to a resource pack.
        """
        return self._resource_pack.get_texture_path(namespace, relative_path)

    def texture_bounds(self, texture_path: str) -> Tuple[float, float, float, float]:
        """Get the bounding box of a given texture path."""
        if texture_path in self._texture_bounds:
            return self._texture_bounds[texture_path]
        else:
            return self._texture_bounds[self._resource_pack.missing_no]

    @property
    def translator(self) -> PyMCTranslate.Version:
        """The translator used to convert the universal blocks into the required version for the resource pack."""
        return self._translator

    def setup(self) -> Generator[float, None, None]:
        """Create and bind the atlas texture."""
        if self._image is None:
            cache_id = struct.unpack(
                "H",
                hashlib.sha1(
                    "".join(self._resource_pack.pack_paths).encode("utf-8")
                ).digest()[:2],
            )[0]

            atlas: Image.Image

            if not self._resource_pack.pack_paths:
                log.warning("There are no resource packs to load.")

            mod_time = max(
                (
                    os.stat(path).st_mtime
                    for pack in self._resource_pack.pack_paths
                    for path in glob.glob(
                        os.path.join(glob.escape(pack), "**", "*.*"), recursive=True
                    )
                ),
                default=0,
            )

            cache_dir = os.path.join(os.environ["CACHE_DIR"], "resource_packs", "atlas")
            img_path = os.path.join(cache_dir, f"{cache_id}.png")
            bounds_path = os.path.join(cache_dir, f"{cache_id}.json")
            try:
                with open(bounds_path) as f:
                    cache_mod_time, bounds = json.load(f)
                if mod_time != cache_mod_time:
                    raise Exception(
                        "The resource packs have changed since last merging."
                    )
                atlas = Image.open(img_path)
            except:
                atlas_iter = textureatlas.create_atlas_iter(
                    self._resource_pack.textures
                )
                try:
                    while True:
                        yield next(atlas_iter)
                except StopIteration as e:
                    (
                        atlas,
                        bounds,
                    ) = e.value
                    os.makedirs(cache_dir, exist_ok=True)
                    atlas.save(img_path)
                    with open(bounds_path, "w") as f:
                        json.dump((mod_time, bounds), f)

            self._image_width, self._image_height = atlas.size
            self._image = numpy.array(atlas, numpy.uint8).ravel()
            self._texture_bounds = bounds

    def _setup_texture(self, context_id: str):
        """Set up the texture for a given context"""
        gl_texture = self._gl_textures[context_id] = glGenTextures(
            1
        )  # Create the texture location
        glBindTexture(GL_TEXTURE_2D, gl_texture)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

        glBindTexture(GL_TEXTURE_2D, gl_texture)
        glTexImage2D(
            GL_TEXTURE_2D,
            0,
            GL_RGBA,
            self._image_width,
            self._image_height,
            0,
            GL_RGBA,
            GL_UNSIGNED_BYTE,
            self._image,
        )
        glBindTexture(GL_TEXTURE_2D, 0)
        log.info("Finished setting up texture atlas in OpenGL")

    def get_block_model(self, universal_block: Block) -> BlockMesh:
        """Get the BlockMesh class for a given universal Block.
        The Block will be translated to the version format using the
        previously specified translator."""
        if universal_block not in self._block_models:
            version_block = self._translator.block.from_universal(
                universal_block.base_block
            )[0]
            if universal_block.extra_blocks:
                for block_ in universal_block.extra_blocks:
                    version_block += self._translator.block.from_universal(block_)[0]

            self._block_models[universal_block] = self._resource_pack.get_block_model(
                version_block
            )

        return self._block_models[universal_block]


import json
from openpyxl import load_workbook
from datetime import datetime

workbook = load_workbook(filename="Marksheet.xlsx", data_only=True)
sheet = workbook.active

grades = []
descriptor_levels = {
    1: "Not Achieved",
    2: "Elementary Achievement",
    3: "Moderate Achievement",
    4: "Adequate Achievement",
    5: "Substantial Achievement",
    6: "Meritorious Achievement",
    7: "Outstanding Achievement"
}

english_avg_mark = 0
xhosa_avg_mark = 0
life_skill_avg_mark = 0
social_science_avg_mark = 0
math_avg_mark = 0
natural_science_avg_mark = 0

def get_level(mark):
    if 0 <= mark <= 29:
        return 1
    elif 30 <= mark <= 39:
        return 2
    elif 40 <= mark <= 49:
        return 3
    elif 50 <= mark <= 59:
        return 4
    elif 60 <= mark <= 69:
        return 5
    elif 70 <= mark <= 79:
        return 6
    elif 80 <= mark <= 100:
        return 7

for item in sheet.iter_rows(min_row=49,
                           max_row=49,
                           min_col=4,
                           max_col=9,
                           values_only=True):
    english_avg_mark = item[0]
    xhosa_avg_mark = item[1]
    math_avg_mark = item[2]
    life_skill_avg_mark = item[3]
    natural_science_avg_mark = item[4]
    social_science_avg_mark = item[5]

for row in sheet.iter_rows(min_row=2,
                           max_row=47,
                           min_col=1,
                           max_col=12,
                           values_only=True):
    grade = {
        "full_name": row[0] + ", " + row[1],
        "english_mark": str(row[3]),
        "xhosa_mark": str(row[4]),
        "math_mark": str(row[5]),
        "life_skill_mark": str(row[6]),
        "natural_science_mark": str(row[7]),
        "social_science_mark": str(row[8]),
        "total_mark": str(row[9]),
        "avg_mark": str(round(row[10])),
        "passed": row[11],
        "english_level": str(get_level(row[3])),
        "xhosa_level": str(get_level(row[4])),
        "math_level": str(get_level(row[5])),
        "life_skill_level": str(get_level(row[6])),
        "natural_science_level": str(get_level(row[7])),
        "social_science_level": str(get_level(row[8])),
        "english_description": descriptor_levels.get(get_level(row[3]),""),
        "xhosa_description": descriptor_levels.get(get_level(row[4]),""),
        "math_description": descriptor_levels.get(get_level(row[5]),""),
        "life_skill_description": descriptor_levels.get(get_level(row[6]),""),
        "natural_science_description": descriptor_levels.get(get_level(row[7]),""),
        "social_science_description":descriptor_levels.get(get_level(row[8]),""),
        "english_avg_mark": str(round(english_avg_mark)),
        "xhosa_avg_mark": str(round(xhosa_avg_mark)),
        "math_avg_mark": str(round(math_avg_mark)),
        "life_skill_avg_mark": str(round(life_skill_avg_mark)),
        "natural_science_avg_mark": str(round(natural_science_avg_mark)),
        "social_science_avg_mark": str(round(social_science_avg_mark)),
    }

    grades.append(grade)

print(grades)
print(json.dumps(grades))


import logging

from Modules import Module
from System.Platform import Platform

class _GATKBase(Module):

    def __init__(self, module_id, is_docker=False):
        super(_GATKBase, self).__init__(module_id, is_docker)

        # Initialize the gatk version
        self.gatk_version = None

    def define_base_args(self):

        # Set GATK executable arguments
        self.add_argument("java",           is_required=True, is_resource=True)
        self.add_argument("gatk",           is_required=True, is_resource=True)
        self.add_argument("gatk_version",   is_required=True)

        # Set reference specific arguments
        self.add_argument("ref",            is_required=True, is_resource=True)
        self.add_argument("ref_idx",        is_required=True, is_resource=True)
        self.add_argument("ref_dict",       is_required=True, is_resource=True)

        # Set chromosome interval specific arguments
        self.add_argument("location")
        self.add_argument("excluded_location")
        self.add_argument("interval_list")
        self.add_argument("bed", is_resource=True)

    def get_gatk_version(self):

        # Generate the gatk version if it was not generated yet
        if self.gatk_version is None:

            gatk_version = self.get_argument("gatk_version")
            gatk_version = str(gatk_version).lower().replace("gatk", "")
            gatk_version = gatk_version.strip()
            self.gatk_version = int(gatk_version.split(".")[0])

        return self.gatk_version

    def get_gatk_command(self):
        # Get input arguments
        gatk    = self.get_argument("gatk")
        mem     = self.get_argument("mem")
        java = self.get_argument("java")
        jvm_options = "-Xmx{0}G -Djava.io.tmpdir={1}".format(mem * 4 // 5, "/tmp/")

        if self.get_gatk_version() < 4:
            return "{0} {1} -jar {2} -T".format(java, jvm_options, gatk)

        # Generate base command with endpoint provided by docker
        else:
            return "{0} {1} -jar {2}".format(java, jvm_options, gatk)

    def get_output_file_flag(self):
        """
        Function returns an appropriate output file flag for GATK tools based on GATK version
        Returns: Output file flag in Str format

        """

        if self.get_gatk_version() < 4:
            return "-o"

        return "-O"

class HaplotypeCaller(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(HaplotypeCaller, self).__init__(module_id, is_docker)
        self.output_keys = ["gvcf", "gvcf_idx", "vcf", "vcf_idx"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                    is_required=True)
        self.add_argument("bam_idx",                is_required=True)
        self.add_argument("BQSR_report",            is_required=False)
        self.add_argument("use_bqsr",               is_required=True, default_value=True)
        self.add_argument("output_type",            is_required=True, default_value="gvcf")
        self.add_argument("nr_cpus",                is_required=True, default_value=8)
        self.add_argument("mem",                    is_required=True, default_value=48)
        self.add_argument("use_soft_clipped_bases", is_required=True, default_value=True)

    def define_output(self):
        # Declare GVCF output filename
        randomer = Platform.generate_unique_id()
        # generate uniques file name based on the output mode set for the Haplotypecaller
        if self.get_argument("output_type") == "gvcf":
            gvcf = self.generate_unique_file_name(extension="{0}.g.vcf".format(randomer))
            self.add_output("gvcf", gvcf)
            # Declare GVCF index output filename
            gvcf_idx = self.generate_unique_file_name(extension="{0}.g.vcf.idx".format(randomer))
            self.add_output("gvcf_idx", gvcf_idx)
        else:
            vcf = self.generate_unique_file_name(extension="{0}.vcf".format(randomer))
            self.add_output("vcf", vcf)
            # Declare VCF index output filename
            vcf_idx = self.generate_unique_file_name(extension="{0}.vcf.idx".format(randomer))
            self.add_output("vcf_idx", vcf_idx)

    def define_command(self):
        # Get input arguments
        bam                    = self.get_argument("bam")
        BQSR                   = self.get_argument("BQSR_report")
        output_type            = self.get_argument("output_type")
        ref                    = self.get_argument("ref")
        L                      = self.get_argument("location")
        XL                     = self.get_argument("excluded_location")
        interval               = self.get_argument("interval_list")
        bed                    = self.get_argument("bed")

        gatk_cmd               = self.get_gatk_command()
        use_bqsr               = self.get_argument("use_bqsr")
        use_soft_clipped_bases = self.get_argument("use_soft_clipped_bases")
        nr_cpus                = self.get_argument("nr_cpus")

        output_file_flag = self.get_output_file_flag()

        # Generating the haplotype caller options
        opts = list()
        opts.append("-I {0}".format(bam))
        opts.append("-R {0}".format(ref))

        # Setting the output file based on the output mode
        if output_type == "gvcf":
            opts.append("{0} {1}".format(output_file_flag, self.get_output("gvcf")))
        else:
            opts.append("{0} {1}".format(output_file_flag, self.get_output("vcf")))

        # Setting the output mode
        if output_type == "gvcf":
            opts.append("-ERC GVCF")

        # Adding the BQSR for lower version of the GATK
        if BQSR is not None and self.get_gatk_version() < 4 and use_bqsr:
            opts.append("-BQSR {0}".format(BQSR))

        # Set the parallelism method
        if self.get_gatk_version() < 4:
            opts.append("-nct {0}".format(nr_cpus))
        else:
            opts.append("--native-pair-hmm-threads {0}".format(nr_cpus))

        # Limit the locations to be processed
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    if included != "unmapped":
                        opts.append("-L \"{0}\"".format(included))
            else:
                opts.append("-L \"{0}\"".format(L))
        if XL is not None:
            if isinstance(XL, list):
                for excluded in XL:
                    opts.append("-XL \"{0}\"".format(excluded))
            else:
                opts.append("-XL \"{0}\"".format(XL))

        # Check if an interval list was provided and if yes, place it
        if interval is not None:
            opts.append("-L {0}".format(interval))

        # Check if a BED file was provided and if yes, place it
        if bed is not None:
            opts.append("-L {0}".format(bed))

        # Add on flag for not using soft clipped bases if defined (used in RNAseq variant calling)
        if use_soft_clipped_bases == "False":
            opts.append("--dont-use-soft-clipped-bases")

        # Generating command for HaplotypeCaller
        return "{0} HaplotypeCaller {1} !LOG3!".format(gatk_cmd, " ".join(opts))

class PrintReads(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(PrintReads, self).__init__(module_id, is_docker)
        self.output_keys            = ["bam"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                is_required=True)
        self.add_argument("bam_idx",            is_required=True)
        self.add_argument("BQSR_report",        is_required=True)
        self.add_argument("nr_cpus",            is_required=True, default_value=2)
        self.add_argument("mem",                is_required=True, default_value="nr_cpus * 2.5")

    def define_output(self):
        # Declare bam output filename
        bam = self.generate_unique_file_name(extension=".recalibrated.bam")
        self.add_output("bam", bam)

    def define_command(self):
        # Obtaining the arguments
        bam     = self.get_argument("bam")
        BQSR    = self.get_argument("BQSR_report")
        ref     = self.get_argument("ref")
        L       = self.get_argument("location")
        XL      = self.get_argument("excluded_location")
        nr_cpus = self.get_argument("nr_cpus")
        output_bam = self.get_output("bam")
        gatk_cmd = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Generating the PrintReads caller options
        opts = list()
        opts.append("-I %s" % bam)
        opts.append("{0} {1}".format(output_file_flag, output_bam))
        opts.append("-nct %d" % nr_cpus)
        opts.append("-R %s" % ref)
        opts.append("-BQSR %s" % BQSR)

        # Limit the locations to be processed
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    opts.append("-L \"%s\"" % included)
            else:
                opts.append("-L \"%s\"" % L)
        if XL is not None:
            if isinstance(XL, list):
                for excluded in XL:
                    opts.append("-XL \"%s\"" % excluded)
            else:
                opts.append("-XL \"%s\"" % XL)

        # Generating command for GATK PrintReads
        return "%s PrintReads %s !LOG3!" % (gatk_cmd, " ".join(opts))

class ApplyBQSR(_GATKBase):
    # GATK 4 replacement for PrintReads
    def __init__(self, module_id, is_docker=False):
        super(ApplyBQSR, self).__init__(module_id, is_docker)
        self.output_keys            = ["bam", "bam_idx"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                is_required=True)
        self.add_argument("bam_idx",            is_required=True)
        self.add_argument("BQSR_report",        is_required=True)
        self.add_argument("nr_cpus",            is_required=True, default_value=2)
        self.add_argument("mem",                is_required=True, default_value="nr_cpus * 2.5")

    def define_output(self):
        # Declare bam output filename
        bam = self.generate_unique_file_name(extension=".recalibrated.bam")
        bam_idx = "{0}.bai".format(bam)
        self.add_output("bam", bam)
        self.add_output("bam_idx", bam_idx)

    def define_command(self):
        # Obtaining the arguments
        bam     = self.get_argument("bam")
        BQSR    = self.get_argument("BQSR_report")
        ref     = self.get_argument("ref")
        L       = self.get_argument("location")
        XL      = self.get_argument("excluded_location")
        output_bam      = self.get_output("bam")
        output_bam_idx  = self.get_output("bam_idx")
        tmp_bam_idx     = str(output_bam_idx).replace(".bam.bai", ".bai")
        gatk_cmd        = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Generating the ApplyBQSR caller options
        opts = list()
        opts.append("-I %s" % bam)
        opts.append("{0} {1}".format(output_file_flag, output_bam))
        opts.append("-R %s" % ref)
        opts.append("--bqsr-recal-file %s" % BQSR)

        # Limit the locations to be processed
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    opts.append("-L \"%s\"" % included)
            else:
                opts.append("-L \"%s\"" % L)
        if XL is not None:
            if isinstance(XL, list):
                for excluded in XL:
                    opts.append("-XL \"%s\"" % excluded)
            else:
                opts.append("-XL \"%s\"" % XL)

        # Generating command for GATK PrintReads
        bqsr_cmd = "%s ApplyBQSR %s !LOG3!" % (gatk_cmd, " ".join(opts))
        mv_cmd = "mv %s %s !LOG2!" % (tmp_bam_idx, output_bam_idx)
        return "%s ; %s" % (bqsr_cmd, mv_cmd)

class BaseRecalibrator(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(BaseRecalibrator, self).__init__(module_id, is_docker)
        self.output_keys    = ["BQSR_report"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                is_required=True)
        self.add_argument("bam_idx",            is_required=True)
        self.add_argument("dbsnp",              is_required=True, is_resource=True)
        self.add_argument("nr_cpus",            is_required=True, default_value=4)
        self.add_argument("mem",                is_required=True, default_value=12)

    def define_output(self):
        # Declare BQSR report file
        bqsr_report = self.generate_unique_file_name(extension=".grp")
        self.add_output("BQSR_report", bqsr_report)

    def define_command(self):
        # Get arguments needed to generate GATK BQSR command
        bam             = self.get_argument("bam")
        ref             = self.get_argument("ref")
        dbsnp           = self.get_argument("dbsnp")
        L               = self.get_argument("location")
        XL              = self.get_argument("excluded_location")
        nr_cpus         = self.get_argument("nr_cpus")
        bqsr_report     = self.get_output("BQSR_report")

        gatk_cmd        = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Generating the base recalibration options
        opts = list()
        opts.append("-I %s" % bam)
        opts.append("{0} {1}".format(output_file_flag, bqsr_report))
        opts.append("-R %s" % ref)

        if self.get_gatk_version() >= 4:
            opts.append("--known-sites %s" % dbsnp)
        else:
            opts.append("-knownSites %s" % dbsnp)
            opts.append("-nct %d" % nr_cpus)
            opts.append("-cov ReadGroupCovariate")
            opts.append("-cov QualityScoreCovariate")
            opts.append("-cov CycleCovariate")
            opts.append("-cov ContextCovariate")

        # Limit the locations to be processed
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    opts.append("-L \"%s\"" % included)
            else:
                opts.append("-L \"%s\"" % L)
        if XL is not None:
            if isinstance(XL, list):
                for excluded in XL:
                    opts.append("-XL \"%s\"" % excluded)
            else:
                opts.append("-XL \"%s\"" % XL)

        # Generating command for base recalibration
        return "{0} BaseRecalibrator {1} !LOG3!".format(gatk_cmd, " ".join(opts))

class IndexVCF(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(IndexVCF, self).__init__(module_id, is_docker)
        self.output_keys  = ["vcf", "vcf_idx"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("vcf",               is_required=True)
        self.add_argument("nr_cpus",            is_required=True, default_value=2)
        self.add_argument("mem",                is_required=True, default_value=13)

    def define_output(self):
        # Declare merged GVCF output filename
        vcf = self.generate_unique_file_name(extension=".vcf")
        self.add_output("vcf", vcf)
        # Declare GVCF index output filename
        vcf_idx = vcf + ".idx"
        self.add_output("vcf_idx", vcf_idx)

    def define_command(self):
        # Obtaining the arguments
        vcf_in  = self.get_argument("vcf")
        gatk    = self.get_argument("gatk")
        ref     = self.get_argument("ref")
        mem     = self.get_argument("mem")
        vcf_out = self.get_output("vcf")

        # Generate command with java if not running on docker
        java = self.get_argument("java")
        jvm_options = "-Xmx%dG -Djava.io.tmpdir=%s" % (mem * 4 // 5, "/tmp/")
        cmd = "%s %s -cp %s org.broadinstitute.gatk.tools.CatVariants" % (java, jvm_options, gatk)

        # Generating the CatVariants options
        opts = list()
        opts.append("-out {0}".format(vcf_out))
        opts.append("-R %s" % ref)
        opts.append("-V %s" % vcf_in)

        # Generating the IndexVCF cmd
        return "%s %s !LOG3!" % (cmd, " ".join(opts))

class FilterMutectCalls(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(FilterMutectCalls, self).__init__(module_id, is_docker)
        self.output_keys    = ["vcf"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("vcf",        is_required=True)
        self.add_argument("nr_cpus",    is_required=True,   default_value=1)
        self.add_argument("mem",        is_required=True,   default_value=2)

    def define_output(self):
        # Declare recoded VCF output filename
        vcf_out = self.generate_unique_file_name(extension=".vcf")
        self.add_output("vcf", vcf_out)

    def define_command(self):
        # Get input arguments
        vcf_in      = self.get_argument("vcf")
        gatk_cmd    = self.get_gatk_command()
        vcf_out     = self.get_output("vcf")

        output_file_flag = self.get_output_file_flag()

        return "{0} FilterMutectCalls -V {1} {3} {2} !LOG3!".format(gatk_cmd, vcf_in, vcf_out, output_file_flag)

class CollectReadCounts(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(CollectReadCounts, self).__init__(module_id, is_docker)
        self.output_keys = ["read_count_out"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",            is_required=True)
        self.add_argument("bam_idx",        is_required=True)
        self.add_argument("nr_cpus",        is_required=True,   default_value=1)
        self.add_argument("mem",            is_required=True,   default_value=2)

    def define_output(self):
        # Declare recoded VCF output filename
        read_count_out = self.generate_unique_file_name(extension=".read_count.txt")
        self.add_output("read_count_out", read_count_out)

    def define_command(self):
        # Get input arguments
        bam             = self.get_argument("bam")
        gatk_cmd        = self.get_gatk_command()
        read_count_out  = self.get_output("read_count_out")
        interval_list   = self.get_argument("interval_list")

        output_file_flag = self.get_output_file_flag()

        cmd = "{0} CollectReadCounts -I {1} {3} {2} --format TSV ".format(gatk_cmd, bam, read_count_out, output_file_flag)

        if interval_list is not None:
            cmd = "{0} -L {1} --interval-merging-rule OVERLAPPING_ONLY".format(cmd, interval_list)

        return "{0} !LOG3!".format(cmd)

class BedToIntervalList(_GATKBase):
    def __init__(self, module_id, is_docker=False):
        super(BedToIntervalList, self).__init__(module_id, is_docker)
        self.output_keys = ["interval_list"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bed",        is_required=True, is_resource=True)
        self.add_argument("nr_cpus",    is_required=True, default_value=1)
        self.add_argument("mem",        is_required=True, default_value=2)

    def define_output(self):
        # Declare recoded VCF output filename
        interval_list = self.generate_unique_file_name(extension=".interval.list")
        self.add_output("interval_list", interval_list)

    def define_command(self):

        # Get input arguments
        bed             = self.get_argument("bed")
        dict_file       = self.get_argument("ref_dict")
        gatk_cmd        = self.get_gatk_command()
        interval_list   = self.get_output("interval_list")

        output_file_flag = self.get_output_file_flag()

        return "{0} BedToIntervalList -I {1} {4} {2} -SD {3} !LOG3!".format(gatk_cmd, bed, interval_list, dict_file,
                                                                            output_file_flag)

class GenotypeGenomicsDB(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(GenotypeGenomicsDB, self).__init__(module_id, is_docker)
        self.output_keys = ["vcf", "vcf_idx"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("genomicsDB", is_required=True)
        self.add_argument("nr_cpus",    is_required=True, default_value=4)
        self.add_argument("mem",        is_required=True, default_value=16)

    def define_output(self):
        # Declare VCF output filename
        vcf = self.generate_unique_file_name(extension=".vcf")
        self.add_output("vcf", vcf)
        # Declare VCF index output filename
        vcf_idx = self.generate_unique_file_name(extension=".vcf.idx")
        self.add_output("vcf_idx", vcf_idx)

    def define_command(self):
        # Get input arguments
        genomics_db = self.get_argument("genomicsDB")
        ref         = self.get_argument("ref")
        L           = self.get_argument("location")
        vcf         = self.get_output("vcf")

        # Make JVM options and GATK command
        gatk_cmd = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Generating the haplotype caller options
        opts = list()

        if isinstance(genomics_db, list):
            for gdb in genomics_db:
                opts.append("-V gendb://{0}".format(gdb))
        else:
            opts.append("-V gendb://{0}".format(genomics_db))

        opts.append("{1} {0}".format(vcf, output_file_flag))
        opts.append("-R {0}".format(ref))

        # Limit the locations to be processes
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    opts.append("-L \"%s\"" % included)
            else:
                opts.append("-L \"%s\"" % L)

            # Add option to restrict vcf to intervals
            opts.append("--only-output-calls-starting-in-intervals")

        # Generating command for base recalibration
        return "{0} GenotypeGVCFs {1} !LOG3!".format(gatk_cmd, " ".join(opts))

class SplitNCigarReads(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(SplitNCigarReads, self).__init__(module_id, is_docker)
        self.output_keys  = ["bam"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                is_required=True)
        self.add_argument("bam_idx",            is_required=True)
        self.add_argument("nr_cpus",            is_required=True, default_value=2)
        self.add_argument("mem",                is_required=True, default_value="nr_cpus * 2.5")

    def define_output(self):
        # Declare BAM output filename
        bam = self.generate_unique_file_name(extension=".split.bam")
        self.add_output("bam", bam)

    def define_command(self):
        # Get arguments needed to generated a SplitNCigarReads command
        bam        = self.get_argument("bam")
        output_bam = self.get_output("bam")
        ref        = self.get_argument("ref")

        # Get JVM options and GATK command
        gatk_cmd = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Generating the options for splitting reads with N in cigar string
        opts = list()
        opts.append("-R {0}".format(ref))
        opts.append("-I {0}".format(bam))
        opts.append("{0} {1}".format(output_file_flag, output_bam))

        if self.get_gatk_version() < 4:
            opts.append("-rf ReassignOneMappingQuality")
            opts.append("-RMQF 255")
            opts.append("-RMQT 60")
            opts.append("-U ALLOW_N_CIGAR_READS")

        # Generate command for splitting reads
        return "{0} SplitNCigarReads {1} !LOG3!".format(gatk_cmd, " ".join(opts))

class Mutect2(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(Mutect2, self).__init__(module_id, is_docker)
        self.output_keys = ["vcf", "vcf_idx"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",                is_required=True)
        self.add_argument("bam_idx",            is_required=True)
        self.add_argument("sample_name",        is_required=True)
        self.add_argument("is_tumor",           is_required=True)
        self.add_argument("germline_vcf",       is_required=False,  is_resource=True)
        self.add_argument("nr_cpus",            is_required=True,   default_value=8)
        self.add_argument("mem",                is_required=True,   default_value=30)

    def define_output(self):
        # Declare VCF output filename
        vcf = self.generate_unique_file_name(extension=".vcf")
        self.add_output("vcf", vcf)
        # Declare VCF index output filename
        vcf_idx = self.generate_unique_file_name(extension=".vcf.idx")
        self.add_output("vcf_idx", vcf_idx)

    def define_command(self):
        # Get input arguments
        bams            = self.get_argument("bam")
        ref             = self.get_argument("ref")
        germline_vcf    = self.get_argument("germline_vcf")
        L               = self.get_argument("location")
        XL              = self.get_argument("excluded_location")
        nr_cpus         = self.get_argument("nr_cpus")
        interval        = self.get_argument("interval_list")
        bed             = self.get_argument("bed")

        vcf = self.get_output("vcf")

        gatk_cmd        = self.get_gatk_command()

        output_file_flag = self.get_output_file_flag()

        # Get sample names and tumor status
        sample_names, is_tumor = self.__process_samples()

        # Generating the Mutect2 options
        opts = list()

        # Add Tumor/Normal sample names
        if is_tumor[0]:
            opts.append("-tumor %s" % sample_names[0])
            opts.append("-normal %s" % sample_names[1])
        else:
            opts.append("-tumor %s" % sample_names[1])
            opts.append("-normal %s" % sample_names[0])

        def flatten(lis):
            """Given a list, possibly nested to any level, return it flattened."""
            new_lis = []
            for item in lis:
                if type(item) == type([]):
                    new_lis.extend(flatten(item))
                else:
                    new_lis.append(item)
            return new_lis

        # Add arguments for bams
        bams = flatten(bams)
        opts.extend(["-I %s" % bam for bam in bams])

        opts.append("{0} {1}".format(output_file_flag, vcf))
        opts.append("-R %s" % ref)
        opts.append("--native-pair-hmm-threads %s" % nr_cpus)

        if germline_vcf is not None:
            opts.append("--germline-resource %s" % germline_vcf)

        # Limit the locations to be processes
        if L is not None:
            if isinstance(L, list):
                for included in L:
                    if included != "unmapped":
                        opts.append("-L \"%s\"" % included)
            else:
                opts.append("-L \"%s\"" % L)
        if XL is not None:
            if isinstance(XL, list):
                for excluded in XL:
                    opts.append("-XL \"%s\"" % excluded)
            else:
                opts.append("-XL \"%s\"" % XL)

        # Check if an interval list was provided and if yes, place it
        if interval is not None:
            opts.append("-L {0}".format(interval))

        # Check if a BED file was provided and if yes, place it
        if bed is not None:
            opts.append("-L {0}".format(bed))

        # Generating command for Mutect2
        return "{0} Mutect2 {1} !LOG3!".format(gatk_cmd, " ".join(opts))

    def __process_samples(self):

        # Generate a dictionary that will contain the samples and their tumor status
        tumor_status = {}

        # Get samples and tumor status
        sample_names = self.get_argument("sample_name")
        is_tumor = self.get_argument("is_tumor")

        # Make sure samples and tumor status are lists
        sample_names = sample_names if isinstance(sample_names, list) else [sample_names]
        is_tumor = is_tumor if isinstance(is_tumor, list) else [is_tumor]

        # Add each sample to the tumor status dictionary
        for _name, _tumor in zip(sample_names, is_tumor):

            # Extract the sample ID
            _id = _name.rsplit("_", 1)[0]

            # Check if the current sample id has already been introduced but with a different tumor status
            if _id in tumor_status and tumor_status[_id] != _tumor:
                logging.error("Same sample ID '%s' was provided as different tumor status!" % _id)
                raise RuntimeError("Same sample ID '%s' was provided as different tumor status!" % _id)

            # If we have not stopped, just added it (possibly again) in the dictionary
            tumor_status[_id] = _tumor

        return list(tumor_status.keys()), list(tumor_status.values())

class DepthOfCoverage(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(DepthOfCoverage, self).__init__(module_id, is_docker)
        self.prefix         = None
        self.output_keys    = ["per_base_summary", "interval_summary", "interval_statistics", "sample_summary",
                               "sample_statistics", "cumulative_coverage_counts", "cumulative_coverage_proportions"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bam",            is_required=True)
        self.add_argument("bam_idx",        is_required=True)
        self.add_argument("ref",            is_required=True, is_resource=True)
        self.add_argument("interval_list",  is_required=False)
        self.add_argument("gene_list",      is_required=False, default_value=None)
        self.add_argument("read_group",     is_required=False, default_value=None)
        self.add_argument("unsafe",         is_required=False, default_value='ALLOW_N_CIGAR_READS')
        self.add_argument("nr_cpus",        is_required=True, default_value=8)
        self.add_argument("mem",            is_required=True, default_value="nr_cpus * 2")

    def define_output(self):
        # Declare unique file name for a single output file
        per_base_summary = self.generate_unique_file_name(extension=".per_base_summary.txt")

        # Split the genereated unique output file name to get prefix to use to generate other output filenames
        self.prefix = per_base_summary.split(".per_base_summary.txt")[0]

        # Generate rest of the output file names
        interval_summary                = "{0}.sample_interval_summary".format(self.prefix)
        interval_statistics             = "{0}.sample_interval_statistics".format(self.prefix)
        sample_summary                  = "{0}.sample_summary".format(self.prefix)
        sample_statistics               = "{0}.sample_statistics".format(self.prefix)
        cumulative_coverage_counts      = "{0}.sample_cumulative_coverage_counts".format(self.prefix)
        cumulative_coverage_proportions = "{0}.sample_cumulative_coverage_proportions".format(self.prefix)

        self.add_output("per_base_summary", self.prefix)
        self.add_output("interval_summary", interval_summary)
        self.add_output("interval_statistics", interval_statistics)
        self.add_output("sample_summary", sample_summary)
        self.add_output("sample_statistics", sample_statistics)
        self.add_output("cumulative_coverage_counts", cumulative_coverage_counts)
        self.add_output("cumulative_coverage_proportions", cumulative_coverage_proportions)

    def define_command(self):
        # Get input arguments
        bam             = self.get_argument("bam")
        ref             = self.get_argument("ref")
        interval_list   = self.get_argument("interval_list")
        gene_list       = self.get_argument("gene_list")
        read_group      = self.get_argument("read_group")
        unsafe          = self.get_argument("unsafe")

        # Get base GATK command line
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DepthOfCoverage
        cmd = "{0} DepthOfCoverage -I {1} -R {2} -ct 1 -ct 10 -ct 25 -ct 50 -ct 75 -ct 100 -ct 150 -ct 200 -ct 250 " \
              "-ct 500 {3} {4}".format(gatk_cmd, bam, ref, output_file_flag, self.prefix)

        if interval_list is not None:
            cmd = "{0} -L {1}".format(cmd, interval_list)

        if gene_list is not None:
            cmd = "{0} -geneList {1}".format(cmd, gene_list)

        if read_group is not None:
            cmd = "{0} -pt {1}".format(cmd, read_group)

        if unsafe != 'ALLOW_N_CIGAR_READS':
            logging.error("Provided unsafe operation {0} is not allowed. Only 'ALLOW_N_CIGAR_READS' operation is "
                          "allowed.".format(unsafe))
            raise NotImplementedError("Invalid unsafe operation for DepthOfCoverage.")

        if unsafe:
            cmd = "{0} --unsafe {1}".format(cmd, unsafe)

        return "{0} !LOG3!".format(cmd)

class PreprocessIntervals(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(PreprocessIntervals, self).__init__(module_id, is_docker)
        self.output_keys = ["interval_list"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("bin_length", is_required=True, default_value=0)
        self.add_argument("nr_cpus", is_required=True, default_value=1)
        self.add_argument("mem", is_required=True, default_value=2)

    def define_output(self):
        # Declare interval list output filename
        interval_list = self.generate_unique_file_name(extension=".interval.list")
        self.add_output("interval_list", interval_list)

    def define_command(self):
        # Get input arguments
        L = self.get_argument("location")
        bin_length = self.get_argument("bin_length")
        ref = self.get_argument("ref")

        # Get output arguments
        interval_list = self.get_output("interval_list")

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Generate the command line for PreProcessIntervals
        cmd = "{0} PreprocessIntervals -R {1} --bin-length {2} {3} {4}".format(gatk_cmd, ref, bin_length,
                                                                               output_file_flag, interval_list)

        # pass the location to include in the processing
        if L is not None:
            cmd = "{0} -L {1} --interval-merging-rule OVERLAPPING_ONLY".format(cmd, L)

        return "{0} !LOG3!".format(cmd)

class DenoiseReadCounts(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(DenoiseReadCounts, self).__init__(module_id, is_docker)
        self.output_keys = ["std_copy_ratio", "denoise_copy_ratio"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("read_count_out", is_required=True)
        self.add_argument("pon", is_required=False, is_resource=True, default_value=None)
        self.add_argument("annotated_intervals", is_required=False, default_value=None)
        self.add_argument("number_of_eigensamples", is_required=False, default_value=None)
        self.add_argument("nr_cpus", is_required=True, default_value=1)
        self.add_argument("mem", is_required=True, default_value=2)

    def define_output(self):
        # Declare output filename for copy ratio
        std_copy_ratio = self.generate_unique_file_name(extension=".standardizedCR.txt")
        denoise_copy_ratio = self.generate_unique_file_name(extension=".denoisedCR.txt")
        self.add_output("std_copy_ratio", std_copy_ratio)
        self.add_output("denoise_copy_ratio", denoise_copy_ratio)

    def define_command(self):
        # Get input arguments
        read_count_out = self.get_argument("read_count_out")
        pon = self.get_argument("pon")
        annotated_intervals = self.get_argument("annotated_intervals")
        eigensamples = self.get_argument("number_of_eigensamples")

        # Get output arguments
        std_copy_ratio = self.get_output("std_copy_ratio")
        denoise_copy_ratio = self.get_output("denoise_copy_ratio")

        if pon is not None and annotated_intervals is not None:
            logging.error("PoN and annotated intervals both can not be provided at the same time.")
            raise NotImplementedError("PoN and annotated intervals both can not be provided at the same time.")

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} DenoiseReadCounts".format(gatk_cmd)

        # if the Panel of Normal is provided, denoise the read count using it
        if pon:
            cmd = "{0} --count-panel-of-normals {1}".format(cmd, pon)

        # if the annotated interval is provided, denoise the read count using it
        if annotated_intervals:
            cmd = "{0} --annotated-intervals {1}".format(cmd, annotated_intervals)

        # if the number of eigne samples is provided, use them for denoising
        if eigensamples:
            cmd = "{0} --number-of-eigensamples {1}".format(cmd, eigensamples)

        # add the rest of the arguments to command
        cmd = "{0} -I {1} --standardized-copy-ratios {2} --denoised-copy-ratios {3}".format(cmd, read_count_out,
                                                                                std_copy_ratio,
                                                                                denoise_copy_ratio)

        return "{0} !LOG3!".format(cmd)

class PlotDenoisedCopyRatios(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(PlotDenoisedCopyRatios, self).__init__(module_id, is_docker)
        self.output_keys = ["denoised_plot", "denoised_plot_lm4", "std_mad", "denoised_mad", "delta_mad",
                            "scale_delta_mad"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("sample_name", is_required=True)
        self.add_argument("std_copy_ratio", is_required=True)
        self.add_argument("denoise_copy_ratio", is_required=False, default_value=None)
        self.add_argument("ref_dict", is_required=False, is_resource=True)
        self.add_argument("min_contig_len", is_required=False, default_value=46709983)
        self.add_argument("nr_cpus", is_required=True, default_value=1)
        self.add_argument("mem", is_required=True, default_value=2)

    def define_output(self):
        # Get the sample name to use it in file name creation
        sample_name = self.get_argument("sample_name")

        # Declare unique file name for a single output file
        denoised_plot = self.generate_unique_file_name(extension="{0}.denoised.png".format(sample_name))

        # Split the genereated unique output file name to get prefix to use to generate other output filenames
        prefix = denoised_plot.split(".denoised.png")[0]

        # Generate rest of the output file names
        denoised_plot_lm4 = "{0}.denoisedLimit4.png".format(prefix)
        std_mad = "{0}.standardizedMAD.txt".format(prefix)
        denoised_mad = "{0}.denoisedMAD.txt".format(prefix)
        delta_mad = "{0}.deltaMAD.txt".format(prefix)
        scale_delta_mad = "{0}.scaledDeltaMAD.txt".format(prefix)

        # Add output file keys to be returned to Bucket
        self.add_output("denoised_plot", denoised_plot)
        self.add_output("denoised_plot_lm4", denoised_plot_lm4)
        self.add_output("std_mad", std_mad)
        self.add_output("denoised_mad", denoised_mad)
        self.add_output("delta_mad", delta_mad)
        self.add_output("scale_delta_mad", scale_delta_mad)

    def define_command(self):
        # Get input arguments
        std_copy_ratio = self.get_argument("std_copy_ratio")
        denoise_copy_ratio = self.get_argument("denoise_copy_ratio")
        ref_dict = self.get_argument("ref_dict")
        min_contig_len = self.get_argument("min_contig_len")

        # get the prefix for output file names
        prefix = self.get_output("denoised_plot").get_filename().split(".denoised.png")[0]

        # Get output directory
        out_dir = self.get_output_dir()

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} PlotDenoisedCopyRatios".format(gatk_cmd)

        # add the rest of the arguments to command
        cmd = "{0} --standardized-copy-ratios {1} --denoised-copy-ratios {2} --sequence-dictionary {3} " \
              "--minimum-contig-length {4} --output-prefix {5} {6} {7}".format(cmd, std_copy_ratio, denoise_copy_ratio,
                                                                               ref_dict, min_contig_len, prefix,
                                                                               output_file_flag, out_dir)

        return "{0} !LOG3!".format(cmd)

class CollectAllelicCounts(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(CollectAllelicCounts, self).__init__(module_id, is_docker)
        self.output_keys = ["allelic_counts"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("sample_name", is_required=True)
        self.add_argument("bam", is_required=True)
        self.add_argument("bam_idx", is_required=True)
        self.add_argument("ref", is_required=True, is_resource=True)
        self.add_argument("interval_list", is_required=True)
        self.add_argument("nr_cpus", is_required=True, default_value=8)
        self.add_argument("mem", is_required=True, default_value=30)

    def define_output(self):
        # Get the sample name to use it in file name creation
        sample_name = self.get_argument("sample_name")

        # Declare unique file name for a single output file
        allelic_counts = self.generate_unique_file_name(extension="{0}.allelicCounts.txt".format(sample_name))

        self.add_output("allelic_counts", allelic_counts)

    def define_command(self):
        # Get input arguments
        bam = self.get_argument("bam")
        ref = self.get_argument("ref")
        interval_list = self.get_argument("interval_list")

        # get the prefix for output file names
        allelic_counts = self.get_output("allelic_counts")

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} CollectAllelicCounts".format(gatk_cmd)

        # add the rest of the arguments to command
        cmd = "{0} -I {1} -R {2} -L {3} {4} {5}".format(cmd, bam, ref, interval_list, output_file_flag, allelic_counts)

        return "{0} !LOG3!".format(cmd)

class ModelSegments(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(ModelSegments, self).__init__(module_id, is_docker)
        self.output_keys = ["model_begin_seg", "model_final_seg", "cr_seg", "model_begin_af_param",
                            "model_begin_cr_param", "model_final_af_param", "model_final_cr_param"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("sample_name", is_required=True)
        self.add_argument("denoise_copy_ratio", is_required=True)
        self.add_argument("nr_cpus", is_required=True, default_value=4)
        self.add_argument("mem", is_required=True, default_value=8)

    def define_output(self):
        # Get the sample name to use it in file name creation
        sample_name = self.get_argument("sample_name")

        # Declare unique file name for a single output file
        model_begin_seg = self.generate_unique_file_name(extension="{0}.modelBegin.seg".format(sample_name))

        # Split the genereated unique output file name to get prefix to use to generate other output filenames
        prefix = model_begin_seg.split(".modelBegin.seg")[0]

        # Generate rest of the output file names
        model_final_seg = "{0}.modelFinal.seg".format(prefix)
        cr_seg = "{0}.cr.seg".format(prefix)
        model_begin_af_param = "{0}.modelBegin.af.param".format(prefix)
        model_begin_cr_param = "{0}.modelBegin.cr.param".format(prefix)
        model_final_af_param = "{0}.modelFinal.af.param".format(prefix)
        model_final_cr_param = "{0}.modelFinal.cr.param".format(prefix)

        # Add output file keys to be returned to Bucket
        self.add_output("model_begin_seg", model_begin_seg)
        self.add_output("model_final_seg", model_final_seg)
        self.add_output("cr_seg", cr_seg)
        self.add_output("model_begin_af_param", model_begin_af_param)
        self.add_output("model_begin_cr_param", model_begin_cr_param)
        self.add_output("model_final_af_param", model_final_af_param)
        self.add_output("model_final_cr_param", model_final_cr_param)

    def define_command(self):
        # Get input arguments
        denoise_copy_ratio = self.get_argument("denoise_copy_ratio")

        # get the prefix for output file names
        prefix = self.get_output("model_begin_seg").get_filename().split(".modelBegin.seg")[0]

        # Get output directory
        out_dir = self.get_output_dir()

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} ModelSegments".format(gatk_cmd)

        # add the rest of the arguments to command
        cmd = "{0} --denoised-copy-ratios {1} --output-prefix {2} {3} {4}".format(cmd, denoise_copy_ratio, prefix,
                                                                                output_file_flag, out_dir)

        return "{0} !LOG3!".format(cmd)

class CallCopyRatioSegments(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(CallCopyRatioSegments, self).__init__(module_id, is_docker)
        self.output_keys = ["seg_call"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("sample_name", is_required=True)
        self.add_argument("cr_seg", is_required=True)
        self.add_argument("nr_cpus", is_required=True, default_value=4)
        self.add_argument("mem", is_required=True, default_value=8)

    def define_output(self):
        # Get the sample name to use it in file name creation
        sample_name = self.get_argument("sample_name")

        # Declare unique file name for a single output file
        called_seg = self.generate_unique_file_name(extension="{0}.called.seg".format(sample_name))

        # Add output file keys to be returned to Bucket
        self.add_output("seg_call", called_seg)

    def define_command(self):
        # Get input arguments
        cr_seg = self.get_argument("cr_seg")

        # get the prefix for output file name
        called_seg = self.get_output("seg_call")

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} CallCopyRatioSegments".format(gatk_cmd)

        # add the rest of the arguments to command
        cmd = "{0} -I {1} {2} {3}".format(cmd, cr_seg, output_file_flag, called_seg)

        return "{0} !LOG3!".format(cmd)

class PlotModeledSegments(_GATKBase):

    def __init__(self, module_id, is_docker=False):
        super(PlotModeledSegments, self).__init__(module_id, is_docker)
        self.output_keys = ["model_plot"]

    def define_input(self):
        self.define_base_args()
        self.add_argument("sample_name", is_required=True)
        self.add_argument("denoise_copy_ratio", is_required=True)
        self.add_argument("model_final_seg", is_required=True)
        self.add_argument("ref_dict", is_required=True)
        self.add_argument("nr_cpus", is_required=True, default_value=4)
        self.add_argument("mem", is_required=True, default_value=8)

    def define_output(self):
        # Get the sample name to use it in file name creation
        sample_name = self.get_argument("sample_name")

        # Declare unique file name for a single output file
        modeled_plot = self.generate_unique_file_name(extension="{0}.modeled.png".format(sample_name))

        # Add output file keys to be returned to Bucket
        self.add_output("model_plot", modeled_plot)

    def define_command(self):
        # Get input arguments
        denoise_copy_ratio = self.get_argument("denoise_copy_ratio")
        model_final_seg = self.get_argument("model_final_seg")
        ref_dict = self.get_argument("ref_dict")

        # get the prefix for output file name
        prefix = self.get_output("model_plot").get_filename().split(".modeled.png")[0]

        # Get output directory
        out_dir = self.get_output_dir()

        # Get GATK base command
        gatk_cmd = self.get_gatk_command()

        # Get the output file flag depends on GATK version
        output_file_flag = self.get_output_file_flag()

        # Generate the command line for DenoiseReadCounts
        cmd = "{0} PlotModeledSegments".format(gatk_cmd)

        # add the rest of the arguments to command
        cmd = "{0} --denoised-copy-ratios {1} --segments {2} --sequence-dictionary {3} --output-prefix {4} {5} {6}" \
              "".format(cmd, denoise_copy_ratio, model_final_seg, ref_dict, prefix, output_file_flag, out_dir)

        return "{0} !LOG3!".format(cmd)