from Bio import pairwise2, SeqIO
from Bio.SubsMat import MatrixInfo as matlist
from scanpy.tools._dpt import DPT
from scipy.sparse import coo_matrix, issparse, spdiags, linalg
import scvelo as scv
import scvelo.plotting.utils as scvu
from scvelo.preprocessing.moments import get_connectivities
from scvelo.preprocessing.neighbors import neighbors, verify_neighbors
from scvelo.preprocessing.neighbors import get_neighs, get_n_neighs
from scvelo.tools.utils import groups_to_bool, scale, strings_to_categoricals
from sklearn.neighbors import NearestNeighbors
from tqdm import tqdm

from mutation import predict_sequence_prob
from utils import *

def sum_var(A):
    """summation over axis 1 (var) equivalent to np.sum(A, 1)"""
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        return A.sum(1).A1 if issparse(A) else np.sum(A, axis=1)

def norm(A):
    """computes the L2-norm along axis 1
    (e.g. genes or embedding dimensions) equivalent to np.linalg.norm(A, axis=1)
    """
    if issparse(A):
        return np.sqrt(A.multiply(A).sum(1).A1)
    else:
        return np.sqrt(np.einsum('ij, ij -> i', A, A)
                       if A.ndim > 1 else np.sum(A * A))

def get_iterative_indices(
        indices,
        index,
        n_recurse_neighbors=0,
        max_neighs=None,
):
    def iterate_indices(indices, index, n_recurse_neighbors):
        if n_recurse_neighbors > 1:
            index = iterate_indices(indices, index, n_recurse_neighbors - 1)
        ix = np.append(index, indices[index])  # direct and indirect neighbors
        if np.isnan(ix).any():
            ix = ix[~np.isnan(ix)]
        return ix.astype(int)

    indices = np.unique(iterate_indices(indices, index, n_recurse_neighbors))
    if max_neighs is not None and len(indices) > max_neighs:
        indices = np.random.choice(indices, max_neighs, replace=False)
    return indices

def get_indices(dist, n_neighbors=None, mode_neighbors='distances'):
    from scvelo.preprocessing.neighbors import compute_connectivities_umap

    D = dist.copy()
    D.data += 1e-6

    n_counts = sum_var(D > 0)
    n_neighbors = (
        n_counts.min() if n_neighbors is None else min(n_counts.min(), n_neighbors)
    )
    rows = np.where(n_counts > n_neighbors)[0]
    cumsum_neighs = np.insert(n_counts.cumsum(), 0, 0)
    dat = D.data

    for row in rows:
        n0, n1 = cumsum_neighs[row], cumsum_neighs[row + 1]
        rm_idx = n0 + dat[n0:n1].argsort()[n_neighbors:]
        dat[rm_idx] = 0
    D.eliminate_zeros()

    D.data -= 1e-6
    if mode_neighbors == 'distances':
        indices = D.indices.reshape((-1, n_neighbors))
    elif mode_neighbors == 'connectivities':
        knn_indices = D.indices.reshape((-1, n_neighbors))
        knn_distances = D.data.reshape((-1, n_neighbors))
        _, conn = compute_connectivities_umap(
            knn_indices, knn_distances, D.shape[0], n_neighbors
        )
        indices = get_indices_from_csr(conn)
    return indices, D

def likelihood_compare(seq1, seq2, args, vocabulary, model,
                       pos1=None, pos2=None, seq_cache={}, verbose=False):
    likelihoods = []

    for seq_pred, positions in zip([ seq1, seq2 ], [ pos1, pos2 ]):
        if positions is None:
            positions = range(len(seq_pred))

        if seq_pred in seq_cache:
            seq_probs = seq_cache[seq_pred][list(positions)]

        else:
            y_pred = predict_sequence_prob(
                args, seq_pred, vocabulary, model, verbose=verbose
            )
            if not ('esm' in args.model_name or args.model_name == 'tape'):
                y_pred = np.log(y_pred)

            seq_probs = np.array([
                y_pred[i + 1, (
                    vocabulary[seq_pred[i]]
                    if seq_pred[i] in vocabulary else
                    model.unk_idx_
                )]
                for i in positions
            ])

        likelihoods.append(np.mean(seq_probs))

    return likelihoods[1] - likelihoods[0]

def likelihood_full(seq1, seq2, args, vocabulary, model,
                    seq_cache={}, verbose=False):
    return likelihood_compare(
        seq1, seq2, args, vocabulary, model,
        seq_cache=seq_cache, verbose=verbose,
    )

def likelihood_muts(seq1, seq2, args, vocabulary, model,
                    seq_cache={}, verbose=False):
    # Align, prefer matches to gaps.
    alignment = pairwise2.align.globalms(
        seq1, seq2, 5, -4, -4, -.1, one_alignment_only=True
    )[0]
    a_seq1, a_seq2, _, _, _ = alignment

    # Map alignment to original indices.
    del1, sub1, del2, sub2 = [], [], [], []
    for a_seq, other_seq, deletions, substitutions in zip(
            [ a_seq1, a_seq2, ], [ a_seq2, a_seq1, ],
            [ del1, del2 ], [ sub1, sub2, ]
    ):
        orig_idx = 0
        for a_idx, ch in enumerate(a_seq):
            if ch == '-':
                continue
            if other_seq[a_idx] == '-':
                deletions.append(orig_idx)
            elif other_seq[a_idx] != ch:
                substitutions.append(orig_idx)
            orig_idx += 1

    return likelihood_compare(
        seq1, seq2, args, vocabulary, model,
        pos1=sub1, pos2=sub2, seq_cache=seq_cache, verbose=verbose,
    )

def likelihood_blosum62(
        seq1, seq2, args, vocabulary, model,
        seq_cache={}, verbose=False, natural_aas=None,
):
    from Bio.SubsMat import MatrixInfo as matlist
    matrix = matlist.blosum62

    # Align, prefer matches to gaps.
    alignment = pairwise2.align.globalms(
        seq1, seq2, 5, -4, -4, -.1, one_alignment_only=True
    )[0]
    a_seq1, a_seq2, _, _, _ = alignment

    scores = []
    for ch1, ch2 in zip(a_seq1, a_seq2):
        if ch1 == ch2:
            continue
        if (ch1, ch2) in matrix:
            scores.append(matrix[(ch1, ch2)])
        elif (ch2, ch1) in matrix:
            scores.append(matrix[(ch2, ch1)])

    return np.mean(scores)

def likelihood_self(seq1, seq2, args, vocabulary, model,
                    seq_cache={}, verbose=False):
    # Align, prefer matches to gaps.
    alignment =pairwise2.align.globalms(
        seq1, seq2, 5, -4, -4, -.1, one_alignment_only=True
    )[0]
    a_seq1, a_seq2, _, _, _ = alignment

    # See how mutating to `seq2' changes probability.

    likelihood_change = []

    for a_seq, other_seq in zip([ a_seq1, a_seq2 ],
                                [ a_seq2, a_seq1 ]):
        if a_seq in seq_cache:
            y_pred = seq_cache[a_seq]
        else:
            y_pred = predict_sequence_prob(
                args, a_seq, vocabulary, model, verbose=verbose
            )
            if not ('esm' in args.model_name or args.model_name == 'tape'):
                y_pred = np.log(y_pred)

        orig_idx, scores = 0, []
        for a_idx, ch in enumerate(a_seq):
            if ch == '-':
                continue
            if other_seq[a_idx] == '-':
                pass
            elif other_seq[a_idx] != ch:
                ch_idx = vocabulary[ch] \
                         if ch in vocabulary else \
                         model.unk_idx_
                o_idx = vocabulary[other_seq[a_idx]] \
                        if other_seq[a_idx] in vocabulary else \
                        model.unk_idx_

                prob_wt = y_pred[a_idx + 1, ch_idx]
                prob_mut = y_pred[a_idx + 1, o_idx]
                scores.append(prob_wt - prob_mut)
            orig_idx += 1

        likelihood_change.append(np.mean(scores))

    return likelihood_change[1] - likelihood_change[0]

def vals_to_csr(vals, rows, cols, shape, split_negative=False):
    graph = coo_matrix((vals, (rows, cols)), shape=shape)

    if split_negative:
        graph_neg = graph.copy()

        graph.data = np.clip(graph.data, 0, 1)
        graph_neg.data = np.clip(graph_neg.data, -1, 0)

        graph.eliminate_zeros()
        graph_neg.eliminate_zeros()

        return graph.tocsr(), graph_neg.tocsr()

    else:
        return graph.tocsr()

class VelocityGraph:
    def __init__(
            self,
            adata,
            seqs,
            score='other',
            scale_dist=False,
            vkey='velocity',
            n_recurse_neighbors=None,
            random_neighbors_at_max=None,
            mode_neighbors='distances',
            verbose=False,
    ):
        self.adata = adata

        self.seqs = seqs
        self.seq_probs = {}

        self.score = score

        self.scale_dist = scale_dist

        self.n_recurse_neighbors = n_recurse_neighbors
        if self.n_recurse_neighbors is None:
            if mode_neighbors == 'connectivities':
                self.n_recurse_neighbors = 1
            else:
                self.n_recurse_neighbors = 2

        if np.min((get_neighs(adata, 'distances') > 0).sum(1).A1) == 0:
            raise ValueError(
                'Your neighbor graph seems to be corrupted. '
                'Consider recomputing via scanpy.pp.neighbors.'
            )
        self.indices = get_indices(
            dist=get_neighs(adata, 'distances'),
            mode_neighbors=mode_neighbors,
        )[0]

        self.max_neighs = random_neighbors_at_max

        gkey, gkey_ = f'{vkey}_graph', f'{vkey}_graph_neg'
        self.graph = adata.uns[gkey] if gkey in adata.uns.keys() else []
        self.graph_neg = adata.uns[gkey_] if gkey_ in adata.uns.keys() else []

        self.self_prob = None

        self.verbose = verbose

    def compute_likelihoods(self, args, vocabulary, model):
        if self.verbose:
            iterator = tqdm(self.seqs)
        else:
            iterator = self.seqs

        for seq in iterator:
            y_pred = predict_sequence_prob(
                args, seq, vocabulary, model, verbose=self.verbose
            )

            if self.score == 'other':
                self.seq_probs[seq] = np.array([
                    y_pred[i + 1, (
                        vocabulary[seq[i]]
                        if seq[i] in vocabulary else
                        model.unk_idx_
                    )] for i in range(len(seq))
                ])
            else:
                self.seq_probs[seq] = y_pred

        if self.verbose:
            sys.stdout.flush()

    def compute_gradients(self, args, vocabulary, model):
        n_obs = self.adata.X.shape[0]
        vals, rows, cols, uncertainties = [], [], [], []

        if self.verbose:
            iterator = tqdm(range(n_obs))
        else:
            iterator = range(n_obs)

        for i in iterator:
            neighs_idx = get_iterative_indices(
                self.indices, i, self.n_recurse_neighbors, self.max_neighs
            )

            if self.score == 'other':
                score_fn = likelihood_muts
            else:
                score_fn = likelihood_self

            val = np.array([
                score_fn(
                    self.seqs[i], self.seqs[j],
                    args, vocabulary, model,
                    seq_cache=self.seq_probs, verbose=self.verbose,
                ) for j in neighs_idx
            ])

            if self.scale_dist:
                dist = self.adata.X[neighs_idx] - self.adata.X[i, None]
                dist = np.sqrt((dist ** 2).sum(1))
                val *= self.scale_dist * dist

            vals.extend(val)
            rows.extend(np.ones(len(neighs_idx)) * i)
            cols.extend(neighs_idx)

        if self.verbose:
            sys.stdout.flush()

        vals = np.hstack(vals)
        vals[np.isnan(vals)] = 0

        self.graph, self.graph_neg = vals_to_csr(
            vals, rows, cols, shape=(n_obs, n_obs), split_negative=True
        )

        confidence = self.graph.max(1).A.flatten()
        self.self_prob = np.clip(np.percentile(confidence, 98) - confidence, 0, 1)

def velocity_graph(
        adata,
        args,
        vocabulary,
        model,
        score='other',
        scale_dist=False,
        seqs=None,
        vkey='velocity',
        n_recurse_neighbors=0,
        random_neighbors_at_max=None,
        mode_neighbors='distances',
        copy=False,
        verbose=True,
):
    adata = adata.copy() if copy else adata
    verify_neighbors(adata)

    if seqs is None:
        seqs = adata.obs['seq']
    if adata.X.shape[0] != len(seqs):
        raise ValueError('Number of sequences should correspond to '
                         'number of observations.')

    valid_scores = { 'self', 'other' }
    if score not in valid_scores:
        raise ValueError('Score must be one of {}'
                         .format(', '.join(valid_scores)))

    vgraph = VelocityGraph(
        adata,
        seqs,
        score=score,
        scale_dist=scale_dist,
        vkey=vkey,
        n_recurse_neighbors=n_recurse_neighbors,
        random_neighbors_at_max=random_neighbors_at_max,
        mode_neighbors=mode_neighbors,
        verbose=verbose,
    )

    if verbose:
        tprint('Computing likelihoods...')
    vgraph.compute_likelihoods(args, vocabulary, model)
    if verbose:
        print('')

    if verbose:
        tprint('Computing velocity graph...')
    vgraph.compute_gradients(args, vocabulary, model)
    if verbose:
        print('')

    adata.uns[f'{vkey}_graph'] = vgraph.graph
    adata.uns[f'{vkey}_graph_neg'] = vgraph.graph_neg
    adata.obs[f'{vkey}_self_transition'] = vgraph.self_prob

    adata.layers[vkey] = np.zeros(adata.X.shape)

    return adata if copy else None

class VPT(DPT):
    def set_iroots(self, root=None):
        if (
            isinstance(root, str)
            and root in self._adata.obs.keys()
            and self._adata.obs[root].max() != 0
        ):
            #self.iroots = get_connectivities(self._adata).dot(self._adata.obs[root])
            self.iroots = np.array(self._adata.obs[root])
            self.iroots = scale(self.iroots)
            self.iroots = np.argwhere(
                self.iroots >= self.iroots.max()
            ).ravel()
        elif isinstance(root, str) and root in self._adata.obs_names:
            self.iroots = [ self._adata.obs_names.get_loc(root) ]
        elif isinstance(root, (int, np.integer)) and root < self._adata.n_obs:
            self.iroots = [ root ]
        else:
            self.iroots = [ None ]

    def compute_transitions(self, density_normalize=True):
        T = self._connectivities
        if density_normalize:
            q = np.asarray(T.sum(axis=0))
            q += q == 0
            Q = (
                spdiags(1.0 / q, 0, T.shape[0], T.shape[0])
                if issparse(T)
                else np.diag(1.0 / q)
            )
            K = Q.dot(T).dot(Q)
        else:
            K = T
        z = np.sqrt(np.asarray(K.sum(axis=0)))
        Z = (
            spdiags(1.0 / z, 0, K.shape[0], K.shape[0])
            if issparse(K)
            else np.diag(1.0 / z)
        )
        self._transitions_sym = Z.dot(K).dot(Z)

    def compute_eigen(self, n_comps=10, sym=None, sort='decrease'):
        if self._transitions_sym is None:
            raise ValueError('Run `.compute_transitions` first.')
        n_comps = min(self._transitions_sym.shape[0] - 1, n_comps)
        evals, evecs = linalg.eigsh(self._transitions_sym, k=n_comps, which='LM')
        self._eigen_values = evals[::-1]
        self._eigen_basis = evecs[:, ::-1]

    def compute_pseudotime(self, inverse=False):
        if self.iroot is not None:
            self._set_pseudotime()
            self.pseudotime = 1 - self.pseudotime if inverse else self.pseudotime
            self.pseudotime[~np.isfinite(self.pseudotime)] = np.nan
        else:
            self.pseudotime = np.empty(self._adata.n_obs)
            self.pseudotime[:] = np.nan

def velocity_pseudotime(
        adata,
        vkey='velocity',
        rank_transform=False,
        groupby=None,
        groups=None,
        root_key=None,
        end_key=None,
        use_ends=False,
        n_dcs=10,
        use_velocity_graph=True,
        save_diffmap=None,
        return_model=None,
        **kwargs,
):
    strings_to_categoricals(adata)
    if root_key is None and 'root_nodes' in adata.obs.keys():
        root0 = adata.obs['root_nodes'][0]
        if not np.isnan(root0) and not isinstance(root0, str):
            root_key = 'root_nodes'
    if end_key is None and 'end_points' in adata.obs.keys():
        end0 = adata.obs['end_points'][0]
        if not np.isnan(end0) and not isinstance(end0, str):
            end_key = 'end_points'

    groupby = (
        'cell_fate' if groupby is None and 'cell_fate' in adata.obs.keys()
        else groupby
    )
    categories = (
        adata.obs[groupby].cat.categories
        if groupby is not None and groups is None
        else [None]
    )
    for cat in categories:
        groups = cat if cat is not None else groups
        if (
            root_key is None
            or root_key in adata.obs.keys()
            and np.max(adata.obs[root_key]) == np.min(adata.obs[root_key])
        ):
            scv.tl.terminal_states(adata, vkey, groupby, groups)
            root_key, end_key = 'root_nodes', 'end_points'
        cell_subset = groups_to_bool(adata, groups=groups, groupby=groupby)
        data = adata.copy() if cell_subset is None else adata[cell_subset].copy()
        if 'allow_kendall_tau_shift' not in kwargs:
            kwargs['allow_kendall_tau_shift'] = True
        vpt = VPT(data, n_dcs=n_dcs, **kwargs)

        if use_velocity_graph:
            T = data.uns[f'{vkey}_graph'] - data.uns[f'{vkey}_graph_neg']
            vpt._connectivities = T + T.T

        vpt.compute_transitions()
        vpt.compute_eigen(n_comps=n_dcs)

        vpt.set_iroots(root_key)
        pseudotimes = [ np.zeros(adata.X.shape[0]) ]
        for iroot in vpt.iroots:
            if iroot is None:
                continue
            vpt.iroot = iroot
            vpt.compute_pseudotime()
            pseudotimes.append(scale(vpt.pseudotime))

        if use_ends:
            vpt.set_iroots(end_key)
            for iroot in vpt.iroots:
                if iroot is None:
                    continue
                vpt.iroot = iroot
                vpt.compute_pseudotime(inverse=True)
                pseudotimes.append(scale(vpt.pseudotime))

        vpt.pseudotime = np.nan_to_num(np.vstack(pseudotimes)).mean(0)
        if rank_transform:
            vpt.pseudotime = ss.rankdata(vpt.pseudotime)
        vpt.pseudotime = scale(vpt.pseudotime)

        if 'n_branchings' in kwargs and kwargs['n_branchings'] > 0:
            vpt.branchings_segments()
        else:
            vpt.indices = vpt.pseudotime.argsort()

        if f'{vkey}_pseudotime' not in adata.obs.keys():
            pseudotime = np.empty(adata.n_obs)
            pseudotime[:] = np.nan
        else:
            pseudotime = adata.obs[f'{vkey}_pseudotime'].values
        pseudotime[cell_subset] = vpt.pseudotime
        adata.obs[f'{vkey}_pseudotime'] = np.array(pseudotime, dtype=np.float64)

        if save_diffmap:
            diffmap = np.empty(shape=(adata.n_obs, n_dcs))
            diffmap[:] = np.nan
            diffmap[cell_subset] = vpt.eigen_basis
            adata.obsm[f'X_diffmap_{groups}'] = diffmap

    return vpt if return_model else None

def quiver_autoscale(X_emb, V_emb):
    import matplotlib.pyplot as pl

    scale_factor = np.abs(X_emb).max()  # just so that it handles very large values
    fig, ax = pl.subplots()
    Q = ax.quiver(
        X_emb[:, 0] / scale_factor,
        X_emb[:, 1] / scale_factor,
        V_emb[:, 0],
        V_emb[:, 1],
        angles='xy',
        scale_units='xy',
        scale=None,
    )
    Q._init()
    fig.clf()
    pl.close(fig)
    return Q.scale / scale_factor

def compute_velocity_on_grid(
        X_emb,
        V_emb,
        density=None,
        smooth=None,
        n_neighbors=None,
        min_mass=None,
        autoscale=True,
        adjust_for_stream=False,
        cutoff_perc=None,
        return_mesh=False,
):
    # remove invalid cells
    idx_valid = np.isfinite(X_emb.sum(1) + V_emb.sum(1))
    X_emb = X_emb[idx_valid]
    V_emb = V_emb[idx_valid]

    # prepare grid
    n_obs, n_dim = X_emb.shape
    density = 1 if density is None else density
    smooth = 0.5 if smooth is None else smooth

    grs = []
    for dim_i in range(n_dim):
        m, M = np.min(X_emb[:, dim_i]), np.max(X_emb[:, dim_i])
        m = m - 0.01 * np.abs(M - m)
        M = M + 0.01 * np.abs(M - m)
        gr = np.linspace(m, M, int(50 * density))
        grs.append(gr)

    meshes_tuple = np.meshgrid(*grs)
    X_grid = np.vstack([i.flat for i in meshes_tuple]).T

    # estimate grid velocities
    if n_neighbors is None:
        n_neighbors = int(n_obs / 50)
    nn = NearestNeighbors(n_neighbors=n_neighbors, n_jobs=-1)
    nn.fit(X_emb)
    dists, neighs = nn.kneighbors(X_grid)

    scale = np.mean([(g[1] - g[0]) for g in grs]) * smooth
    weight = ss.norm.pdf(x=dists, scale=scale)
    p_mass = weight.sum(1)

    V_grid = (V_emb[neighs] * weight[:, :, None]).sum(1)
    V_grid /= np.maximum(1, p_mass)[:, None]
    if min_mass is None:
        min_mass = 1

    if adjust_for_stream:
        X_grid = np.stack([np.unique(X_grid[:, 0]), np.unique(X_grid[:, 1])])
        ns = int(np.sqrt(len(V_grid[:, 0])))
        V_grid = V_grid.T.reshape(2, ns, ns)

        mass = np.sqrt((V_grid ** 2).sum(0))
        min_mass = 10 ** (min_mass - 6)  # default min_mass = 1e-5
        min_mass = np.clip(min_mass, None, np.max(mass) * 0.9)
        cutoff = mass.reshape(V_grid[0].shape) < min_mass

        if cutoff_perc is None:
            cutoff_perc = 5
        length = np.sum(np.mean(np.abs(V_emb[neighs]), axis=1), axis=1).T
        length = length.reshape(ns, ns)
        cutoff |= length < np.percentile(length, cutoff_perc)

        V_grid[0][cutoff] = np.nan
    elif min_mass:
        min_mass *= np.percentile(p_mass, 99) / 100
        X_grid, V_grid = X_grid[p_mass > min_mass], V_grid[p_mass > min_mass]

        if autoscale:
            V_grid /= 3 * quiver_autoscale(X_grid, V_grid)

    if return_mesh:
        return X_grid, meshes_tuple, V_grid

    return X_grid, V_grid

def plot_pseudotime(
        adata,
        pfkey='pseudotime',
        rank_transform=False,
        use_ends=False,
        fill=True,
        levels=10,
        basis=None,
        vkey='velocity',
        density=None,
        smooth=None,
        pf_smooth=None,
        min_mass=None,
        arrow_size=None,
        arrow_length=None,
        arrow_color=None,
        scale=None,
        autoscale=True,
        n_neighbors=None,
        recompute=None,
        X=None,
        V=None,
        X_grid=None,
        V_grid=None,
        PF_grid=None,
        color=None,
        layer=None,
        color_map=None,
        colorbar=True,
        palette=None,
        size=None,
        alpha=0.5,
        offset=1,
        vmin=None,
        vmax=None,
        perc=None,
        sort_order=True,
        groups=None,
        components=None,
        projection='2d',
        legend_loc='none',
        legend_fontsize=None,
        legend_fontweight=None,
        xlabel=None,
        ylabel=None,
        title=None,
        fontsize=None,
        figsize=None,
        dpi=None,
        frameon=None,
        show=None,
        save=None,
        ax=None,
        ncols=None,
        **kwargs,
):
    if pfkey not in adata.obs:
        velocity_pseudotime(
            adata,
            vkey=vkey,
            groups=groups,
            rank_transform=rank_transform,
            use_velocity_graph=True,
            use_ends=use_ends,
        )
        adata.obs[pfkey] = adata.obs[f'{vkey}_pseudotime']

    smooth = 0.5 if smooth is None else smooth
    pf_smooth = smooth if pf_smooth is None else pf_smooth

    basis = scvu.default_basis(adata, **kwargs) \
            if basis is None \
            else scvu.get_basis(adata, basis)
    if vkey == 'all':
        lkeys = list(adata.layers.keys())
        vkey = [key for key in lkeys if 'velocity' in key and '_u' not in key]
    color, color_map = kwargs.pop('c', color), kwargs.pop('cmap', color_map)
    colors = scvu.make_unique_list(color, allow_array=True)
    layers, vkeys = (scvu.make_unique_list(layer),
                     scvu.make_unique_list(vkey))

    if V is None:
        for key in vkeys:
            if recompute or scvu.velocity_embedding_changed(
                    adata, basis=basis, vkey=key
            ):
                scv.pl.velocity_embedding(adata, basis=basis, vkey=key)

    color, layer, vkey = colors[0], layers[0], vkeys[0]
    color = scvu.default_color(adata) if color is None else color

    _adata = (
        adata[scvu.groups_to_bool(adata, groups, groupby=color)]
        if groups is not None and color in adata.obs.keys()
        else adata
    )
    comps, obsm = scvu.get_components(components, basis), _adata.obsm
    X_emb = np.array(obsm[f'X_{basis}'][:, comps]) \
            if X is None else X[:, :2]
    V_emb = np.array(obsm[f'{vkey}_{basis}'][:, comps]) \
            if V is None else V[:, :2]
    if X_grid is None or V_grid is None:
        X_grid, V_grid = compute_velocity_on_grid(
            X_emb=X_emb,
            V_emb=V_emb,
            density=density,
            autoscale=autoscale,
            smooth=smooth,
            n_neighbors=n_neighbors,
            min_mass=min_mass,
        )

    if vmin is None:
        vmin = adata.obs[pfkey].min()

    contour_kwargs = {
        'levels': levels,
        'vmin': vmin,
        'vmax': vmax,
        'alpha': alpha,
        'legend_fontsize': legend_fontsize,
        'legend_fontweight': legend_fontweight,
        'palette': palette,
        'cmap': color_map,
        'xlabel': xlabel,
        'ylabel': ylabel,
        'colorbar': colorbar,
        'dpi': dpi,
    }

    ax, show = scvu.get_ax(ax, show, figsize, dpi)
    hl, hw, hal = scvu.default_arrow(arrow_size)
    if arrow_length is not None:
        scale = 1 / arrow_length
    if scale is None:
        scale = 1
    if arrow_color is None:
        arrow_color = 'grey'
    quiver_kwargs = {'angles': 'xy', 'scale_units': 'xy', 'edgecolors': 'k'}
    quiver_kwargs.update({'scale': scale, 'width': 0.001, 'headlength': hl / 2})
    quiver_kwargs.update({'headwidth': hw / 2, 'headaxislength': hal / 2})
    quiver_kwargs.update({'color': arrow_color, 'linewidth': 0.2, 'zorder': 3})

    for arg in list(kwargs):
        if arg in quiver_kwargs:
            quiver_kwargs.update({arg: kwargs[arg]})
        else:
            scatter_kwargs.update({arg: kwargs[arg]})

    ax.quiver(
        X_grid[:, 0], X_grid[:, 1], V_grid[:, 0], V_grid[:, 1], **quiver_kwargs
    )

    PF_emb = np.array(adata.obs[pfkey]).reshape(-1, 1)
    if offset is not None:
        PF_emb += offset

    if PF_grid is None:
        _, mesh, PF_grid = compute_velocity_on_grid(
            X_emb=X_emb,
            V_emb=PF_emb,
            density=density,
            autoscale=False,
            smooth=pf_smooth,
            n_neighbors=n_neighbors,
            min_mass=0.,
            return_mesh=True,
        )
        PF_grid = PF_grid.reshape(mesh[0].shape)

    if fill:
        contour_fn = ax.contourf
    else:
        contour_fn = ax.contour

    contour = contour_fn(mesh[0], mesh[1], PF_grid, zorder=1,
                         **contour_kwargs)

    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    #cbar = plt.colorbar(contour)
    #cbar.ax.set_ylabel(pfkey)

    scvu.savefig_or_show(dpi=dpi, save=save, show=show)
    if show is False:
        return ax

def shortest_path(
        adata,
        source_idx,
        target_idx,
        vkey='velocity',
):
    if np.min((get_neighs(adata, 'distances') > 0).sum(1).A1) == 0:
        raise ValueError(
            'Your neighbor graph seems to be corrupted. '
            'Consider recomputing via scanpy.pp.neighbors.'
        )

    if f'{vkey}_graph' not in adata.uns:
        raise ValueError(
            'Must run velocity_graph() first.'
        )

    T = adata.uns[f'{vkey}_graph'] - adata.uns[f'{vkey}_graph_neg']

    import networkx as nx

    G = nx.convert_matrix.from_scipy_sparse_matrix(T)

    path = nx.algorithms.shortest_paths.generic.shortest_path(
        G, source=source_idx, target=target_idx,
    )

    return path

def plot_path(
        adata,
        path=None,
        source_idx=None,
        target_idx=None,
        basis='umap',
        vkey='velocity',
        ax=None,
        color='white',
        cmap=None,
        size=15,
        edgecolor='black',
        linecolor='#888888',
        linewidth=0.001,
):
    if path is None and (source_idx is None or target_idx is None):
        raise ValueError(
            'Must provide path indices or source and target indices.'
        )

    if path is None:
        path = shortest_path(adata, source_idx, target_idx, vkey=vkey)

    if ax is None:
        plt.figure()
        ax = plt.gca()

    if f'X_{basis}' not in adata.obsm:
        raise ValueError(
            f'Basis {basis} not found in AnnData.'
        )

    basis_x = np.array(adata.obsm[f'X_{basis}'][path, 0]).ravel()
    basis_y = np.array(adata.obsm[f'X_{basis}'][path, 1]).ravel()

    for idx, (x, y) in enumerate(zip(basis_x, basis_y)):
        if idx < len(basis_x) - 1:
            dx, dy = basis_x[idx + 1] - x, basis_y[idx + 1] - y
            ax.arrow(x, y, dx, dy, width=linewidth, head_width=0,
                     length_includes_head=True,
                     color=linecolor, zorder=5)

    ax.scatter(basis_x, basis_y,
               s=size, c=color, cmap=cmap,
               edgecolors=edgecolor, linewidths=0.5, zorder=10)

    return ax

def tool_onehot_msa(
        adata,
        reference=None,
        key='onehot',
        seq_key='seq',
        backend='mafft',
        dirname='target/evolocity_alignments',
        n_threads=1,
        copy=False,
):
    # Write unaligned fasta.

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord
    seqs = [
        SeqRecord(Seq(seq), id='seq{}'.format(idx), description='')
        for idx, seq in enumerate(adata.obs[seq_key])
    ]

    if dirname.endswith('/'):
        dirname = dirname.rstrip('/')
    mkdir_p(dirname)
    ifname = dirname + '/unaligned.fasta'
    SeqIO.write(seqs, ifname, 'fasta')

    # Align fasta.

    if backend == 'mafft':
        command = (
            'mafft ' +
            '--thread {} '.format(n_threads) +
            '--auto --treeout --inputorder ' +
            ifname
        ).split()
    else:
        raise ValueError('Unsupported backend: {}'.format(backend))

    import subprocess
    ofname = dirname + '/aligned.fasta'
    with open(ofname, 'w') as ofile, \
         open(dirname + '/' + backend + '.log', 'w') as olog:
        subprocess.run(command, stdout=ofile, stderr=olog)

    # Read alignment and turn to one-hot encoding.

    from Bio import AlignIO
    with open(ofname) as f:
        alignment = AlignIO.read(f, 'fasta')

    n_seqs = len(alignment)
    assert(n_seqs == adata.X.shape[0])
    if reference is not None:
        ref_aseq = str(alignment[reference].seq)
        n_residues = len(ref_aseq.replace('-', ''))
    else:
        n_residues = len(alignment[0].seq)
    align_matrix = np.zeros((n_seqs, n_residues))

    vocabulary = {}

    for i, record in enumerate(alignment):
        assert(record.id == 'seq{}'.format(i))
        aseq = str(record.seq)
        j = 0
        for char_idx, char in enumerate(aseq):
            if reference is not None and ref_aseq[char_idx] == '-':
                continue
            if char not in vocabulary:
                vocabulary[char] = len(vocabulary)
            align_matrix[i, j] = vocabulary[char]
            j += 1

    keys = sorted([ vocabulary[key] for key in vocabulary ])
    from sklearn.preprocessing import OneHotEncoder
    enc = OneHotEncoder(
        categories=[ keys ] * align_matrix.shape[1],
        sparse=False,
    )
    X_onehot = enc.fit_transform(align_matrix)
    assert(X_onehot.shape[1] == len(keys) * n_residues)

    lookup = { vocabulary[key]: key for key in vocabulary }

    adata.obsm[f'X_{key}'] = X_onehot
    adata.obs[f'seqs_msa'] = [ str(record.seq) for record in alignment ]
    adata.uns[f'{key}_vocabulary'] = lookup
    adata.uns[f'{key}_shape'] = [ n_residues, len(lookup) ]

    return adata if copy else None

def tool_residue_scores(
        adata,
        basis='onehot',
        scale=1.,
        key='residue_scores',
        copy=False,
):
    if f'X_{basis}' not in adata.obsm:
        raise ValueError(f'Could not find basis "{basis}", '
                         'consider running onehot_msa() first.')

    scv.tl.velocity_embedding(
        adata,
        basis=basis,
        scale=scale,
        autoscale=False,
    )

    onehot_velo = np.array(adata.obsm[f'velocity_{basis}'])

    adata.uns[key] = onehot_velo.sum(0).reshape(
        tuple(adata.uns[f'{basis}_shape'])
    )

    return adata if copy else None

def plot_residue_scores(
        adata,
        percentile_keep=75,
        basis='onehot',
        key='residue_scores',
        cmap='RdBu',
        save=None,
):
    scores = AnnData(adata.uns[key])

    vocab = adata.uns[f'{basis}_vocabulary']
    scores.var_names = [
        vocab[key] for key in sorted(vocab.keys())
    ]

    positions = [ str(x) for x in range(scores.X.shape[0]) ]
    scores.obs['position'] = positions

    if percentile_keep > 0:
        score_sum = np.abs(scores.X).sum(1)
        cutoff = np.percentile(score_sum, percentile_keep)
        scores = scores[score_sum >= cutoff]

    end = max(abs(np.min(scores.X)), np.max(scores.X)) # Zero-centered colors.
    scores.X /= end # Scale within -1 and 1, inclusive.

    plt.figure(figsize=(
        max(scores.X.shape[1] // 2, 5),
        max(scores.X.shape[0] // 20, 5)
    ))
    sns.heatmap(
        scores.X,
        xticklabels=scores.var_names,
        yticklabels=scores.obs['position'],
        cmap=cmap,
        vmin=-1.,
        vmax=1.,
    )

    if save is not None:
        plt.savefig('figures/evolocity_' + save)
        plt.close()
    else:
        return ax

def plot_residue_categories(
        adata,
        positions=None,
        n_plot=5,
        namespace='residue_categories',
        reference=None
):
    if reference is not None:
        seq_ref = adata.obs['seq'][reference]
        seq_ref_msa = adata.obs['seqs_msa'][reference]
        pos2msa, ref_idx = {}, 0
        for idx, ch in enumerate(seq_ref_msa):
            if ch == '-':
                continue
            assert(ch == seq_ref[ref_idx])
            pos2msa[ref_idx] = idx
            ref_idx += 1

    if positions is None:
        scores = adata.uns['residue_scores']
        pos_seen = set()
        while len(pos_seen) < n_plot:
            min_idx = np.unravel_index(np.argmin(scores), scores.shape)
            scores[min_idx] = float('inf')
            aa = adata.uns['onehot_vocabulary'][min_idx[1]]
            pos = min_idx[0]
            if pos in pos_seen:
                continue
            pos_seen.add(pos)
            tprint('Lowest score {}: {}{}'.format(len(pos_seen), aa, pos + 1))
        positions = sorted(pos_seen)

    for pos in positions:
        adata.obs[f'pos{pos}'] = [
            seq[pos] if reference is None else seq[pos2msa[pos]]
            for seq in adata.obs['seqs_msa']
        ]
        sc.pl.umap(adata, color=f'pos{pos}', save=f'_{namespace}_pos{pos}.png',
                   edges=True,)

def load_uniref(ds_fname, map_fname=None):
    if ds_fname.endswith('.fasta') or ds_fname.endswith('.fa'):
        uniref_seqs = set([
            str(record.seq) for record in SeqIO.parse(ds_fname, 'fasta')
        ])
    else:
        raise ValueError(f'Invalid extension for file "{ds_fname}"')

    if map_fname is None:
        return uniref_seqs

    cluster_map = {
        record.id: str(record.seq)
        for record in SeqIO.parse(ds_fname, 'fasta')
    }
    with open(map_fname) as f:
        for line in f:
            fields = line.rstrip().split()
            cluster_map[fields[0]] = fields[-1]

    return uniref_seqs, cluster_map

def check_uniref50(
        adata,
        key='uniref50',
        verbose=True,
        id_key='gene_id',
        ds_fname='data/uniref/uniref50_2018_03.fasta',
):
    uniref_seqs = load_uniref(ds_fname)
    is_uniref = [ seq in uniref_seqs for seq in adata.obs['seq'] ]

    if verbose:
        tprint('UniRef50 seqs:')
        for gene_id in adata[is_uniref].obs[id_key]:
            tprint(f'\t{gene_id}')

    adata.obs[key] = is_uniref

def training_distances(
        seqs,
        namespace='',
        key='homology',
        accession_key='accession',
        dataset='uniref',
        ds_fname='data/uniref/uniref50_2018_03.fasta',
        map_fname='data/uniref/uniref50_2018_03_mapping.txt',
        exact_search=True,
):
    dirname = 'target/training_seqs'
    if namespace:
        dirname += f'/{namespace}'
    mkdir_p(dirname)

    # Get set of closest training sequences.

    fname = dirname + f'/training_{dataset}.txt'
    if os.path.isfile(fname):
        with open(fname) as f:
            training_seqs = f.read().rstrip().split()
    else:
        dataset_seqs, cluster_map = load_uniref(ds_fname, map_fname)

        training_seqs = (set([
            seq for seq in seqs if str(seq) in dataset_seqs
        ]) | set([
            cluster_map[cluster_map[meta[accession_key]]]
            for seq in seqs for meta in seqs[seq]
            if accession_key in meta and meta[accession_key] in cluster_map
        ]))

        if namespace:
            with open(fname, 'w') as of:
                for seq in sorted(training_seqs):
                    of.write(str(seq) + '\n')

    # Compute distance to closest training sequence.

    if exact_search:
        for seq in seqs:
            ratio = fuzzyproc.extractOne(str(seq), training_seqs)[1]
            for meta in seqs[seq]:
                meta[key] = float(ratio)

    else:
        from datasketch import MinHash, MinHashLSHForest
        from nltk import ngrams

        # Index training sequences.
        lsh = MinHashLSHForest(num_perm=128)
        for seq in training_seqs:
            seq = str(seq)
            minhash = MinHash(num_perm=128)
            for d in ngrams(seq, 5):
                minhash.update(''.join(d).encode('utf-8'))
            lsh.insert(seq, minhash)
        lsh.index()

        # Query data structure for (approximately) closest seqs.
        for seq in seqs:
            minhash = MinHash(num_perm=128)
            for d in ngrams(seq, 5):
                minhash.update(''.join(d).encode('utf-8'))
            result = lsh.query(minhash, 1)[0]
            ratio = fuzz.ratio(str(seq), result)[1]
            for meta in seqs[seq]:
                meta[key] = float(ratio)

    return seqs

def plot_ancestral(
        df,
        meta_key,
        name_key='name',
        score_key='score',
        homology_key='homology',
        namespace='ancestral',
):
    if meta_key not in df:
        raise ValueError('Metadata key not found.')
    if name_key not in df:
        raise ValueError('Ancestral names key not found.')
    if score_key not in df:
        raise ValueError('Likelihood scores key not found.')

    for name in set(df[name_key]):
        df_name = df[df[name_key] == name]

        plt.figure()
        sns.boxplot(
            data=df_name, x=meta_key, y=score_key,
        )
        plt.axhline(y=0, c='#CCCCCC', linestyle='dashed')
        name_sanitized = name.replace('/', '-')
        plt.savefig(f'figures/{namespace}_ancestral_{name_sanitized}.svg',
                    dpi=500)
        plt.close()

        if homology_key in df_name:
            r, p = ss.spearmanr(df_name[score_key].values,
                                df_name[homology_key].values,
                                nan_policy='omit')
            tprint('{} corr with ancestral: Spearman r = {}, P = {}'.format(
                name, r, p
            ))

        for meta in set(df_name[meta_key]):
            score_dist = df_name[df_name[meta_key] == meta].score.values
            tprint('{} and {}: {}% percentile'.format(
                name, meta,
                ss.percentileofscore(score_dist, 0)
            ))


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

import os
import shutil
import subprocess
import math
import cv2
import numpy as np
from butterflow.settings import default as settings
from butterflow.source import OpenCvFrameSource
from butterflow import mux
from butterflow import avinfo
from butterflow import draw
from butterflow.interpolate import time_steps_for_nfrs

import logging
log = logging.getLogger('butterflow')

class Renderer(object):
    def __init__(self, src, dest, sequence, rate, optflow_fn, interpolate_fn,
                 w, h, scaling_method, lossless, keep_subregions, show_preview,
                 add_info, text_type, mark_frames, mux):
        self.src = src
        self.dest = dest
        self.sequence = sequence
        self.rate = rate
        self.optflow_fn = optflow_fn
        self.interpolate_fn = interpolate_fn
        self.w = w
        self.h = h
        self.scaling_method = scaling_method
        self.lossless = lossless
        self.keep_subregions = keep_subregions
        self.show_preview = show_preview
        self.add_info = add_info
        self.text_type = text_type
        self.mark_frames = mark_frames
        self.mux = mux
        self.pipe = None
        self.fr_source = None
        self.av_info = avinfo.get_av_info(src)
        self.source_frs = 0
        self.frs_interpolated = 0
        self.frs_duped = 0
        self.frs_dropped = 0
        self.frs_written = 0
        self.subs_to_render = 0
        self.frs_to_render = 0
        self.curr_sub_idx = 0
        self.window_title = os.path.basename(self.src) + ' - Butterflow'
        self.progress = 0

    def mk_render_pipe(self, dest):
        vf = []
        vf.append('format=yuv420p')
        call = [
            settings['avutil'],
            '-loglevel', settings['av_loglevel'],
            '-y',
            '-threads', '0',
            '-f', 'rawvideo',
            '-pix_fmt', 'bgr24',
            '-s', '{}x{}'.format(self.w, self.h),
            '-r', str(self.rate),
            '-i', '-',
            '-map_metadata', '-1',
            '-map_chapters', '-1',
            '-vf', ','.join(vf),
            '-r', str(self.rate),
            '-an',
            '-sn',
            '-c:v', settings['cv'],
            '-preset', settings['preset']]
        if settings['cv'] == 'libx264':
            quality = ['-crf', str(settings['crf'])]
            if self.lossless:
                quality = ['-qp', '0']
            call.extend(quality)
            call.extend(['-level', '4.2'])
        params = []
        call.extend(['-{}-params'.format(settings['cv'].replace('lib', ''))])
        params.append('log-level={}'.format(settings['enc_loglevel']))
        if settings['cv'] == 'libx265':
            quality = 'crf={}'.format(settings['crf'])
            if self.lossless:
                # Bug: https://trac.ffmpeg.org/ticket/4284
                quality = 'lossless=1'
            params.append(quality)
        if len(params) > 0:
            call.extend([':'.join(params)])
        call.extend([dest])
        log.info('[Subprocess] Opening a pipe to the video writer')
        log.debug('Call: {}'.format(' '.join(call)))
        self.pipe = subprocess.Popen(call, stdin=subprocess.PIPE)
        if self.pipe == 1:
            raise RuntimeError

    def close(self):
        if self.pipe and not self.pipe.stdin.closed:
            self.pipe.stdin.flush()
            self.pipe.stdin.close()
            self.pipe.wait()
            log.info('[Subprocess] Closing pipe to the video writer')

    def scale_fr(self, fr):
        return cv2.resize(fr,
                          (self.w, self.h),
                          interpolation=self.scaling_method)

    def calc_frs_to_render(self, sub):
        reg_len = (sub.fb - sub.fa) + 1
        reg_duration = (sub.tb - sub.ta) / 1000.0
        to_render = 0

        if sub.target_dur:
            to_render = int(self.rate *
                            (sub.target_dur / 1000.0))
        elif sub.target_fps:
            to_render = int(sub.target_fps * reg_duration)
        elif sub.target_spd:
            to_render = int(self.rate * reg_duration *
                            (1 / sub.target_spd))

        to_render = max(0, to_render)
        interpolate_each_go = float(to_render) / max(1, (reg_len - 1))

        if interpolate_each_go == 0:
            to_render = 1
        return to_render

    def render_subregion(self, sub):
        reg_len = (sub.fb - sub.fa) + 1
        frs_to_render = self.calc_frs_to_render(sub)
        interpolate_each_go = int(float(frs_to_render) / max(1, (reg_len - 1)))
        pairs = reg_len - 1

        log.info("Frames in region:\t%d-%d", sub.fa, sub.fb)
        log.info("Region length:\t%d", reg_len)
        log.info("Region duration:\t%fs", (sub.tb - sub.ta) / 1000.0)
        log.info("Number of frame pairs:\t%d", pairs)

        if interpolate_each_go == 0:
            log.warn("Interpolation rate:\t0 (only render S-frames)")
        else:
            log.info("Interpolation rate:\t%d", interpolate_each_go)

        steps_string = ""
        steps = time_steps_for_nfrs(interpolate_each_go)
        for i, x in enumerate(steps):
            steps_string += "{:.3f}".format(x)
            if i < len(steps)-1:
                steps_string += ","
        if steps_string == "":
            steps_string = "N/A"
        log.info("Time stepping:\t%s", steps_string)

        log.info("Frames to write:\t%d", frs_to_render)

        if pairs >= 1:
            will_make = (interpolate_each_go * pairs) + pairs
        else:
            will_make = 1

        extra_frs = will_make - frs_to_render

        log.info("Will interpolate:\t%d", will_make)
        log.info("Extra frames (to discard):\t%d", extra_frs)

        drp_every = 0
        dup_every = 0

        if extra_frs > 0:
            drp_every = will_make / math.fabs(extra_frs)
        if extra_frs < 0:
            dup_every = will_make / math.fabs(extra_frs)

        log.info("Drop every:\t%d", drp_every)
        log.info("Dupe every:\t%d", dup_every)

        src_seen = 0
        frs_interpolated = 0
        work_idx = 0
        frs_written = 0
        frs_duped = 0
        frs_dropped = 0
        runs = 0
        final_run = False

        fr_1 = None

        log.debug("Seeking to %d", sub.fa)
        self.fr_source.seek_to_fr(sub.fa)

        log.debug("Reading %d into B", sub.fa)
        fr_2 = self.fr_source.read()

        if fr_2 is None:
            log.warn("First frame in the region is None (B is None)")
        src_seen += 1

        if sub.fa == sub.fb or frs_to_render == 1:
            runs = 1
            final_run = True
            log.info("Ready to run:\t1 time (only writing S-frame)")
        else:
            log.debug("Seeking to %d", sub.fa+1)
            self.fr_source.seek_to_fr(sub.fa + 1)

            runs = reg_len
            log.info("Ready to run:\t%d times", runs)

        if self.scaling_method == settings['scaler_dn']:
            fr_2 = self.scale_fr(fr_2)

        def fr_draw_scale(w_fits, h_fits):
            return min(float(fr_2.shape[1]) / float(w_fits),
                       float(fr_2.shape[0]) / float(h_fits))

        debug_draw_scale = fr_draw_scale(settings['txt_w_fits'],
                                         settings['txt_h_fits'])
        progress_bar_draw_scale = fr_draw_scale(settings['bar_w_fits'],
                                                settings['bar_h_fits'])
        marker_draw_scale = fr_draw_scale(settings['mrk_w_fits'],
                                          settings['mrk_h_fits'])

        txt = 'Frame, Shape={}x{}, is too small to draw on: Type={{}}\tScale={{}} < Min={{}}'.format(
              fr_2.shape[1], fr_2.shape[0])
        if self.add_info and debug_draw_scale < settings['txt_min_scale']:
            log.warning(txt.format('info', debug_draw_scale,
                                   settings['txt_min_scale']))
        if self.show_preview and progress_bar_draw_scale < 1.0:
            log.warning(txt.format('progress', progress_bar_draw_scale, 1.0))
        if self.mark_frames and marker_draw_scale < 1.0:
            log.warning(txt.format('marker', marker_draw_scale, 1.0))

        show_n = settings['debug_show_n_runs']
        show_period = settings['debug_show_progress_period']
        showed_snipped_message = False

        def in_show_debug_range(x):
            if show_n == -1:
                return True
            return x <= show_n or x >= runs - show_n + 1

        if show_n == -1 or show_n*2 >= runs:
            log.info("Showing all runs:")
        else:
            log.info("Showing a sample of the first and last %d runs:", show_n)

        for run in range(0, runs):
            fr_period = max(1, int(show_period * (runs - show_n*2)))

            if not in_show_debug_range(run) and not showed_snipped_message:
                log.info("<Snipping %d runs from the console, but will update progress periodically every %d frames rendered>",
                         runs - show_n*2, fr_period)
                showed_snipped_message = True

            if not in_show_debug_range(run) and showed_snipped_message:
                if run % fr_period == 0:
                    log.info("<Rendering progress: {:.2f}%>".format(
                             self.progress*100))

            if run >= runs - 1:
                final_run = True

            pair_a = sub.fa + run
            pair_b = pair_a + 1 if run + 1 < runs else pair_a

            if final_run:
                log.info("Run %d (this is the final run):", run)
            else:
                log.debug("Run %d:", run)
            log.debug("Pair A: %d, B: %d", pair_a, pair_b)

            frs_to_write = []

            log.debug("Copy last B frame, %d, into A", self.fr_source.idx-1)
            fr_1 = fr_2
            if fr_1 is None:
                log.error("A is None")

            if final_run:
                frs_to_write.append((fr_1, 'SOURCE', 1))
                log.info("To write: S{}".format(pair_a))
            else:
                try:
                    log.debug("Read %d into B", self.fr_source.idx)
                    fr_2 = self.fr_source.read()
                except RuntimeError:
                    log.error("Couldn't read %d (will abort runs)", self.fr_source.idx)
                    log.warn("Setting B to None")
                    fr_2 = None
                if fr_2 is None:
                    log.warn("B is None")
                    frs_to_write.append((fr_1, 'SOURCE', 1))
                    final_run = True
                    log.info("To write: S{}".format(pair_a))
                if not final_run:
                    src_seen += 1

                    if self.scaling_method == settings['scaler_dn']:
                        fr_2 = self.scale_fr(fr_2)

                    fr_1_gr = cv2.cvtColor(fr_1, cv2.COLOR_BGR2GRAY)
                    fr_2_gr = cv2.cvtColor(fr_2, cv2.COLOR_BGR2GRAY)

                    f_uv = self.optflow_fn(fr_1_gr, fr_2_gr)
                    b_uv = self.optflow_fn(fr_2_gr, fr_1_gr)

                    if isinstance(f_uv, np.ndarray):
                        fu = f_uv[:,:,0]
                        fv = f_uv[:,:,1]
                        bu = b_uv[:,:,0]
                        bv = b_uv[:,:,1]
                    else:
                        fu, fv = f_uv
                        bu, bv = b_uv

                    fr_1_32 = np.float32(fr_1) * 1/255.0
                    fr_2_32 = np.float32(fr_2) * 1/255.0

                    will_write = True

                    would_drp = []
                    cmp_interpolate_each_go = interpolate_each_go
                    cmp_work_idx = work_idx - 1

                    for x in range(1 + interpolate_each_go):
                        cmp_work_idx += 1
                        if drp_every > 0 and \
                                math.fmod(cmp_work_idx, drp_every) < 1.0:
                            would_drp.append(x + 1)

                    if len(would_drp) > 0:
                        txt = ""
                        for i, x in enumerate(would_drp):
                            txt += "{}".format(str(x))
                            if i < len(would_drp)-1:
                                txt += ","
                        log.debug("Would drop indices:\t" + txt)

                        if len(would_drp) <= interpolate_each_go:
                            cmp_interpolate_each_go -= len(would_drp)
                            log.debug("Compensating interpolation rate:\t%d (-%d)",
                                      cmp_interpolate_each_go, len(would_drp))
                        else:
                            will_write = False
                        if not will_write:
                            work_idx += 1
                            self.frs_dropped += 1
                            if in_show_debug_range(run):
                                log.info("Compensating, dropping S-frame")

                    if will_write:
                        interpolated_frs = self.interpolate_fn(
                            fr_1_32, fr_2_32, fu, fv, bu, bv,
                            cmp_interpolate_each_go)

                        frs_interpolated += len(interpolated_frs)

                        frs_to_write.append((fr_1, 'SOURCE', 0))
                        for i, fr in enumerate(interpolated_frs):
                            frs_to_write.append((fr, 'INTERPOLATED', i+1))
                        if in_show_debug_range(run):
                            temp_progress = (float(self.frs_written) +
                                        len(frs_to_write)) / self.frs_to_render
                            temp_progress *= 100.0
                            log.info("To write: S{}\tI{},{}\t{:.2f}%".format(
                                     pair_a, len(interpolated_frs), len(would_drp),
                                     temp_progress))

            for i, (fr, fr_type, idx_between_pair) in enumerate(frs_to_write):
                work_idx += 1
                writes_needed = 1

                if dup_every > 0 and math.fmod(work_idx, dup_every) < 1.0:
                    frs_duped += 1
                    writes_needed = 2
                if final_run:
                    writes_needed = (frs_to_render - frs_written)
                    if drp_every > 0 and math.fmod(work_idx, drp_every) < 1.0:
                        self.frs_dropped += 1
                        if i == 0:
                            log.warn("Dropping S{}".format(pair_a))
                        else:
                            log.warn("Dropping I{}".format(idx_between_pair))
                        continue

                for write_idx in range(writes_needed):
                    fr_to_write = fr
                    frs_written += 1
                    self.frs_written += 1
                    self.progress = float(self.frs_written)/self.frs_to_render
                    is_dupe = False
                    if write_idx == 0:
                        if fr_type == 'SOURCE':
                            self.source_frs += 1
                        else:
                            self.frs_interpolated += 1
                    else:
                        is_dupe = True
                        self.frs_duped += 1
                        if fr_type == 'SOURCE':
                            log.warn("Duping S%d", pair_a)
                        else:
                            log.warn("Duping I%d", idx_between_pair)

                    if self.scaling_method == settings['scaler_up']:
                        fr = self.scale_fr(fr)

                    if self.mark_frames:
                        draw.draw_marker(fr, fill=fr_type == 'INTERPOLATED')
                    if self.add_info:
                        if writes_needed > 1:
                            fr_to_write = fr.copy()
                        draw.draw_debug_text(fr_to_write, self.text_type,
                                             self.rate, self.optflow_fn,
                                             self.frs_written, pair_a, pair_b,
                                             idx_between_pair, fr_type,
                                             is_dupe, frs_to_render,
                                             frs_written, sub,
                                             self.curr_sub_idx,
                                             self.subs_to_render,
                                             drp_every, dup_every, src_seen,
                                             frs_interpolated, frs_dropped,
                                             frs_duped)
                    if self.show_preview:
                        fr_to_show = fr.copy()
                        draw.draw_progress_bar(fr_to_show, progress=self.progress)
                        cv2.imshow(self.window_title, np.asarray(fr_to_show))
                        cv2.waitKey(settings['imshow_ms'])

                    self.pipe.stdin.write(bytes(fr_to_write.data))

    def render(self):
        filename = os.path.splitext(os.path.basename(self.src))[0]
        tempfile1 = os.path.join(
            settings['tempdir'],
            '{}.{}.{}'.format(filename, os.getpid(), settings['v_container']).lower())
        log.info("Rendering to:\t%s", os.path.basename(tempfile1))
        log.info("Final destination:\t%s", self.dest)
        self.fr_source = OpenCvFrameSource(self.src)
        self.fr_source.open()
        self.mk_render_pipe(tempfile1)
        self.frs_to_render = 0
        for sub in self.sequence.subregions:
            if not self.keep_subregions and sub.skip:
                continue
            else:
                self.subs_to_render += 1
                self.frs_to_render += self.calc_frs_to_render(sub)
        if self.show_preview:
            cv2.namedWindow(self.window_title,
                            cv2.WINDOW_OPENGL)
            cv2.resizeWindow(self.window_title, self.w, self.h)
        self.progress = 0
        log.info("Rendering progress:\t{:.2f}%".format(0))
        for i, sub in enumerate(self.sequence.subregions):
            if not self.keep_subregions and sub.skip:
                log.info("Skipping Subregion (%d): %s", i, str(sub))
                continue
            else:
                log.info("Start working on Subregion (%d): %s", i, str(sub))
                self.curr_sub_idx += 1
                self.render_subregion(sub)
                log.info("Done rendering Subregion (%d)", i)
        if self.show_preview:
            cv2.destroyAllWindows()
        self.fr_source.close()
        self.close()
        log.info("Rendering is finished")
        if self.mux:
            if self.av_info['a_stream_exists']:
                self.mux_orig_audio_with_rendered_video(tempfile1)
                return
            else:
                log.warn('Not muxing because no audio stream exists in the input file')
        log.info("Moving: %s -> %s", os.path.basename(tempfile1), self.dest)
        shutil.move(tempfile1, self.dest)

    def mux_orig_audio_with_rendered_video(self, vid):
        log.info("Muxing progress:\t{:.2f}%".format(0))
        progress = 0
        def update_progress():
            log.info("Muxing progress:\t{:.2f}%".format(progress*100))
        filename = os.path.splitext(os.path.basename(self.src))[0]
        audio_files = []
        to_extract = 0
        for sub in self.sequence.subregions:
            if not self.keep_subregions and sub.skip:
                continue
            else:
                to_extract += 1
        if to_extract == 0:
            progress += 1.0/3
            update_progress()
        progress_chunk = 1.0/to_extract/3
        for i, sub in enumerate(self.sequence.subregions):
            if not self.keep_subregions and sub.skip:
                continue
            tempfile1 = os.path.join(
                settings['tempdir'],
                '{}.{}.{}.{}'.format(filename,
                                     i,
                                     os.getpid(),
                                     settings['a_container']).lower())
            log.info("Start working on audio from subregion (%d):", i)
            log.info("Extracting to:\t%s", os.path.basename(tempfile1))
            speed = sub.target_spd
            if speed is None:
                reg_duration = (sub.tb - sub.ta) / 1000.0
                frs = self.calc_frs_to_render(sub)
                speed = (self.rate * reg_duration) / frs
                log.info("Speed not set for mux, calculated as: %fx", speed)
            mux.extract_audio(self.src,
                              tempfile1,
                              sub.ta,
                              sub.tb,
                              speed)
            audio_files.append(tempfile1)
            progress += progress_chunk
            update_progress()
        tempfile2 = os.path.join(
            settings['tempdir'],
            '{}.merged.{}.{}'.format(filename,
                                     os.getpid(),
                                     settings['a_container']).lower())
        log.info("Merging to:\t%s", os.path.basename(tempfile2))
        mux.concat_av_files(tempfile2, audio_files)
        progress += 1.0/3
        update_progress()
        mux.mux_av(vid, tempfile2, self.dest)
        progress += 1.0/3
        update_progress()
        for file in audio_files:
            log.info("Delete:\t%s", os.path.basename(file))
            os.remove(file)
        log.info("Delete:\t%s", os.path.basename(tempfile2))
        os.remove(tempfile2)
        log.info("Delete:\t%s", os.path.basename(vid))
        os.remove(vid)

    def __del__(self):
        self.close()


from numpy import zeros, array
import numpy as np
from wisdem.aeroelasticse.FAST_vars_out import FstOutput, Fst7Output

# This variable tree contains all parameters required to create a FAST model
# for FAST versions 7 and 8.

# .fst Simulation Control
Fst = {}
Fst['Echo']            = False
Fst['AbortLevel']      = ''
Fst['TMax']            = 0.0
Fst['DT']              = 0.0
Fst['InterpOrder']     = 0
Fst['NumCrctn']        = 0
Fst['DT_UJac']         = 0.0
Fst['UJacSclFact']     = 0.0

# Feature Switches and Flags
Fst['CompElast']    = 0
Fst['CompInflow']   = 0
Fst['CompAero']     = 0
Fst['CompServo']    = 0
Fst['CompHydro']    = 0
Fst['CompSub']      = 0
Fst['CompMooring']  = 0
Fst['CompIce']      = 0
Fst['CompNoise']    = 0             #FAST7 only

# Input Files
Fst['EDFile']          = ''
Fst['BDBldFile1']      = ''
Fst['BDBldFile2']      = ''
Fst['BDBldFile3']      = ''
Fst['InflowFile']      = ''
Fst['AeroFile']        = ''
Fst['ServoFile']       = ''
Fst['HydroFile']       = ''
Fst['SubFile']         = ''
Fst['MooringFile']     = ''
Fst['IceFile']         = ''

# FAST Output Parameters
Fst['SumPrint']   = False
Fst['SttsTime']   = 0.0
Fst['ChkptTime']  = 0.0
Fst['DT_Out']     = 0.0
Fst['TStart']     = 0.0
Fst['OutFileFmt'] = 0
Fst['TabDelim']   = False
Fst['OutFmt']     = ''

# Fst
Fst['Linearize']    = False
Fst['NLinTimes']    = 2
Fst['LinTimes']     = (30,          60)
Fst['LinInputs']    = 1
Fst['LinOutputs']   = 1
Fst['LinOutJac']    = False
Fst['LinOutMod']    = False

# Fst
Fst['WrVTK']        = 0
Fst['VTK_type']     = 0
Fst['VTK_fields']   = False
Fst['VTK_fps']      = 0

# ElastoDyn Simulation Control
ElastoDyn = {}
ElastoDyn['Echo']             = False
ElastoDyn['Method']           = 0
ElastoDyn['DT']               = 0.0

# Environmental Condition
ElastoDyn['Gravity']          = 0.0

# Degrees of Freedom
ElastoDyn['FlapElastoDyn1']               = False
ElastoDyn['FlapElastoDyn2']               = False
ElastoDyn['EdgeElastoDyn']                = False
ElastoDyn['TeetElastoDyn']                = False
ElastoDyn['DrTrElastoDyn']                = False
ElastoDyn['GenElastoDyn']                 = False
ElastoDyn['YawElastoDyn']                 = False
ElastoDyn['TwFAElastoDyn1']               = False
ElastoDyn['TwFAElastoDyn2']               = False
ElastoDyn['TwSSElastoDyn1']               = False
ElastoDyn['TwSSElastoDyn2']               = False
ElastoDyn['PtfmSgElastoDyn']              = False
ElastoDyn['PtfmSwElastoDyn']              = False
ElastoDyn['PtfmHvElastoDyn']              = False
ElastoDyn['PtfmRElastoDyn']               = False
ElastoDyn['PtfmPElastoDyn']               = False
ElastoDyn['PtfmYElastoDyn']               = False

# Initial Conditions
ElastoDyn['OoPDefl']          = 0.0
ElastoDyn['IPDefl']           = 0.0
ElastoDyn['BlPitch1']         = 0.0
ElastoDyn['BlPitch2']         = 0.0
ElastoDyn['BlPitch3']         = 0.0
ElastoDyn['TeetDefl']         = 0.0
ElastoDyn['Azimuth']          = 0.0
ElastoDyn['RotSpeed']         = 0.0
ElastoDyn['NacYaw']           = 0.0
ElastoDyn['TTDspFA']          = 0.0
ElastoDyn['TTDspSS']          = 0.0
ElastoDyn['PtfmSurge']        = 0.0
ElastoDyn['PtfmSway']         = 0.0
ElastoDyn['PtfmHeave']        = 0.0
ElastoDyn['PtfmRoll']         = 0.0
ElastoDyn['PtfmPitch']        = 0.0
ElastoDyn['PtfmYaw']          = 0.0

# Turbine Configuration
ElastoDyn['NumBl']           = 0
ElastoDyn['TipRad']          = 0.0
ElastoDyn['HubRad']          = 0.0
ElastoDyn['PreCone1']        = 0.0
ElastoDyn['PreCone2']        = 0.0
ElastoDyn['PreCone3']        = 0.0
ElastoDyn['HubCM']           = 0.0
ElastoDyn['UndSling']        = 0.0
ElastoDyn['Delta3']          = 0.0
ElastoDyn['AzimB1Up']        = 0.0
ElastoDyn['OverHang']        = 0.0
ElastoDyn['ShftGagL']        = 0.0
ElastoDyn['ShftTilt']        = 0.0
ElastoDyn['NacCMxn']         = 0.0
ElastoDyn['NacCMyn']         = 0.0
ElastoDyn['NacCMzn']         = 0.0
ElastoDyn['NcIMUxn']         = 0.0
ElastoDyn['NcIMUyn']         = 0.0
ElastoDyn['NcIMUzn']         = 0.0
ElastoDyn['Twr2Shft']        = 0.0
ElastoDyn['TowerHt']         = 0.0
ElastoDyn['TowerBsHt']       = 0.0
ElastoDyn['PtfmCMxt']        = 0.0
ElastoDyn['PtfmCMyt']        = 0.0
ElastoDyn['PtfmCMzt']        = 0.0
ElastoDyn['PtfmRefzt']       = 0.0

# Mass and Inertia
ElastoDyn['TipMass1']       = 0.0
ElastoDyn['TipMass2']       = 0.0
ElastoDyn['TipMass2']       = 0.0
ElastoDyn['HubMass']        = 0.0
ElastoDyn['HubIner']        = 0.0
ElastoDyn['GenIner']        = 0.0
ElastoDyn['NacMass']        = 0.0
ElastoDyn['NacYIner']       = 0.0
ElastoDyn['YawBrMass']      = 0.0
ElastoDyn['PtfmMass']       = 0.0
ElastoDyn['PtfmRIner']      = 0.0
ElastoDyn['PtfmPIner']      = 0.0
ElastoDyn['PtfmYIner']      = 0.0

# ED Blade (Structure)
ElastoDyn['BldNodes']        = 0
ElastoDyn['BldFile1']        = ''
ElastoDyn['BldFile2']        = ''
ElastoDyn['BldFile3']        = ''

# Including the blade files and properties in the same object,
# as is done here, implies that the properties are done for all
# blades (assumed for now)

# General Model Inputs
ElastoDynBlade = {}
ElastoDynBlade['NBlInpSt']        = 0             #Number of blade input stations (-)
ElastoDynBlade['BldFlDmp1']       = 0.0           #Blade flap mode #1 structural damping in percent of critical (%)
ElastoDynBlade['BldFlDmp2']       = 0.0           #Blade flap mode #2 structural damping in percent of critical (%)
ElastoDynBlade['BldEdDmp1']       = 0.0           #Blade edge mode #1 structural damping in percent of critical (%)
ElastoDynBlade['FlStTunr1']       = 0.0           #Blade flapwise modal stiffness tuner, 1st mode (-)
ElastoDynBlade['FlStTunr2']       = 0.0           #Blade flapwise modal stiffness tuner, 2nd mode (-)
ElastoDynBlade['AdjBlMs']         = 0.0           #Factor to adjust blade mass density (-)
ElastoDynBlade['AdjFlSt']         = 0.0           #Factor to adjust blade flap stiffness (-)
ElastoDynBlade['AdjEdSt']         = 0.0           #Factor to adjust blade edge stiffness (-)

# Distributed Blade Properties
ElastoDynBlade['BlFract']         = zeros([1])
ElastoDynBlade['AeroCent']        = zeros([1])
ElastoDynBlade['PitchAxis']       = zeros([1])
ElastoDynBlade['StrcTwst']        = zeros([1])
ElastoDynBlade['BMassDen']        = zeros([1])
ElastoDynBlade['FlpStff']         = zeros([1])
ElastoDynBlade['EdgStff']         = zeros([1])
ElastoDynBlade['GJStff']          = zeros([1])
ElastoDynBlade['EAStff']          = zeros([1])
ElastoDynBlade['Alpha']           = zeros([1])
ElastoDynBlade['FlpIner']         = zeros([1])
ElastoDynBlade['EdgIner']         = zeros([1])
ElastoDynBlade['PrecrvRef']       = zeros([1])
ElastoDynBlade['PreswpRef']       = zeros([1])    #[AH] Added during openmdao1 update
ElastoDynBlade['FlpcgOf']         = zeros([1])
ElastoDynBlade['Edgcgof']         = zeros([1])
ElastoDynBlade['FlpEAOf']         = zeros([1])
ElastoDynBlade['EdgEAOf']         = zeros([1])

# Blade Mode Shapes
ElastoDynBlade['BldFl1Sh']        = zeros([1])
ElastoDynBlade['BldFl2Sh']        = zeros([1])
ElastoDynBlade['BldEdgSh']        = zeros([1])

# Rotor-Teeter
ElastoDyn['TeetMod']        = 0
ElastoDyn['TeetDmpP']       = 0.0
ElastoDyn['TeetDmp']        = 0.0
ElastoDyn['TeetCDmp']       = 0.0
ElastoDyn['TeetSStP']       = 0.0
ElastoDyn['TeetHStP']       = 0.0
ElastoDyn['TeetSSSp']       = 0.0
ElastoDyn['TeetHSSp']       = 0.0

ElastoDyn['GBoxEff']         = 0.0
ElastoDyn['GBRatio']         = 0.0
ElastoDyn['DTTorSpr']        = 0.0
ElastoDyn['DTTorDmp']        = 0.0

ElastoDyn['ElastoDyn']            = False
ElastoDyn['FurlFile']           = ''

ElastoDyn['TwrNodes']             = 0
ElastoDyn['TwrFile']              = ''

# General Tower Parameters
ElastoDynTower = {}
ElastoDynTower['NTwInptSt']            = 0             #Number of input stations to specify tower geometry
ElastoDynTower['CalcTMode']            = False         #calculate tower mode shapes internally {T: ignore mode shapes from below, F: use mode shapes from below} [CURRENTLY IGNORED] (flag)
ElastoDynTower['TwrFADmp1']            = 0.0           #Tower 1st fore-aft mode structural damping ratio (%)
ElastoDynTower['TwrFADmp2']            = 0.0           #Tower 2nd fore-aft mode structural damping ratio (%)
ElastoDynTower['TwrSSDmp1']            = 0.0           #Tower 1st side-to-side mode structural damping ratio (%)
ElastoDynTower['TwrSSDmp2']            = 0.0           #Tower 2nd side-to-side mode structural damping ratio (%)

# Tower Adjustment Factors
ElastoDynTower['FAStTunr1']            = 0.0           #Tower fore-aft modal stiffness tuner, 1st mode (-)
ElastoDynTower['FAStTunr2']            = 0.0           #Tower fore-aft modal stiffness tuner, 2nd mode (-)
ElastoDynTower['SSStTunr1']            = 0.0           #Tower side-to-side stiffness tuner, 1st mode (-)
ElastoDynTower['SSStTunr2']            = 0.0           #Tower side-to-side stiffness tuner, 2nd mode (-)
ElastoDynTower['AdjTwMa']              = 0.0           #Factor to adjust tower mass density (-)
ElastoDynTower['AdjFASt']              = 0.0           #Factor to adjust tower fore-aft stiffness (-)
ElastoDynTower['AdjSSSt']              = 0.0           #Factor to adjust tower side-to-side stiffness (-)

# Distributed Tower Properties
ElastoDynTower['HtFract']              = zeros([1])
ElastoDynTower['TMassDen']             = zeros([1])
ElastoDynTower['TwFAStif']             = zeros([1])
ElastoDynTower['TwSSStif']             = zeros([1])
ElastoDynTower['TwGJStif']             = zeros([1])
ElastoDynTower['TwEAStif']             = zeros([1])
ElastoDynTower['TwFAIner']             = zeros([1])
ElastoDynTower['TwSSIner']             = zeros([1])
ElastoDynTower['TwFAcgOf']             = zeros([1])
ElastoDynTower['TwSScgOf']             = zeros([1])

# Tower Mode Shapes
ElastoDynTower['TwFAM1Sh']             = zeros([1])    #Tower Fore-Aft Mode 1 Shape Coefficients x^2, x^3, x^4, x^5, x^6
ElastoDynTower['TwFAM2Sh']             = zeros([1])    #Tower Fore-Aft Mode 2 Shape Coefficients x^2, x^3, x^4, x^5, x^6
ElastoDynTower['TwSSM1Sh']             = zeros([1])    #Tower Side-to-Side Mode 1 Shape Coefficients x^2, x^3, x^4, x^5, x^6
ElastoDynTower['TwSSM2Sh']             = zeros([1])    #Tower Side-to-Side Mode 2 Shape Coefficients x^2, x^3, x^4, x^5, x^6

ElastoDyn = {}
ElastoDyn['SumPrint']       = False
ElastoDyn['OutFile']        = 0
ElastoDyn['TabDelim']       = False
ElastoDyn['OutFmt']         = ''
ElastoDyn['TStart']         = 0.0
ElastoDyn['DecFact']        = 0.0
ElastoDyn['NTwGages']       = 0
ElastoDyn['TwrGagNd']       = []
ElastoDyn['NBlGages']       = 0
ElastoDyn['BldGagNd']       = []

# Inflow Wind General Parameters
InflowWind = {}
InflowWind['Echo']            = False
InflowWind['WindType']        = 0
InflowWind['PropagationDir']  = 0.0
InflowWind['NWindVel']        = 0
InflowWind['WindVxiList']     = 0.0
InflowWind['WindVyiList']     = 0.0
InflowWind['WindVziList']     = 0.0

# Parameters for Steady Wind Conditions [used only for WindType = 1]
InflowWind['HWindSpeed'] = 0.0
InflowWind['RefHt']     = 0.0
InflowWind['PLexp']     = 0.0

# Parameters for Uniform wind file [used only for WindType = 2]
InflowWind['Filename'] = ''
InflowWind['RefHt']    = 0.0
InflowWind['RefLength'] = 0.0

# Parameters for Binary TurbSim Full-Field files [used only for WindType = 3]
InflowWind['Filename'] = ''

# Parameters for Binary Bladed-style Full-Field files [used only for WindType = 4]
InflowWind['FilenameRoot'] = ''
InflowWind['TowerFile'] = False

# Parameters for HAWC-format binary files [Only used with WindType = 5]
InflowWind['FileName_u']  = ''
InflowWind['FileName_v']  = ''
InflowWind['FileName_w']  = ''
InflowWind['nx']          = 0
InflowWind['ny']          = 0
InflowWind['nz']          = 0
InflowWind['dx']          = 0.0
InflowWind['dy']          = 0.0
InflowWind['dz']          = 0.0
InflowWind['RefHt']       = 0.0
InflowWind['ScaleMethod'] = 0
InflowWind['SFx']         = 0.0
InflowWind['SFy']         = 0.0
InflowWind['SFz']         = 0.0
InflowWind['SigmaFx']     = 0.0
InflowWind['SigmaFy']     = 0.0
InflowWind['SigmaFz']     = 0.0
InflowWind['URef']        = 0.0
InflowWind['WindProfile'] = 0
InflowWind['PLExp']       = 0.0
InflowWind['Z0']          = 0.0

# Inflow Wind Output Parameters (actual OutList included in master OutList)
InflowWind['SumPrint']   = False

# # Wnd Wind File Parameters
# WndWind = {}
# WndWind['TimeSteps']          = 0             #number of time steps
# WndWind['Time']               = zeros([1])    #time steps
# WndWind['HorSpd']             = zeros([1])    #horizontal wind speed
# WndWind['WindDir']            = zeros([1])    #wind direction
# WndWind['VerSpd']             = zeros([1])    #vertical wind speed
# WndWind['HorShr']             = zeros([1])    #horizontal shear
# WndWind['VerShr']             = zeros([1])    #vertical power-law shear
# WndWind['LnVShr']             = zeros([1])    #vertical linear shear
# WndWind['GstSpd']             = zeros([1])    #gust speed not sheared by Aerodyn

# AeroDyn Parameters
AeroDyn14 = {}
# General Model Inputs
AeroDyn14['StallMod']         = ""
AeroDyn14['UseCm']            = ""
AeroDyn14['InfModel']         = ""
AeroDyn14['IndModel']         = ""
AeroDyn14['AToler']           = 0.
AeroDyn14['TLModel']          = ""
AeroDyn14['HLModel']          = ""
AeroDyn14['TwrShad']          = ""
AeroDyn14['TwrPotent']        = False
AeroDyn14['TwrShadow']        = False
AeroDyn14['TwrFile']          = ""
AeroDyn14['CalcTwrAero']      = False
AeroDyn14['AirDens']          = 0.0
AeroDyn14['KinVisc']          = 0.0
AeroDyn14['DTAero']           = "default"
AeroDyn14['NumFoil']          = 0
AeroDyn14['FoilNm']           = [""]

AeroDynBlade = {}
AeroDynBlade['BldNodes']         = 0
AeroDynBlade['RNodes']           = np.asarray([])
AeroDynBlade['AeroTwst']         = np.asarray([])
AeroDynBlade['DRNodes']          = np.asarray([])
AeroDynBlade['Chord']            = np.asarray([])
AeroDynBlade['NFoil']            = np.asarray([])
AeroDynBlade['PrnElm']           = np.asarray([])

# AeroDyn Blade
AeroDynTower = {}
AeroDynTower['NTwrHt']        = 0
AeroDynTower['NTwrRe']        = 0
AeroDynTower['NTwrCD']        = 0
AeroDynTower['Tower_Wake_Constant'] = 0.
AeroDynTower['TwrHtFr']       = np.asarray([])
AeroDynTower['TwrWid']        = np.asarray([])
AeroDynTower['NTwrCDCol']     = np.asarray([])
AeroDynTower['TwrRe']         = np.asarray([])
AeroDynTower['TwrCD']         = np.asarray([])

# AeroDyn Airfoil Polar
AeroDynPolar = {}
AeroDynPolar['IDParam']     = 0.0           #Table ID Parameter (Typically Reynolds number)
AeroDynPolar['StallAngle']  = 0.0           #Stall angle (deg)
AeroDynPolar['ZeroCn']      = 0.0           #Zero lift angle of attack (deg)
AeroDynPolar['CnSlope']     = 0.0           #Cn slope for zero lift (dimensionless)
AeroDynPolar['CnPosStall']  = 0.0           #Cn at stall value for positive angle of attack
AeroDynPolar['CnNegStall']  = 0.0           #Cn at stall value for negative angle of attack
AeroDynPolar['alphaCdMin']  = 0.0           #Angle of attack for minimum CD (deg)
AeroDynPolar['CdMin']       = 0.0           #Minimum Cd Value

AeroDynPolar['alpha']       = zeros([1])    #angle of attack
AeroDynPolar['cl']          = zeros([1])    #coefficient of lift
AeroDynPolar['cd']          = zeros([1])    #coefficient of drag
AeroDynPolar['cm']          = zeros([1])    #coefficient of the pitching moment

AeroDynPolar['Re']          = 0.0
AeroDynPolar['Ctrl']        = 0.0
AeroDynPolar['InclUAdata']  = 0.0
AeroDynPolar['alpha0']      = 0.0
AeroDynPolar['alpha1']      = 0.0
AeroDynPolar['alpha2']      = 0.0
AeroDynPolar['eta_e']       = 0.0
AeroDynPolar['C_nalpha']    = 0.0
AeroDynPolar['T_f0']        = 0.0
AeroDynPolar['T_V0']        = 0.0
AeroDynPolar['T_p']         = 0.0
AeroDynPolar['T_VL']        = 0.0
AeroDynPolar['b1']          = 0.0
AeroDynPolar['b2']          = 0.0
AeroDynPolar['b5']          = 0.0
AeroDynPolar['A1']          = 0.0
AeroDynPolar['A2']          = 0.0
AeroDynPolar['A5']          = 0.0
AeroDynPolar['S1']          = 0.0
AeroDynPolar['S2']          = 0.0
AeroDynPolar['S3']          = 0.0
AeroDynPolar['S4']          = 0.0
AeroDynPolar['Cn1']         = 0.0
AeroDynPolar['Cn2']         = 0.0
AeroDynPolar['St_sh']       = 0.0
AeroDynPolar['Cd0']         = 0.0
AeroDynPolar['Cm0']         = 0.0
AeroDynPolar['k0']          = 0.0
AeroDynPolar['k1']          = 0.0
AeroDynPolar['k2']          = 0.0
AeroDynPolar['k3']          = 0.0
AeroDynPolar['k1_hat']      = 0.0
AeroDynPolar['x_cp_bar']    = 0.0
AeroDynPolar['UACutout']    = 0.0
AeroDynPolar['filtCutOff']  = 0.0

# AeroDyn15
AeroDyn15 = {}
AeroDyn15['Echo']           = False
AeroDyn15['DTAero']         = 0.0
AeroDyn15['WakeMod']        = 0
AeroDyn15['AFAeroMod']      = 0
AeroDyn15['TwrPotent']      = 0
AeroDyn15['TwrShadow']      = False
AeroDyn15['TwrAero']        = False
AeroDyn15['FrozenWake']     = False
AeroDyn15['CavitCheck']     = False

AeroDyn15['AirDens']        = 0.0
AeroDyn15['KinVisc']        = 0.0
AeroDyn15['SpdSound']       = 0.0
AeroDyn15['Patm']           = 0.0
AeroDyn15['Pvap']           = 0.0
AeroDyn15['FluidDepth']     = 0.0

AeroDyn15['SkewMod']        = 0
AeroDyn15['SkewModFactor']  = "default"
AeroDyn15['TipLoss']        = False
AeroDyn15['HubLoss']        = False
AeroDyn15['TanInd']         = False
AeroDyn15['AIDrag']         = False
AeroDyn15['TIDrag']         = False
AeroDyn15['IndToler']       = 0.0
AeroDyn15['MaxIter']        = 0

AeroDyn15['DBEMT_Mod']      = 2
AeroDyn15['tau1_const']     = 4

AeroDyn15['UAMod']          = 0
AeroDyn15['FLookup']        = False

AeroDyn15['InCol_Alfa']        = 0
AeroDyn15['InCol_Cl']          = 0
AeroDyn15['InCol_Cd']          = 0
AeroDyn15['InCol_Cm']          = 0
AeroDyn15['InCol_Cpmin']       = 0
AeroDyn15['NumAFfiles']        = 0
AeroDyn15['AFNames']           = []

AeroDyn15['UseBlCm']           = False
AeroDyn15['ADBlFile1']         = ''
AeroDyn15['ADBlFile2']         = ''
AeroDyn15['ADBlFile3']         = ''

AeroDyn15['NumTwrNds']         = 0
AeroDyn15['TwrElev']           = []
AeroDyn15['TwrDiam']           = []
AeroDyn15['TwrCd']             = []
AeroDyn15['TwrElev']           = []
AeroDyn15['TwrDiam']           = []
AeroDyn15['TwrCd']             = []

AeroDyn15['SumPrint']          = False
AeroDyn15['NBlOuts']           = 0
AeroDyn15['BlOutNd']           = []
AeroDyn15['NTwOuts']           = 0
AeroDyn15['TwOutNd']           = []

# ServoDyn Simulation Control
ServoDyn = {}
ServoDyn['Echo']             = False
ServoDyn['DT']               = 0.0

# Pitch Control
ServoDyn['PCMode']           = 0
ServoDyn['TPCOn']            = 0.0
ServoDyn['TPitManS1']        = 0.0
ServoDyn['TPitManS2']        = 0.0
ServoDyn['TPitManS3']        = 0.0
ServoDyn['TPitManE1']        = 0.0           #FAST7 only
ServoDyn['TPitManE2']        = 0.0           #FAST7 only
ServoDyn['TPitManE3']        = 0.0           #FAST7 only
ServoDyn['PitManRat1']       = 0.0
ServoDyn['PitManRat2']       = 0.0
ServoDyn['PitManRat3']       = 0.0
ServoDyn['BlPitchF1']        = 0.0
ServoDyn['BlPitchF2']        = 0.0
ServoDyn['BlPitchF3']        = 0.0
ServoDyn['BlPitch1']         = 0.0           #FAST7 only
ServoDyn['BlPitch2']         = 0.0           #FAST7 only
ServoDyn['BlPitch3']         = 0.0           #FAST7 only

# Generator and Torque Control
ServoDyn['VSContrl']         = 0
ServoDyn['GenModel']         = 0
ServoDyn['GenEff']           = 0.0
ServoDyn['GenTiStr']         = False
ServoDyn['GenTiStp']         = False
ServoDyn['SpdGenOn']         = 0.0
ServoDyn['TimGenOn']         = 0.0
ServoDyn['TimGenOf']         = 0.0

# Simple Variable-Speed Torque Control
ServoDyn['VS_RtGnSp']        = 0.0
ServoDyn['VS_RtTq']          = 0.0
ServoDyn['VS_Rgn2K']         = 0.0
ServoDyn['VS_SlPc']          = 0.0

# Simple Induction Generator
ServoDyn['SIG_SlPc']         = 0.0
ServoDyn['SIG_SySp']         = 0.0
ServoDyn['SIG_RtTq']         = 0.0
ServoDyn['SIG_PORt']         = 0.0

# Thevenin-Equivalent Induction Generator
ServoDyn['TEC_Freq']         = 0.0
ServoDyn['TEC_NPol']         = 0
ServoDyn['TEC_SRes']         = 0.0
ServoDyn['TEC_RRes']         = 0.0
ServoDyn['TEC_VLL']          = 0.0
ServoDyn['TEC_SLR']          = 0.0
ServoDyn['TEC_RLR']          = 0.0
ServoDyn['TEC_MR']           = 0.0

# High-Speed Shaft Brake
ServoDyn['HSSBrMode']        = 0
ServoDyn['THSSBrDp']         = 0.0
ServoDyn['HSSBrDT']          = 0.0
ServoDyn['HSSBrTqF']         = 0.0

# Nacelle-Yaw Control
ServoDyn['YCMode']           = 0
ServoDyn['TYCOn']            = 0.0
ServoDyn['YawNeut']          = 0.0
ServoDyn['YawSpr']           = 0.0
ServoDyn['YawDamp']          = 0.0
ServoDyn['TYawManS']         = 0.0
ServoDyn['YawManRat']        = 0.0
ServoDyn['NacYawF']          = 0.0

# Tip Brake (used in FAST7 only)
ServoDyn['TiDynBrk']         = 0.0
ServoDyn['TTpBrDp1']         = 0.0
ServoDyn['TTpBrDp2']         = 0.0
ServoDyn['TTpBrDp3']         = 0.0
ServoDyn['TBDepISp1']        = 0.0
ServoDyn['TBDepISp2']        = 0.0
ServoDyn['TBDepISp3']        = 0.0
ServoDyn['TBDrConN']         = 0.0
ServoDyn['TBDrConD']         = 0.0
ServoDyn['TpBrDT']           = 0.0

# Tuned Mass Damper
ServoDyn = {}
ServoDyn['CompNTMD']         = False
ServoDyn['NTMDfile']         = ''
ServoDyn['CompTTMD']         = False
ServoDyn['TTMDfile']         = ''

# Bladed Interface
ServoDyn['DLL_FileName']     = ''
ServoDyn['DLL_InFile']       = ''
ServoDyn['DLL_ProcName']     = ''
ServoDyn['DLL_DT']           = ''
ServoDyn['DLL_Ramp']         = False
ServoDyn['BPCutoff']         = 0.0
ServoDyn['NacYaw_North']     = 0.0
ServoDyn['Ptch_Cntrl']       = 0.0
ServoDyn['Ptch_SetPnt']      = 0.0
ServoDyn['Ptch_Min']         = 0.0
ServoDyn['Ptch_Max']         = 0.0
ServoDyn['PtchRate_Min']     = 0.0
ServoDyn['PtchRate_Max']     = 0.0
ServoDyn['Gain_OM']          = 0.0
ServoDyn['GenSpd_MinOM']     = 0.0
ServoDyn['GenSpd_MaxOM']     = 0.0
ServoDyn['GenSpd_Dem']       = 0.0
ServoDyn['GenTrq_Dem']       = 0.0
ServoDyn['GenPwr_Dem']       = 0.0
ServoDyn['DLL_NumTrq']       = 0.0
ServoDyn['GenSpd_TLU']       = zeros([0])
ServoDyn['GenTrq_TLU']       = zeros([0])

# ServoDyn Output Params
ServoDyn['SumPrint']         = False
ServoDyn['OutFile']          = 0
ServoDyn['TabDelim']         = False
ServoDyn['OutFmt']           = ''
ServoDyn['TStart']           = 0.0

# HydroDyn Input File
HydroDyn = {}
HydroDyn['Echo']             = False

# ENVIRONMENTAL CONDITIONS
HydroDyn['WtrDens']          = 0.
HydroDyn['WtrDpth']          = 0.
HydroDyn['MSL2SWL']          = 0.

# WAVES
HydroDyn['WaveMod']          = 0
HydroDyn['WaveStMod']        = 0
HydroDyn['WaveTMax']         = 0.
HydroDyn['WaveDT']           = 0.
HydroDyn['WaveHs']           = 0.
HydroDyn['WaveTp']           = 0.
HydroDyn['WavePkShp']        = "DEFAULT"
HydroDyn['WvLowCOff']        = 0.
HydroDyn['WvHiCOff']         = 0.
HydroDyn['WaveDir']          = 0.
HydroDyn['WaveDirMod']       = 0
HydroDyn['WaveDirSpread']    = 0.
HydroDyn['WaveNDir']         = 0
HydroDyn['WaveDirRange']     = 0.
HydroDyn['WaveSeed1']        = 0.
HydroDyn['WaveSeed2']        = 0.
HydroDyn['WaveNDAmp']        = False
HydroDyn['WvKinFile']        = ""
HydroDyn['NWaveElev']        = 0.
HydroDyn['WaveElevxi']       = 0.
HydroDyn['WaveElevyi']       = 0.

# 2ND-ORDER WAVES
HydroDyn['WvDiffQTF']        = False
HydroDyn['WvSumQTF']         = False
HydroDyn['WvLowCOffD']       = 0.
HydroDyn['WvHiCOffD']        = 0.
HydroDyn['WvLowCOffS']       = 0.
HydroDyn['WvHiCOffS']        = 0.

# CURRENT
HydroDyn['CurrMod']          = 0
HydroDyn['CurrSSV0']         = 0.
HydroDyn['CurrSSDir']        = "DEFAULT"
HydroDyn['CurrNSRef']        = 0.
HydroDyn['CurrNSV0']         = 0.
HydroDyn['CurrNSDir']        = 0.
HydroDyn['CurrDIV']          = 0.
HydroDyn['CurrDIDir']        = 0.

# FLOATING PLATFORM
HydroDyn['PotMod']           = 0
HydroDyn['PotFile']          = ""
HydroDyn['WAMITULEN']        = 0.
HydroDyn['PtfmVol0']         = 0.
HydroDyn['PtfmCOBxt']        = 0.
HydroDyn['PtfmCOByt']        = 0.
HydroDyn['RdtnMod']          = 0
HydroDyn['RdtnTMax']         = 0.
HydroDyn['RdtnDT']           = 0.

# 2ND-ORDER FLOATING PLATFORM FORCES
HydroDyn['MnDrift']          = 0.
HydroDyn['NewmanApp']        = 0.
HydroDyn['DiffQTF']          = 0.
HydroDyn['SumQTF']           = 0.

# FLOATING PLATFORM FORCE FLAGS
HydroDyn['PtfmSgF']          = True
HydroDyn['PtfmSwF']          = True
HydroDyn['PtfmHvF']          = True
HydroDyn['PtfmRF']           = True
HydroDyn['PtfmPF']           = True
HydroDyn['PtfmYF']           = True

# PLATFORM ADDITIONAL STIFFNESS AND DAMPING
HydroDyn['AddF0']            = np.zeros((1,6))
HydroDyn['AddCLin']          = np.zeros((6,6))
HydroDyn['AddBLin']          = np.zeros((6,6))
HydroDyn['AddBQuad']         = np.zeros((6,6))

# AXIAL COEFFICIENTS
HydroDyn['NAxCoef']          = 0
HydroDyn['AxCoefID']         = 0
HydroDyn['AxCd']             = 0.
HydroDyn['AxCa']             = 0.
HydroDyn['AxCp']             = 0.

# MEMBER JOINTS
HydroDyn['NJoints']          = 0
HydroDyn['JointID']          = []
HydroDyn['Jointxi']          = []
HydroDyn['Jointyi']          = []
HydroDyn['Jointzi']          = []
HydroDyn['JointAxID']        = []
HydroDyn['JointOvrlp']       = []

# MEMBER CROSS-SECTION PROPERTIES
HydroDyn['NPropSets']        = 0
HydroDyn['PropSetID']        = []
HydroDyn['PropD']            = []
HydroDyn['PropThck']         = []

# SIMPLE HYDRODYNAMIC COEFFICIENTS
HydroDyn['SimplCd']          = 0.
HydroDyn['SimplCdMG']        = 0.
HydroDyn['SimplCa']          = 0.
HydroDyn['SimplCaMG']        = 0.
HydroDyn['SimplCp']          = 0.
HydroDyn['SimplCpMG']        = 0.
HydroDyn['SimplAxCa']        = 0.
HydroDyn['SimplAxCaMG']      = 0.
HydroDyn['SimplAxCp']        = 0.
HydroDyn['SimplAxCpMG']      = 0.

# DEPTH-BASED HYDRODYNAMIC COEFFICIENTS
HydroDyn['NCoefDpth']        = 0
HydroDyn['Dpth']             = []
HydroDyn['DpthCd']           = []
HydroDyn['DpthCdMG']         = []
HydroDyn['DpthCa']           = []
HydroDyn['DpthCaMG']         = []
HydroDyn['DpthCp']           = []
HydroDyn['DpthCpMG']         = []
HydroDyn['DpthAxCa']         = []
HydroDyn['DpthAxCaMG']       = []
HydroDyn['DpthAxCp']         = []
HydroDyn['DpthAxCpMG']       = []

# MEMBER-BASED HYDRODYNAMIC COEFFICIENTS
HydroDyn['NCoefMembers']     = 0
HydroDyn['MemberID_HydC']    = []
HydroDyn['MemberCd1']        = []
HydroDyn['MemberCd2']        = []
HydroDyn['MemberCdMG1']      = []
HydroDyn['MemberCdMG2']      = []
HydroDyn['MemberCa1']        = []
HydroDyn['MemberCa2']        = []
HydroDyn['MemberCaMG1']      = []
HydroDyn['MemberCaMG2']      = []
HydroDyn['MemberCp1']        = []
HydroDyn['MemberCp2']        = []
HydroDyn['MemberCpMG1']      = []
HydroDyn['MemberCpMG2']      = []
HydroDyn['MemberAxCa1']      = []
HydroDyn['MemberAxCa2']      = []
HydroDyn['MemberAxCaMG1']    = []
HydroDyn['MemberAxCaMG2']    = []
HydroDyn['MemberAxCp1']      = []
HydroDyn['MemberAxCp2']      = []
HydroDyn['MemberAxCpMG1']    = []
HydroDyn['MemberAxCpMG2']    = []

# MEMBERS
HydroDyn['NMembers']         = 0
HydroDyn['MemberID']         = []
HydroDyn['MJointID1']        = []
HydroDyn['MJointID2']        = []
HydroDyn['MPropSetID1']      = []
HydroDyn['MPropSetID2']      = []
HydroDyn['MDivSize']         = []
HydroDyn['MCoefMod']         = []
HydroDyn['PropPot']          = []

# FILLED MEMBERS
HydroDyn['NFillGroups']      = 0
HydroDyn['FillNumM']         = []
HydroDyn['FillMList']        = []
HydroDyn['FillFSLoc']        = []
HydroDyn['FillDens']         = []

# MARINE GROWTH
HydroDyn['NMGDepths']        = 0
HydroDyn['MGDpth']           = []
HydroDyn['MGThck']           = []
HydroDyn['MGDens']           = []

# MEMBER OUTPUT LIST
HydroDyn['NMOutputs']        = 0
HydroDyn['MemberID_out']     = []
HydroDyn['NOutLoc']          = []
HydroDyn['NodeLocs']         = []

# JOINT OUTPUT LIST
HydroDyn['NJOutputs']        = 0
HydroDyn['JOutLst']          = 0

# OUTPUT
HydroDyn['HDSum']            = True
HydroDyn['OutAll']           = False
HydroDyn['OutSwtch']         = 2
HydroDyn['OutFmt']           = ""
HydroDyn['OutSFmt']          = ""

## MAP++ Input File
# LINE DICTIONARY
MAP = {}
MAP['LineType']              = ""
MAP['Diam']                  = 0.
MAP['MassDenInAir']          = 0.
MAP['EA']                    = 0.
MAP['CB']                    = 0.
MAP['CIntDamp']              = 0.
MAP['Ca']                    = 0.
MAP['Cdn']                   = 0.
MAP['Cdt']                   = 0.

# NODE PROPERTIES
MAP['Node']                  = []
MAP['Type']                  = []
MAP['X']                     = []
MAP['Y']                     = []
MAP['Z']                     = []
MAP['M']                     = []
MAP['B']                     = []
MAP['FX']                    = []
MAP['FY']                    = []
MAP['FZ']                    = []

# LINE PROPERTIES
MAP['Line']                  = 0
MAP['LineType']              = ""
MAP['UnstrLen']              = 0.
MAP['NodeAnch']              = 0
MAP['NodeFair']              = 0
MAP['Flags']                 = []

# SOLVER OPTIONS
MAP['Option']                = []

#######################
Fst7 = {}
Fst7['Echo']        = False
Fst7['ADAMSPrep']   = 0
Fst7['AnalMode']    = 0
Fst7['NumBl']       = 0
Fst7['TMax']        = 0.
Fst7['DT']          = 0.
Fst7['YCMode']      = 0
Fst7['TYCOn']       = 0.
Fst7['PCMode']      = 0
Fst7['TPCOn']       = 0.
Fst7['VSContrl']    = 0
Fst7['VS_RtGnSp']   = 0.
Fst7['VS_RtTq']     = 0.
Fst7['VS_Rgn2K']    = 0.
Fst7['VS_SlPc']     = 0.
Fst7['GenModel']    = 0
Fst7['GenTiStr']    = False
Fst7['GenTiStp']    = False
Fst7['SpdGenOn']    = 0.
Fst7['TimGenOn']    = 0.
Fst7['TimGenOf']    = 0.
Fst7['HSSBrMode']   = 0
Fst7['THSSBrDp']    = 0.
Fst7['TiDynBrk']    = 0.
Fst7['TTpBrDp1']    = 0.
Fst7['TTpBrDp2']    = 0.
Fst7['TTpBrDp3']    = 0.
Fst7['TBDepISp1']   = 0.
Fst7['TBDepISp2']   = 0.
Fst7['TBDepISp3']   = 0.
Fst7['TYawManS']    = 0.
Fst7['TYawManE']    = 0.
Fst7['NacYawF']     = 0.
Fst7['TPitManS1']   = 0.
Fst7['TPitManS2']   = 0.
Fst7['TPitManS3']   = 0.
Fst7['TPitManE1']   = 0.
Fst7['TPitManE2']   = 0.
Fst7['TPitManE3']   = 0.
Fst7['BlPitch1']    = 0.
Fst7['BlPitch2']    = 0.
Fst7['BlPitch3']    = 0.
Fst7['BlPitchF1']   = 0.
Fst7['BlPitchF2']   = 0.
Fst7['BlPitchF3']   = 0.
Fst7['Gravity']     = 0.
Fst7['FlapDOF1']    = False
Fst7['FlapDOF2']    = False
Fst7['EdgeDOF']     = False
Fst7['TeetDOF']     = False
Fst7['DrTrDOF']     = False
Fst7['GenDOF']      = False
Fst7['YawDOF']      = False
Fst7['TwFADOF1']    = False
Fst7['TwFADOF2']    = False
Fst7['TwSSDOF1']    = False
Fst7['TwSSDOF2']    = False
Fst7['CompAero']    = False
Fst7['CompNoise']   = False
Fst7['OoPDefl']     = 0.
Fst7['IPDefl']      = 0.
Fst7['TeetDefl']    = 0.
Fst7['Azimuth']     = 0.
Fst7['RotSpeed']    = 0.
Fst7['NacYaw']      = 0.
Fst7['TTDspFA']     = 0.
Fst7['TTDspSS']     = 0.
Fst7['TipRad']      = 0.
Fst7['HubRad']      = 0.
Fst7['PSpnElN']     = 0.
Fst7['UndSling']    = 0
Fst7['HubCM']       = 0.
Fst7['OverHang']    = 0.
Fst7['NacCMxn']     = 0.
Fst7['NacCMy']      = 0.
Fst7['NacCMz']      = 0.
Fst7['TowerH']      = 0.
Fst7['Twr2Shft']    = 0.
Fst7['TwrRBHt']     = 0.
Fst7['ShftTilt']    = 0.
Fst7['Delta3']      = 0.
Fst7['PreCone1']    = 0.
Fst7['PreCone2']    = 0.
Fst7['PreCone3']    = 0.
Fst7['AzimB1Up']    = 0.
Fst7['YawBrMass']   = 0.
Fst7['NacMas']      = 0.
Fst7['HubMas']      = 0.
Fst7['TipMass1']    = 0.
Fst7['TipMass2']    = 0.
Fst7['TipMass3']    = 0.
Fst7['NacYIner']    = 0.
Fst7['GenIner']     = 0.
Fst7['HubIner']     = 0.
Fst7['GBoxEff']     = 0.
Fst7['GenEff']      = 0.
Fst7['GBRatio']     = 0.
Fst7['GBRevers']    = False
Fst7['HSSBrTqF']    = 0.
Fst7['HSSBrDT']     = 0.
Fst7['DynBrkFi']    = ""
Fst7['DTTorSpr']    = 0.
Fst7['DTTorDmp']    = 0.
Fst7['SIG_SlPc']    = 0.
Fst7['SIG_SySp']    = 0.
Fst7['SIG_RtTq']    = 0.
Fst7['SIG_PORt']    = 0.
Fst7['TEC_Freq']    = 0.
Fst7['TEC_NPol']    = 0
Fst7['TEC_SRes']    = 0.
Fst7['TEC_RRes']    = 0.
Fst7['TEC_VLL']     = 0.
Fst7['TEC_SLR']     = 0.
Fst7['TEC_RLR']     = 0.
Fst7['TEC_MR']      = 0.
Fst7['PtfmModel']   = 0
Fst7['PtfmFile']    = ""
Fst7['TwrNodes']    = 0
Fst7['TwrFile']     = ""
Fst7['YawSpr']      = 0.0
Fst7['YawDamp']     = 0.0
Fst7['YawNeut']     = 0.0
Fst7['Furling']     = False
Fst7['FurlFile']    = ""
Fst7['TeetMod']     = 0
Fst7['TeetDmpP']    = 0.
Fst7['TeetDmp']     = 0.
Fst7['TeetCDmp']    = 0.
Fst7['TeetSStP']    = 0.
Fst7['TeetHStP']    = 0.
Fst7['TeetSSSp']    = 0.
Fst7['TeetHSSp']    = 0.
Fst7['TBDrConN']    = 0.
Fst7['TBDrConD']    = 0.
Fst7['TpBrDT']      = 0.
Fst7['BldFile1']    = ""
Fst7['BldFile2']    = ""
Fst7['BldFile3']    = ""
Fst7['ADFile']      = ""
Fst7['NoiseFile']   = ""
Fst7['ADAMSFile']   = ""
Fst7['LinFile']     = ""
Fst7['SumPrint']    = False
Fst7['OutFileFmt']  = 1
Fst7['TabDelim']    = False
Fst7['OutFmt']      = ""
Fst7['TStart']      = 0.0
Fst7['DecFact']     = 0.
Fst7['SttsTime']    = 0.
Fst7['NcIMUxn']     = 0.
Fst7['NcIMUyn']     = 0.
Fst7['NcIMUzn']     = 0.
Fst7['ShftGagL']    = 0.
Fst7['NTwGages']    = 0
Fst7['TwrGagNd']    = 0
Fst7['NBlGages']    = 0
Fst7['BldGagNd']    = 0

# ====== INITIALIZE FAST MODEL BY INITIALIZING ALL VARIABLE TREES ======

FstModel = {}

# Description
FstModel['description']       = ''

FstModel['Fst']               = Fst
FstModel['ElastoDyn']         = ElastoDyn
FstModel['ElastoDynBlade']    = ElastoDynBlade
FstModel['ElastoDynTower']    = ElastoDynTower
FstModel['InflowWind']        = InflowWind
FstModel['AeroDyn14']         = AeroDyn14
FstModel['AeroDyn15']         = AeroDyn15
FstModel['AeroDynBlade']      = AeroDynBlade
FstModel['AeroDynTower']      = AeroDynTower
FstModel['AeroDynPolar']      = AeroDynPolar
FstModel['ServoDyn']          = ServoDyn
FstModel['HydroDyn']          = HydroDyn
FstModel['MAP']               = MAP
FstModel['Fst7']              = Fst7

# List of Outputs (all input files -- FST, ED, SD)
# TODO: Update FstOutput for a few new outputs in FAST8
FstModel['outlist']           = FstOutput   #
FstModel['outlist7']          = Fst7Output