import torch
import torch.nn as nn
import torch.nn.functional as F

class Encoder(nn.Module):

    def __init__(self, in_channels, out_channels, base_channels=64, n_layers=4):
        super().__init__()

        layers = [
            nn.ReflectionPad2d(3),
            nn.Conv2d(in_channels, base_channels, kernel_size=7, padding=0),
            nn.InstanceNorm2d(base_channels),
            nn.ReLU(inplace=True),
        ]

        channels = base_channels

        # downsampling
        for _ in range(n_layers):
            layers += [
                nn.Conv2d(channels, 2 * channels, kernel_size=3, stride=2, padding=1),
                nn.InstanceNorm2d(2 * channels),
                nn.ReLU(inplace=True),
            ]
            channels *= 2

        # upsampling
        for _ in range(n_layers):
            layers += [
                nn.ConvTranspose2d(channels, channels // 2, kernel_size=3, stride=2, padding=1, output_padding=1),
                nn.InstanceNorm2d(channels // 2),
                nn.ReLU(inplace=True),
            ]
            channels //= 2

        layers += [
            nn.ReflectionPad2d(3),
            nn.Conv2d(base_channels, out_channels, kernel_size=7, padding=0),
            nn.Tanh(),
        ]

        self.out_channels = out_channels
        self.layers = nn.Sequential(*layers)

    def instancewise_average_pooling(self, x, inst):

        x_mean = torch.zeros_like(x)
        classes = torch.unique(inst, return_inverse=False, return_counts=False)

        for i in classes:
            for b in range(x.size(0)):
                indexes = torch.nonzero(inst[b:b+1] == 1, as_tuple=False)
                for j in range(self.out_channels):
                    x_ins = x[indexes[:, 0] + b, indexes[:, 1] + j, indexes[:, 2], indexes[:, 3]]
                    mean_feat = torch.mean(x_ins).expand_as(x_ins)
                    x_mean[indexes[:, 0] + b, indexes[:, 1] + j, indexes[:, 2], indexes[:, 3]] = mean_feat

        return x_mean

    def forward(self, x, inst):
        x = self.layers(x)
        x = self.instancewise_average_pooling(x, inst)
        return x


# 2D 히스토그램

import cv2
import matplotlib.pylab as plt

# 컬러 스타일을 1.x 스타일로 사용
plt.style.use("classic")
img = cv2.imread("./img/mountain.jpg")

# 파랑과 초록에 대한 2차원 히스토그램
plt.subplot(131)
hist = cv2.calcHist([img], [0,1], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title("Blue and Green")
plt.colorbar(p)

# 초록과 빨강에 대한 2차원 히스토그램
plt.subplot(132)
hist = cv2.calcHist([img], [1,2], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title("Green and Red")
plt.colorbar(p)

# 파랑과 빨강에 대한 2차원 히스토그램
plt.subplot(133)
hist = cv2.calcHist([img], [0,2], None, [32,32], [0,256,0,256])
p = plt.imshow(hist)
plt.title("Blue and Red")
plt.colorbar(p)

plt.show()


import sys
import os
import argparse
import configparser
import random
import time
import calendar
import re
import py3buddy
import pydbus
import gi

def processmsg(account, sender, message, conversation, flags):
    # a demo version to show some of the capabilities of
    # the iBuddy

    # first reset the iBuddy
    ibuddy.reset()

    # a list of smileys as sent by Google Hangout
    smileys = [':D', ':-D', '^_^', ':-)', ':)', '☺']

    # a command to execute when a smiley is received
    smilecommand = 'RED:HEART:WINGSHIGH:GO:SHORTSLEEP:YELLOW:NOHEART:WINGSLOW:GO:SHORTSLEEP:HEART:BLUE:WINGSHIGH:GO:SHORTSLEEP:PURPLE:NOHEART:WINGSLOW:GO:SHORTSLEEP:HEART:CYAN:WINGSHIGH:GO:SHORTSLEEP:WHITE:NOHEART:WINGSLOW:GO:SHORTSLEEP:RESET'
    for s in smileys:
        if s in message:
            ibuddy.executecommand(smilecommand)
            break

    ibuddy.reset()

def main(argv):
    parser = argparse.ArgumentParser()

    # options for the commandline
    parser.add_argument("-c", "--config", action="store", dest="cfg",
                        help="path to configuration file", metavar="FILE")
    args = parser.parse_args()

    # first some sanity checks for the configuration file
    if args.cfg is None:
        parser.error("Configuration file missing")

    if not os.path.exists(args.cfg):
        parser.error("Configuration file does not exist")

    # then parse the configuration file
    config = configparser.ConfigParser()

    configfile = open(args.cfg, 'r')

    try:
        config.readfp(configfile)
    except Exception:
        print("Cannot read configuration file", file=sys.stderr)
        sys.exit(1)

    buddy_config = {}
    for section in config.sections():
        if section == 'ibuddy':
            try:
                productid = int(config.get(section, 'productid'))
                buddy_config['productid'] = productid
            except:
                pass

            buddy_config['reset_position'] = False
            try:
                reset_position_val = config.get(section, 'reset_position')
                if reset_position_val == 'yes':
                    buddy_config['reset_position'] = True
            except:
                pass
        if section == 'twitter':
            pass

    # This is very ugly, but the only way (I know) to expose the iBuddy to the
    # method processing the message from Pidgin
    global ibuddy

    # initialize an iBuddy and check if a device was found and is accessible
    ibuddy = py3buddy.iBuddy(buddy_config)
    if ibuddy.dev is None:
        print("No iBuddy found, or iBuddy not accessible", file=sys.stderr)
        sys.exit(1)

    # connect to session DBus
    bus = pydbus.SessionBus()

    # register a callback for messages that are received
    purple = bus.get("im.pidgin.purple.PurpleService",
                     "/im/pidgin/purple/PurpleObject")
    purple.ReceivedImMsg.connect(processmsg)

    gi.repository.GObject.MainLoop().run()

    # finally reset the i-buddy again
    ibuddy.reset()

if __name__ == "__main__":
    main(sys.argv)


'''
Training and testing data structure
```
data/
    train/
        cat1/
            cat1001.jpg
            cat1002.jpg
            ...
        cat2/
            cat2001.jpg
            cat2002.jpg
            ...
    validation/
        cat1/
            cat1001.jpg
            cat1002.jpg
            ...
        cat2/
            cat2001.jpg
            cat2002.jpg
            ...
```
'''
import tensorflow as tf
import keras.backend as K
import keras
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential, Model, load_model
from keras.layers import Dropout, Flatten, Dense
import time
import build_CNN
import action_generator

img_width, img_height = 224, 224

train_data_dir = 'gs://resnet_bucket/tiny_image_200/train'
validation_data_dir = 'gs://resnet_bucket/tiny_image_200/val'
nb_train_samples = 100000
nb_validation_samples = 10000
epochs = 50
batch_size = 64

# tensorflow.python.lib.io.file_io.FileIO(train_file, mode='r')

def tf_config_allow_growth_gpu():
    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    sess = tf.Session(config=config)
    K.set_session(sess)

def pop_layer(model):
    if not model.outputs:
        raise Exception('Sequential model cannot be popped: model is empty.')

    model.layers.pop()
    if not model.layers:
        model.outputs = []
        model.inbound_nodes = []
        model.outbound_nodes = []
    else:
        model.layers[-1].outbound_nodes = []
        model.outputs = [model.layers[-1].output]
    model.built = False

def init_compile_model(state, laststate):
    # build the ResNet50 network
    initial_model = applications.ResNet50(include_top=True)
    # state = [1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0]
    initial_model = build_CNN.update_model(state, 'imagenet')
    pop_layer(initial_model)
    last = initial_model.layers[-1].output
    preds = Dense(200, activation='softmax', name="fc2")(last)
    model = Model(initial_model.input, preds)
    # model = load_model('current_model.h5')
    model.load_weights('current_model.h5')
    print('Model loaded.')
    model.summary()
    # pop_layer(model)
    # set the first x layers (up to the last conv block)
    # to non-trainable (weights will not be updated)
    trainable_index = action_generator.find_last_changed(state, laststate)
    for layer in model.layers[:25]:
        name = layer.get_config()['name']
        if 'blk' not in name:
            layer.trainable = False
        elif int(name.split('blk')[1]) < trainable_index:
            layer.trainable = False

    # compile the model with a SGD/momentum optimizer
    # and a very slow learning rate.
    model.compile(loss='categorical_crossentropy',
                  optimizer=optimizers.SGD(lr=1e-3, momentum=0.9),
                  metrics=['accuracy'])
    print('Model compiled.')
    return model

def init_generators():
    # prepare data augmentation configuration
    train_datagen = ImageDataGenerator(
        rescale=1. / 255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

    test_datagen = ImageDataGenerator(rescale=1. / 255)

    train_generator = train_datagen.flow_from_directory(
        train_data_dir,
        target_size=(img_height, img_width),
        batch_size=batch_size)

    validation_generator = test_datagen.flow_from_directory(
        validation_data_dir,
        target_size=(img_height, img_width),
        batch_size=batch_size)
    print('Data generators created.')
    return train_generator, validation_generator

class PrintRuntime(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.run_time = []
        self.begin = 0
    def on_batch_begin(self, batch, logs={}):
        self.begin = time.time()
    def on_batch_end(self, batch, logs={}):
        end = time.time()
        runtime = end-self.begin
        self.run_time.append(runtime)
        if len(self.run_time)%100:
            # print("Run-time on last batch:", runtime, "seconds")
            print("Avg Run-time on last batch:", sum(self.run_time)/len(self.run_time), "seconds")

# fine-tune the model

def test():  # tf_config_allow_growth_gpu()
    laststate = [1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0]
    state = [1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0]
    print("step1")
    model = init_compile_model(state, laststate)
    print("step2")
    train_generator, validation_generator = init_generators()
    print("step3")
    runtime_callback = PrintRuntime()
    print("step4")
    model.fit_generator(
        train_generator,
        steps_per_epoch=10000 / 64,
        epochs=30,
        validation_data=validation_generator,
        validation_steps=1000 / 64, verbose=2)

    model.save('my_model2.h5')


from PIL import Image
import numpy as np
import math
import glob
import pickle as pk

WIDTH = 19
HEIGHT = 19
file_to_integral = {}
'''Region of an image with (x, y) as top left corner
'''

class Region:

    def __init__(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    # Returns sum of pixel within this region based on the given integral image
    def get_sum(self, integral_image):
        return integral_image[self.x + self.w][self.y + self.h] + \
               integral_image[self.x][self.y] \
               - integral_image[self.x + self.w][self.y] - \
               integral_image[self.x][self.y + self.h]

''' A WeakLearner represents a weak classifier
'''

class WeakLearner:

    def __init__(self, feature):
        self.feature = feature              # feature
        self.thresh = 0.0                     # threshold
        self.p = 0.0                          # polarity
        self.alpha = 0.0                      # alpha
        self.e = 0.0                          # error

    # Classifies the data and returns the predicted results
    # If positive, return 1 and 0 otherwise
    #   x - data
    def classify(self, x):
        im = Image.open(x)
        np_im = np.asarray(im)
        integral_image = make_integral(np_im)
        feature_value = self.feature.apply_feature(integral_image)
        predicted_y = 0
        if self.p == 1:
            if feature_value > self.thresh:
                predicted_y = 1
        else:
            if feature_value < self.thresh:
                predicted_y = 1
        return predicted_y

''' A Feature represents a Haar-like feature
    It contains rectangles that adds to the feature value
    and rectangles that are negative and lower the feature value
'''

class Feature:

    def __init__(self, pos_rect, neg_rect):
        self.pos_rect = pos_rect                # list of rectangles that are added to sum
        self.neg_rect = neg_rect                # list of rectangles that are subtracted from sum
        self.feature_values = {}                # map of feature values

    # Applies the feature to the given integral image
    # Returns the feature value
    def apply_feature(self, integral_image):
        feature_sum = 0.0
        for region in self.pos_rect:
            feature_sum += region.get_sum(integral_image)
        for region in self.neg_rect:
            feature_sum -= region.get_sum(integral_image)
        return feature_sum

# Loads file with given name
def load_model(model):
    with open(model, "rb") as f:
        return pk.load(f)

def populate_integral_image():
    for currentFile in glob.glob("./faces/train/face/*.pgm"):
        im = Image.open(currentFile)
        np_im = np.asarray(im)
        integral_image = make_integral(np_im)
        file_to_integral[currentFile] = integral_image
    for currentFile in glob.glob("./faces/train/non-face/*.pgm"):
        im = Image.open(currentFile)
        np_im = np.asarray(im)
        integral_image = make_integral(np_im)
        file_to_integral[currentFile] = integral_image

# Makes the feature matrix where each row is one feature and each column is a feature value
# for a sample data
# Returns a tuple (feature matrix, sample y)
#   features - list of features
#   data - sample data
def make_feature_m(features, data):

    # Create matrix where each row is a feature and each column is the feature value
    feature_values = np.zeros((len(features), len(data)))

    # Array that saves expected values for each sample data
    sample_y = []
    for i in range(len(data)):
        sample_y.append(data[i][1])

    i = 0
    for feature in features:
        j = 0
        for (x, y) in data:
            feature_values[i][j] = feature.apply_feature(file_to_integral[x])
            j += 1
        i += 1
    return feature_values, sample_y

# Normalizes the given list of sample weights
#   sample_w - list of sample weights
def normalize(sample_w):
    tot = sum(sample_w)
    for i in range(len(sample_w)):
        sample_w[i] = sample_w[i] / tot

# Calculates and returns a tuple of (detection rate, false positive rate) on the given
# data set
#   pos_data - data that should return 1
#   neg_data - data that should return 0
#   detector - detector to test data on
def validate(pos_data, neg_data, detector):
    detect_count = 0.0
    for (x, y) in pos_data:
        res = detector.classify(x)
        if res == 1:
            detect_count += 1

    error_count = 0.0
    for (x, y) in neg_data:
        res = detector.classify(x)
        if res == 1:
            error_count += 1

    return detect_count / len(pos_data), error_count / len(neg_data)

# Initializes the sorted feature to feature values mapping for each feature
#   feature - list of features
#   m - matrix where number of rows is number of features and number of columns
#       is number of sample data
def get_feature_values(features, m):
    for i in range(m.shape[0]):
        for j in range(m.shape[1]):
            temp = m[i][j]
            features[i].feature_values[j] = temp
        sorted(features[i].feature_values.items(), key=lambda kv: kv[1])

# Looks for optimal threshold and polarity
# Optimal is the minimum sum of the errors of the sample weights
#   feature - the feature to be optimized
#   sample_w - the sample weights after applying the
#               feature
#   sample_y - list of sample y-values
def make_model(feature, sample_w, sample_y):
    ret = WeakLearner(feature)
    tot_pos = 0.0
    tot_neg = 0.0
    for i in range(len(sample_w)):
        if sample_y[i] == 1:
            tot_pos += sample_w[i]
        else:
            tot_neg += sample_w[i]

    print("Total Positive: " + str(tot_pos))
    print("Total negative: " + str(tot_neg))
    min_error = tot_pos + tot_neg
    min_sample = None
    curr_pos = 0.0
    curr_neg = 0.0
    p = 0
    for i in feature.feature_values:
        if sample_y[i] == 1:
            curr_pos += sample_w[i]
        else:
            curr_neg += sample_w[i]
            err_1 = curr_neg + (tot_pos - curr_pos)
            err_2 = curr_pos + (tot_neg - curr_neg)
            error = min(err_1, err_2)
            if error < min_error:
                min_error = error
                min_sample = i
                if error == err_1:
                    p = 1
                else:
                    p = 0
    ret.thresh = feature.feature_values[min_sample]
    ret.p = p
    print("Min error value: " + str(min_error))
    beta = min_error / (1 - min_error)
    if beta == 0:
        beta = math.pow(1, -10)
    ret.alpha = math.log2(1.0 / beta)
    ret.e = min_error
    return ret

# Adjusts the sample weights
#   feature - the feature that has been applied
#   sample_w - the sample weights
#   data - images
def adjust_weights(model, sample_w, data):
    for i in range(len(sample_w)):
        predicted_y = model.classify(data[i][0])
        sample_res = 1
        if predicted_y == data[i][1]:
            sample_res = 0
        sample_w[i] = sample_w[i] * math.pow((model.e / (1.0 - model.e)), 1.0 - sample_res)

# Initializes and returns a map of sample weight pairs
#   data - tuples of form (x, y) where x is image and y is
#           correct label
def initialize_weights(data):
    tot_pos = 0
    tot_neg = 0
    for (x, y) in data:
        if y == 1:
            tot_pos += 1
        else:
            tot_neg += 1

    w = []
    for (x, y) in data:
        if y == 1:
            w.append(1.0 / (2 * tot_pos))
        else:
            w.append(1.0 / (2 * tot_neg))
    return w

# Makes and returns a list of features
def make_features():
    features = []
    for w in range(1, WIDTH + 1):
        for h in range(1, HEIGHT + 1):
            x = 0
            while x + w < WIDTH:
                y = 0
                while y + h < HEIGHT:
                    rect = Region(x, y, w, h)
                    right_rect = Region(x + w, y, w, h)
                    bottom_rect = Region(x, y + h, w, h)
                    right_edge_rect = Region(x + (2 * w), y, w, h)
                    bottom_right = Region(x + w, y + h, w, h)

                    if x + (2 * w) < WIDTH:
                        # features.append(Feature([rect], [right_rect]))
                        features.append(Feature([right_rect], [rect]))

                    if y + (2 * h) < HEIGHT:
                        features.append(Feature([rect], [bottom_rect]))
                        # features.append(Feature([bottom_rect], [rect]))

                    if x + (3 * w) < WIDTH:
                        features.append(Feature([right_rect], [rect, right_edge_rect]))
                        # features.append(Feature([rect, right_edge_rect], [right_rect]))

                    if x + (2 * w) < WIDTH and y + (2 * h) < HEIGHT:
                        features.append(Feature([rect, bottom_right], [right_rect, bottom_rect]))
                        # features.append(Feature([right_rect, bottom_rect], [rect, bottom_right]))
                    y += 1
                x += 1
    return features

# Makes and return an integral image for the given image
#   im_data - image in the form of a matrix
def make_integral(im_data):
    integral_im = np.zeros(im_data.shape)

    for row in range(im_data.shape[0]):
        for col in range(im_data.shape[1]):
            curr_sum = im_data[row][col]
            if row != 0:
                curr_sum += integral_im[row - 1][col]
            if col != 0:
                curr_sum += integral_im[row][col - 1]
            if row != 0 and col != 0:
                curr_sum -= integral_im[row - 1][col - 1]
            integral_im[row][col] = curr_sum
    return integral_im


# from map2loop import m2l_topology
import networkx as nx
import random
import numpy as np
import pandas as pd
import os

##########################################################################
# Save out and compile taskfile needed to generate geomodeller model using the geomodellerbatch engine
#
# loop2geomodeller(test_data_path,tmp_path,output_path,save_faults,compute_etc)
# Args:
# test_data_path root directory of test data
# tmp_path directory of temporary outputs
# output_path directory of outputs
# ave_faults flag for saving faults or not
# compute_etc flag for actual calculations or just project output
#
# Creates geomodeller taskfile files from varous map2loop outputs
##########################################################################
def loop2geomodeller(model_name,test_data_path,tmp_path,output_path,dtm_file,bbox,model_top,model_base,save_faults,compute_etc,workflow):

    f=open(test_data_path+'/'+model_name+'/m2l.taskfile','w')
    f.write('#---------------------------------------------------------------\n')
    f.write('#-----------------------Project Header-----------------------\n')
    f.write('#---------------------------------------------------------------\n')
    f.write('name: "UWA_Intrepid"\n')
    f.write('description: "Automate_batch_Model"\n')
    f.write('    GeomodellerTask {\n')
    f.write('    CreateProject {\n')
    f.write('        name: "Hamersley"\n')
    f.write('        author: "Mark"\n')
    f.write('        date: "23/10/2019  0: 0: 0"\n')
    f.write('        projection { map_projection: "GDA94 / MGA50"}\n')
    f.write('        version: "2.0"\n')
    f.write('        units: meters\n')
    f.write('        precision: 1.0\n')
    f.write('        Extents {\n')
    f.write('            xmin: '+str(bbox[0])+'\n')
    f.write('            ymin: '+str(bbox[1])+'\n')
    f.write('            zmin: '+str(model_base)+'\n')
    f.write('            xmax: '+str(bbox[2])+'\n')
    f.write('            ymax: '+str(bbox[3])+'\n')
    f.write('            zmax: '+str(model_top)+'\n')
    f.write('        }\n')
    f.write('        deflection2d: 0.001\n')
    f.write('        deflection3d: 0.001\n')
    f.write('        discretisation: 10.0\n')
    f.write('        referenceTop: false\n')
    f.write('        CustomDTM {\n')
    f.write('            Extents {\n')
    f.write('            xmin: '+str(bbox[0])+'\n')
    f.write('            ymin: '+str(bbox[1])+'\n')
    f.write('            xmax: '+str(bbox[2])+'\n')
    f.write('            ymax: '+str(bbox[3])+'\n')
    f.write('            }\n')
    f.write('            name: "Topography"\n')
    f.write('            filename {\n')
    f.write('                Grid_Name: "'+dtm_file+'"\n')
    f.write('            }\n')
    f.write('            nx: 10\n')
    f.write('            ny: 10\n')
    f.write('        }\n')
    f.write('    }\n')
    f.write('}\n')

    orientations=pd.read_csv(output_path+'orientations_clean.csv',',')
    contacts=pd.read_csv(output_path+'contacts_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')

    empty_fm=[]

    for indx,afm in all_sorts.iterrows():
        foundcontact=False
        for indx2,acontact in contacts.iterrows():
            if(acontact['formation'] in afm['code']):
                foundcontact=True
                break
        foundorientation=False
        for indx3,ano in orientations.iterrows():
            if(ano['formation'] in afm['code']):
                foundorientation=True
                break
        if(not foundcontact or not foundorientation):
            empty_fm.append(afm['code'])

    #print(empty_fm)

    all_sorts=np.genfromtxt(tmp_path+'all_sorts_clean.csv',delimiter=',',dtype='U100')
    nformations=len(all_sorts)

    f.write('#---------------------------------------------------------------\n')
    f.write('#-----------------------Create Formations-----------------------\n')
    f.write('#---------------------------------------------------------------\n')

    for i in range (1,nformations):
        if( not all_sorts[i,4] in empty_fm):
            f.write('GeomodellerTask {\n')
            f.write('CreateFormation {\n')

            ostr='    name: "'+all_sorts[i,4].replace("\n","")+'"\n'
            f.write(ostr)

            ostr='    red: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            ostr='    green: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            ostr='    blue: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            f.write('    }\n')
            f.write('}\n')

    f.write('#---------------------------------------------------------------\n')
    f.write('#-----------------------Set Stratigraphic Pile------------------\n')
    f.write('#---------------------------------------------------------------\n')

    for i in range (1,nformations):
    #for i in range (nformations-1,0,-1):
        if(all_sorts[i,2]==str(1)):
            f.write('GeomodellerTask {\n')
            f.write('SetSeries {\n')

            ostr='    name: "'+all_sorts[i][5].replace("\n","")+'"\n'
            f.write(ostr)

            ostr='    position: 1\n'
            f.write(ostr)

            ostr='    relation: "erode"\n'
            f.write(ostr)

            f.write('    }\n')
            f.write('}\n')

            for j in range(nformations-1,0,-1):
    #        for j in range(1,nformations):
                if(all_sorts[j,1]==all_sorts[i,1]):
                    if( not all_sorts[j][4] in empty_fm):
                        f.write('GeomodellerTask {\n')
                        f.write('AddFormationToSeries {\n')

                        ostr='    series: "'+all_sorts[j][5]+'"\n'
                        f.write(ostr)

                        ostr='    formation: "'+all_sorts[j][4]+'"\n'
                        f.write(ostr)

                        f.write('    }\n')
                        f.write('}\n')

    if(save_faults):
        output_path=test_data_path+'output/'

        faults_len=pd.read_csv(output_path+'fault_dimensions.csv')

        n_allfaults=len(faults_len)

        fcount=0
        for i in range(0,n_allfaults):
            f.write('GeomodellerTask {\n')
            f.write('CreateFault {\n')
            ostr='    name: "'+faults_len.iloc[i]["Fault"]+'"\n'
            f.write(ostr)

            ostr='    red: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            ostr='    green: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            ostr='    blue: '+str(random.randint(1,256)-1)+'\n'
            f.write(ostr)

            f.write('    }\n')
            f.write('}\n')
            fcount=fcount+1

            f.write('GeomodellerTask {\n')
            f.write('    Set3dFaultLimits {\n')
            f.write('        Fault_name: "'+faults_len.iloc[i]["Fault"]+ '"\n')
            f.write('        Horizontal: '+str(faults_len.iloc[i]["HorizontalRadius"])+ '\n')
            f.write('        Vertical: '+str(faults_len.iloc[i]["VerticalRadius"])+ '\n')
            f.write('        InfluenceDistance: '+str(faults_len.iloc[i]["InfluenceDistance"])+ '\n')
            f.write('    }\n')
            f.write('}\n')

    f.write('#---------------------------------------------------------------\n')
    f.write('#-----------------------Import 3D contact data ---Base Model----\n')
    f.write('#---------------------------------------------------------------\n')

    contacts=pd.read_csv(output_path+'contacts_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')
    #all_sorts.set_index('code',  inplace = True)
    #display(all_sorts)

    for inx,afm in all_sorts.iterrows():
        #print(afm[0])
        if( not afm['code'] in empty_fm):
            f.write('GeomodellerTask {\n')
            f.write('    Add3DInterfacesToFormation {\n')
            f.write('          formation: "'+str(afm['code'])+'"\n')

            for indx2,acontact in contacts.iterrows():
                if(acontact['formation'] in afm['code'] ):
                    ostr='              point {x:'+str(acontact['X'])+'; y:'+str(acontact['Y'])+'; z:'+str(acontact['Z'])+'}\n'
                    f.write(ostr)
            f.write('    }\n')
            f.write('}\n')
    f.write('#---------------------------------------------------------------\n')
    f.write('#------------------Import 3D orientation data ---Base Model-----\n')
    f.write('#---------------------------------------------------------------\n')

    orientations=pd.read_csv(output_path+'orientations_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')
    #all_sorts.set_index('code',  inplace = True)
    #display(all_sorts)

    for inx,afm in all_sorts.iterrows():
        #print(groups[agp])
        if( not afm['code'] in empty_fm):
            f.write('GeomodellerTask {\n')
            f.write('    Add3DFoliationToFormation {\n')
            f.write('          formation: "'+str(afm['code'])+'"\n')
            for indx2,ano in orientations.iterrows():
                if(ano['formation'] in afm['code']):
                    f.write('           foliation {\n')
                    ostr='                  Point3D {x:'+str(ano['X'])+'; y:'+str(ano['Y'])+'; z:'+str(ano['Z'])+'}\n'
                    f.write(ostr)
                    ostr='                  direction: '+str(ano['azimuth'])+'\n'
                    f.write(ostr)
                    ostr='                  dip: '+str(ano['dip'])+'\n'
                    f.write(ostr)
                    if(ano['polarity']==1):
                        ostr='                  polarity: Normal_Polarity\n'
                    else:
                        ostr='                  polarity: Reverse_Polarity\n'
                    f.write(ostr)
                    ostr='           }\n'
                    f.write(ostr)
            f.write('    }\n')
            f.write('}\n')

    f.write('#---------------------------------------------------------------\n')
    f.write('#-----------------------Import 3D fault data ---Base Model------\n')
    f.write('#---------------------------------------------------------------\n')

    contacts=pd.read_csv(output_path+'faults.csv',',')
    faults=pd.read_csv(output_path+'fault_dimensions.csv',',')

    for indx,afault in faults.iterrows():
        f.write('GeomodellerTask {\n')
        f.write('    Add3DInterfacesToFormation {\n')
        f.write('          formation: "'+str(afault['Fault'])+'"\n')
        for indx2,acontact in contacts.iterrows():
            if(acontact['formation'] == afault['Fault']):
                ostr='              point {x:'+str(acontact['X'])+'; y:'+str(acontact['Y'])+'; z:'+str(acontact['Z'])+'}\n'
                f.write(ostr)
        f.write('    }\n')
        f.write('}\n')

    f.write('#---------------------------------------------------------------\n')
    f.write('#------------------Import 3D fault orientation data ------------\n')
    f.write('#---------------------------------------------------------------\n')

    orientations=pd.read_csv(output_path+'fault_orientations.csv',',')
    faults=pd.read_csv(output_path+'fault_dimensions.csv',',')

    for indx,afault in faults.iterrows():
        f.write('GeomodellerTask {\n')
        f.write('    Add3DFoliationToFormation {\n')
        f.write('          formation: "'+str(afault['Fault'])+'"\n')
        for indx2,ano in orientations.iterrows():
            if(ano['formation'] == afault['Fault']):
                f.write('           foliation {\n')
                ostr='                  Point3D {x:'+str(ano['X'])+'; y:'+str(ano['Y'])+'; z:'+str(ano['Z'])+'}\n'
                f.write(ostr)
                ostr='                  direction: '+str(ano['DipDirection'])+'\n'
                f.write(ostr)
                if(ano['dip'] == -999):
                    ostr='                  dip: '+str(random.randint(60,90))+'\n'
                else:
                    ostr='                  dip: '+str(ano['dip'])+'\n'
                f.write(ostr)
                if(ano['DipPolarity']==1):
                    ostr='                  polarity: Normal_Polarity\n'
                else:
                    ostr='                  polarity: Reverse_Polarity\n'
                f.write(ostr)
                ostr='           }\n'
                f.write(ostr)
        f.write('    }\n')
        f.write('}\n')

    if(save_faults):
        G=nx.read_gml(tmp_path+"fault_network.gml",label='label')
        #nx.draw(G, with_labels=True, font_weight='bold')
        edges=list(G.edges)
        #for i in range(0,len(edges)):
            #print(edges[i][0],edges[i][1])
        cycles=list(nx.simple_cycles(G))
        #display(cycles)
        f.write('#---------------------------------------------------------------\n')
        f.write('#-----------------------Link faults with faults ----------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('GeomodellerTask {\n')
        f.write('    LinkFaultsWithFaults {\n')

        for i in range(0,len(edges)):
                found=False
                for j in range(0,len(cycles)):
                    if(edges[i][0]== cycles[j][0] and edges[i][1]== cycles[j][1]):
                        found=True # fault pair is first two elements in a cycle list so don't save to taskfile
                if(not found):
                    ostr='        FaultStopsOnFaults{ fault: "'+edges[i][1]+'"; stopson: "'+edges[i][0]+'"}\n'
                    f.write(ostr)

        f.write('    }\n')
        f.write('}\n')

    if(save_faults):
        all_fault_group=np.genfromtxt(output_path+'group-fault-relationships.csv',delimiter=',',dtype='U100')
        ngroups=len(all_fault_group)
        all_fault_group=np.transpose(all_fault_group)
        nfaults=len(all_fault_group)

        f.write('#---------------------------------------------------------------\n')
        f.write('#-----------------------Link series with faults ----------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('GeomodellerTask {\n')
        f.write('    LinkFaultsWithSeries {\n')

        for i in range(1,nfaults):
            first=True
            for j in range(1,ngroups):
                if(all_fault_group[i,j]==str(1)):
                    if(first):
                        ostr='    FaultSeriesLinks{ fault: "'+all_fault_group[i,0]+'"; series: ['
                        f.write(ostr)
                        ostr='"'+all_fault_group[0,j]+'"'
                        f.write(ostr)
                        first=False
                    else:
                        ostr=', "'+all_fault_group[0,j]+'"'
                        f.write(ostr)
            if(not first):
                ostr=']}\n'
                f.write(ostr)

        f.write('    }\n')
        f.write('}\n')

    f.write('GeomodellerTask {\n')
    f.write('    SaveProjectAs {\n')
    f.write('        filename: "./'+model_name+'.xml"\n')
    f.write('    }\n')
    f.write('}\n')
    f.close()

    if(compute_etc):
        f=open(test_data_path+model_name+'/'+'m2l_compute.taskfile','w')
        f.write('#---------------------------------------------------------------\n')
        f.write('#----------------------------Load Model----------------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('GeomodellerTask {\n')
        f.write('    OpenProjectNoGUI {\n')
        f.write('        filename: "./'+model_name+'.xml"\n')
        f.write('    }\n')
        f.write('}\n')

        f.write('#---------------------------------------------------------------\n')
        f.write('#----------------------------Compute Model----------------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('\n')
        f.write('GeomodellerTask {\n')
        f.write('    ComputeModel {\n')
        f.write('        SeriesList {\n')
        f.write('            node: "All" \n')
        f.write('        }\n')
        f.write('        SectionList {\n')
        f.write('            node: "All"\n')
        f.write('        }\n')
        f.write('        FaultList {\n')
        f.write('            node: "All"\n')
        f.write('        }\n')
        f.write('        radius: 10.0\n')
        f.write('    }\n')
        f.write('}\n')

        f.write('#---------------------------------------------------------------\n')
        f.write('#-----------------------Add geophysical Properties--------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('\n')
        f.write('\n')
        f.write('\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('#--------------------------Export Lithology Voxet---------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('GeomodellerTask {\n')
        f.write('    SaveLithologyVoxet {\n')
        f.write('        nx: 25\n')
        f.write('        ny: 25\n')
        f.write('        nz: 40\n')
        f.write('        LithologyVoxetFileStub: "./Litho_Voxet/LithoVoxet.vo"\n')
        f.write('    }\n')
        f.write('}\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('#--------------------------Save As Model------------------------\n')
        f.write('#---------------------------------------------------------------\n')
        f.write('\n')

        f.write('GeomodellerTask {\n')
        f.write('    SaveProjectAs {\n')
        f.write('        filename: "/'+model_name+'.xml"\n')
        f.write('    }\n')
        f.write('}\n')
        f.write('GeomodellerTask {\n')
        f.write('    CloseProjectNoGUI {\n')
        f.write('    }\n')
        f.write('}\n')

        f.close()

# same same expect it builds a list that then gets written all at once (this version is slower!)
def loop2geomodeller2(model_name,test_data_path,tmp_path,output_path,dtm_file,bbox,save_faults,compute_etc,workflow):

    f=open(test_data_path+'/'+model_name+'/m2l.taskfile','w')
    ostr=[]

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#-----------------------Project Header-----------------------\n')
    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('name: "UWA_Intrepid"\n')
    ostr.append('description: "Automate_batch_Model"\n')
    ostr.append('    GeomodellerTask {\n')
    ostr.append('    CreateProject {\n')
    ostr.append('        name: "Hamersley"\n')
    ostr.append('        author: "Mark"\n')
    ostr.append('        date: "23/10/2019  0: 0: 0"\n')
    ostr.append('        projection { map_projection: "GDA94 / MGA50"}\n')
    ostr.append('        version: "2.0"\n')
    ostr.append('        units: meters\n')
    ostr.append('        precision: 1.0\n')
    ostr.append('        Extents {\n')
    ostr.append('            xmin: '+str(bbox[0])+'\n')
    ostr.append('            ymin: '+str(bbox[1])+'\n')
    ostr.append('            zmin: -7000\n')
    ostr.append('            xmax: '+str(bbox[2])+'\n')
    ostr.append('            ymax: '+str(bbox[3])+'\n')
    ostr.append('            zmax: 1200\n')
    ostr.append('        }\n')
    ostr.append('        deflection2d: 0.001\n')
    ostr.append('        deflection3d: 0.001\n')
    ostr.append('        discretisation: 10.0\n')
    ostr.append('        referenceTop: false\n')
    ostr.append('        CustomDTM {\n')
    ostr.append('            Extents {\n')
    ostr.append('            xmin: '+str(bbox[0])+'\n')
    ostr.append('            ymin: '+str(bbox[1])+'\n')
    ostr.append('            xmax: '+str(bbox[2])+'\n')
    ostr.append('            ymax: '+str(bbox[3])+'\n')
    ostr.append('            }\n')
    ostr.append('            name: "Topography"\n')
    ostr.append('            filename {\n')
    ostr.append('                Grid_Name: "'+dtm_file+'"\n')
    ostr.append('            }\n')
    ostr.append('            nx: 10\n')
    ostr.append('            ny: 10\n')
    ostr.append('        }\n')
    ostr.append('    }\n')
    ostr.append('}\n')

    orientations=pd.read_csv(output_path+'orientations_clean.csv',',')
    contacts=pd.read_csv(output_path+'contacts_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')

    empty_fm=[]

    for indx,afm in all_sorts.iterrows():
        foundcontact=False
        for indx2,acontact in contacts.iterrows():
            if(acontact['formation'] in afm['code']):
                foundcontact=True
                break
        foundorientation=False
        for indx3,ano in orientations.iterrows():
            if(ano['formation'] in afm['code']):
                foundorientation=True
                break
        if(not foundcontact or not foundorientation):
            empty_fm.append(afm['code'])

    #print(empty_fm)

    all_sorts=np.genfromtxt(tmp_path+'all_sorts_clean.csv',delimiter=',',dtype='U100')
    nformations=len(all_sorts)

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#-----------------------Create Formations-----------------------\n')
    ostr.append('#---------------------------------------------------------------\n')

    for i in range (1,nformations):
        if( not all_sorts[i,4] in empty_fm):
            ostr.append('GeomodellerTask {\n')
            ostr.append('CreateFormation {\n')

            ostr2='    name: "'+all_sorts[i,4].replace("\n","")+'"\n'
            ostr.append(ostr2)

            ostr2='    red: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr2='    green: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr2='    blue: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr.append('    }\n')
            ostr.append('}\n')

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#-----------------------Set Stratigraphic Pile------------------\n')
    ostr.append('#---------------------------------------------------------------\n')

    for i in range (1,nformations):
    #for i in range (nformations-1,0,-1):
        if(all_sorts[i,2]==str(1)):
            ostr.append('GeomodellerTask {\n')
            ostr.append('SetSeries {\n')

            ostr2='    name: "'+all_sorts[i][5].replace("\n","")+'"\n'
            ostr.append(ostr2)

            ostr2='    position: 1\n'
            ostr.append(ostr2)

            ostr2='    relation: "erode"\n'
            ostr.append(ostr2)

            ostr.append('    }\n')
            ostr.append('}\n')

            for j in range(nformations-1,0,-1):
    #        for j in range(1,nformations):
                if(all_sorts[j,1]==all_sorts[i,1]):
                    if( not all_sorts[j][4] in empty_fm):
                        ostr.append('GeomodellerTask {\n')
                        ostr.append('AddFormationToSeries {\n')

                        ostr2='    series: "'+all_sorts[j][5]+'"\n'
                        ostr.append(ostr2)

                        ostr2='    formation: "'+all_sorts[j][4]+'"\n'
                        ostr.append(ostr2)

                        ostr.append('    }\n')
                        ostr.append('}\n')

    if(save_faults):
        output_path=test_data_path+'output/'

        faults_len=pd.read_csv(output_path+'fault_dimensions.csv')

        n_allfaults=len(faults_len)

        fcount=0
        for i in range(0,n_allfaults):
            ostr.append('GeomodellerTask {\n')
            ostr.append('CreateFault {\n')
            ostr2='    name: "'+faults_len.iloc[i]["Fault"]+'"\n'
            ostr.append(ostr2)

            ostr2='    red: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr2='    green: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr2='    blue: '+str(random.randint(1,256)-1)+'\n'
            ostr.append(ostr2)

            ostr.append('    }\n')
            ostr.append('}\n')
            fcount=fcount+1

            ostr.append('GeomodellerTask {\n')
            ostr.append('    Set3dFaultLimits {\n')
            ostr.append('        Fault_name: "'+faults_len.iloc[i]["Fault"]+ '"\n')
            ostr.append('        Horizontal: '+str(faults_len.iloc[i]["HorizontalRadius"])+ '\n')
            ostr.append('        Vertical: '+str(faults_len.iloc[i]["VerticalRadius"])+ '\n')
            ostr.append('        InfluenceDistance: '+str(faults_len.iloc[i]["InfluenceDistance"])+ '\n')
            ostr.append('    }\n')
            ostr.append('}\n')

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#-----------------------Import 3D contact data ---Base Model----\n')
    ostr.append('#---------------------------------------------------------------\n')

    contacts=pd.read_csv(output_path+'contacts_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')
    #all_sorts.set_index('code',  inplace = True)
    #display(all_sorts)

    for inx,afm in all_sorts.iterrows():
        #print(afm[0])
        if( not afm['code'] in empty_fm):
            ostr.append('GeomodellerTask {\n')
            ostr.append('    Add3DInterfacesToFormation {\n')
            ostr.append('          formation: "'+str(afm['code'])+'"\n')

            for indx2,acontact in contacts.iterrows():
                if(acontact['formation'] in afm['code'] ):
                    ostr2='              point {x:'+str(acontact['X'])+'; y:'+str(acontact['Y'])+'; z:'+str(acontact['Z'])+'}\n'
                    ostr.append(ostr2)
            ostr.append('    }\n')
            ostr.append('}\n')
    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#------------------Import 3D orientation data ---Base Model-----\n')
    ostr.append('#---------------------------------------------------------------\n')

    orientations=pd.read_csv(output_path+'orientations_clean.csv',',')
    all_sorts=pd.read_csv(tmp_path+'all_sorts_clean.csv',',')
    #all_sorts.set_index('code',  inplace = True)
    #display(all_sorts)

    for inx,afm in all_sorts.iterrows():
        #print(groups[agp])
        if( not afm['code'] in empty_fm):
            ostr.append('GeomodellerTask {\n')
            ostr.append('    Add3DFoliationToFormation {\n')
            ostr.append('          formation: "'+str(afm['code'])+'"\n')
            for indx2,ano in orientations.iterrows():
                if(ano['formation'] in afm['code']):
                    ostr.append('           foliation {\n')
                    ostr2='                  Point3D {x:'+str(ano['X'])+'; y:'+str(ano['Y'])+'; z:'+str(ano['Z'])+'}\n'
                    ostr.append(ostr2)
                    ostr2='                  direction: '+str(ano['azimuth'])+'\n'
                    ostr.append(ostr2)
                    ostr2='                  dip: '+str(ano['dip'])+'\n'
                    ostr.append(ostr2)
                    if(ano['polarity']==1):
                        ostr2='                  polarity: Normal_Polarity\n'
                    else:
                        ostr2='                  polarity: Reverse_Polarity\n'
                    ostr.append(ostr2)
                    ostr2='           }\n'
                    ostr.append(ostr2)
            ostr.append('    }\n')
            ostr.append('}\n')

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#-----------------------Import 3D fault data ---Base Model------\n')
    ostr.append('#---------------------------------------------------------------\n')

    contacts=pd.read_csv(output_path+'faults.csv',',')
    faults=pd.read_csv(output_path+'fault_dimensions.csv',',')

    for indx,afault in faults.iterrows():
        ostr.append('GeomodellerTask {\n')
        ostr.append('    Add3DInterfacesToFormation {\n')
        ostr.append('          formation: "'+str(afault['Fault'])+'"\n')
        for indx2,acontact in contacts.iterrows():
            if(acontact['formation'] == afault['Fault']):
                ostr2='              point {x:'+str(acontact['X'])+'; y:'+str(acontact['Y'])+'; z:'+str(acontact['Z'])+'}\n'
                ostr.append(ostr2)
        ostr.append('    }\n')
        ostr.append('}\n')

    ostr.append('#---------------------------------------------------------------\n')
    ostr.append('#------------------Import 3D fault orientation data ------------\n')
    ostr.append('#---------------------------------------------------------------\n')

    orientations=pd.read_csv(output_path+'fault_orientations.csv',',')
    faults=pd.read_csv(output_path+'fault_dimensions.csv',',')

    for indx,afault in faults.iterrows():
        ostr.append('GeomodellerTask {\n')
        ostr.append('    Add3DFoliationToFormation {\n')
        ostr.append('          formation: "'+str(afault['Fault'])+'"\n')
        for indx2,ano in orientations.iterrows():
            if(ano['formation'] == afault['Fault']):
                ostr.append('           foliation {\n')
                ostr2='                  Point3D {x:'+str(ano['X'])+'; y:'+str(ano['Y'])+'; z:'+str(ano['Z'])+'}\n'
                ostr.append(ostr2)
                ostr2='                  direction: '+str(ano['DipDirection'])+'\n'
                ostr.append(ostr2)
                if(ano['dip'] == -999):
                    ostr2='                  dip: '+str(random.randint(60,90))+'\n'
                else:
                    ostr2='                  dip: '+str(ano['dip'])+'\n'
                ostr.append(ostr2)
                if(ano['DipPolarity']==1):
                    ostr2='                  polarity: Normal_Polarity\n'
                else:
                    ostr2='                  polarity: Reverse_Polarity\n'
                ostr.append(ostr2)
                ostr2='           }\n'
                ostr.append(ostr2)
        ostr.append('    }\n')
        ostr.append('}\n')

    if(save_faults):
        G=nx.read_gml(tmp_path+"fault_network.gml",label='label')
        #nx.draw(G, with_labels=True, font_weight='bold')
        edges=list(G.edges)
        #for i in range(0,len(edges)):
            #print(edges[i][0],edges[i][1])
        cycles=list(nx.simple_cycles(G))
        #display(cycles)
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#-----------------------Link faults with faults ----------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    LinkFaultsWithFaults {\n')

        for i in range(0,len(edges)):
                found=False
                for j in range(0,len(cycles)):
                    if(edges[i][0]== cycles[j][0] and edges[i][1]== cycles[j][1]):
                        found=True # fault pair is first two elements in a cycle list so don't save to taskfile
                if(not found):
                    ostr2='        FaultStopsOnFaults{ fault: "'+edges[i][1]+'"; stopson: "'+edges[i][0]+'"}\n'
                    ostr.append(ostr2)

        ostr.append('    }\n')
        ostr.append('}\n')

    if(save_faults):
        all_fault_group=np.genfromtxt(output_path+'group-fault-relationships.csv',delimiter=',',dtype='U100')
        ngroups=len(all_fault_group)
        all_fault_group=np.transpose(all_fault_group)
        nfaults=len(all_fault_group)

        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#-----------------------Link series with faults ----------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    LinkFaultsWithSeries {\n')

        for i in range(1,nfaults):
            first=True
            for j in range(1,ngroups):
                if(all_fault_group[i,j]==str(1)):
                    if(first):
                        ostr2='    FaultSeriesLinks{ fault: "'+all_fault_group[i,0]+'"; series: ['
                        ostr.append(ostr2)
                        ostr2='"'+all_fault_group[0,j]+'"'
                        ostr.append(ostr2)
                        first=False
                    else:
                        ostr2=', "'+all_fault_group[0,j]+'"'
                        ostr.append(ostr2)
            if(not first):
                ostr2=']}\n'
                ostr.append(ostr2)

        ostr.append('    }\n')
        ostr.append('}\n')

    ostr.append('GeomodellerTask {\n')
    ostr.append('    SaveProjectAs {\n')
    ostr.append('        filename: "./'+model_name+'.xml"\n')
    ostr.append('    }\n')
    ostr.append('}\n')
    f.close()

    if(compute_etc):
        f=open(test_data_path+model_name+'/'+'m2l_compute.taskfile','w')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#----------------------------Load Model----------------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    OpenProjectNoGUI {\n')
        ostr.append('        filename: "./'+model_name+'.xml"\n')
        ostr.append('    }\n')
        ostr.append('}\n')

        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#----------------------------Compute Model----------------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    ComputeModel {\n')
        ostr.append('        SeriesList {\n')
        ostr.append('            node: "All" \n')
        ostr.append('        }\n')
        ostr.append('        SectionList {\n')
        ostr.append('            node: "All"\n')
        ostr.append('        }\n')
        ostr.append('        FaultList {\n')
        ostr.append('            node: "All"\n')
        ostr.append('        }\n')
        ostr.append('        radius: 10.0\n')
        ostr.append('    }\n')
        ostr.append('}\n')

        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#-----------------------Add geophysical Properties--------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('\n')
        ostr.append('\n')
        ostr.append('\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#--------------------------Export Lithology Voxet---------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    SaveLithologyVoxet {\n')
        ostr.append('        nx: 25\n')
        ostr.append('        ny: 25\n')
        ostr.append('        nz: 40\n')
        ostr.append('        LithologyVoxetFileStub: "./Litho_Voxet/LithoVoxet.vo"\n')
        ostr.append('    }\n')
        ostr.append('}\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('#--------------------------Save As Model------------------------\n')
        ostr.append('#---------------------------------------------------------------\n')
        ostr.append('\n')

        ostr.append('GeomodellerTask {\n')
        ostr.append('    SaveProjectAs {\n')
        ostr.append('        filename: "/'+model_name+'.xml"\n')
        ostr.append('    }\n')
        ostr.append('}\n')
        ostr.append('GeomodellerTask {\n')
        ostr.append('    CloseProjectNoGUI {\n')
        ostr.append('    }\n')
        ostr.append('}\n')
        f.writelines(ostr)
        f.close()

from pyamg import solve
def solve_pyamg(A,B):
    return solve(A,B,verb=False,tol=1e-8)

##########################################################################
# Import outputs from map2loop to LoopStructural and view with Lavavu
#
# loop2LoopStructural(thickness_file,orientation_file,contacts_file,bbox)
# Args:
# thickness_file path of fornation thickness file
# orientation_file path of orientations file
# contacts_file path of contacts file
# bbox model bounding box
#
# Calculates model and displays in LavaVu wthin notebook
##########################################################################
def loop2LoopStructural(thickness_file,orientation_file,contacts_file,bbox):
    from LoopStructural import GeologicalModel
    from LoopStructural.visualisation import LavaVuModelViewer
    import lavavu

    df = pd.read_csv(thickness_file)

    thickness = {}
    for f in df['formation'].unique():
        thickness[f] = np.mean(df[df['formation']==f]['thickness'])

    #display(thickness)
    order = ['P__TKa_xs_k','P__TKo_stq','P__TKk_sf','P__TK_s',
    'A_HAu_xsl_ci', 'A_HAd_kd', 'A_HAm_cib', 'A_FOj_xs_b',
    'A_FO_xo_a', 'A_FO_od', 'A_FOu_bbo',
    'A_FOp_bs', 'A_FOo_bbo', 'A_FOh_xs_f', 'A_FOr_b']

    strat_val = {}
    val = 0
    for o in order:
        if o in thickness:
            strat_val[o] = val
            val+=thickness[o]

    #display(strat_val)

    orientations = pd.read_csv(orientation_file)
    contacts = pd.read_csv(contacts_file)

    contacts['val'] = np.nan

    for o in strat_val:
        contacts.loc[contacts['formation']==o,'val'] = strat_val[o]
    data = pd.concat([orientations,contacts],sort=False)
    data['type'] = np.nan
    for o in order:
        data.loc[data['formation']==o,'type'] = 's0'
    data

    boundary_points = np.zeros((2,3))
    boundary_points[0,0] = bbox[0]
    boundary_points[0,1] = bbox[1]
    boundary_points[0,2] = -20000
    boundary_points[1,0] = bbox[2]
    boundary_points[1,1] = bbox[3]
    boundary_points[1,2] = 1200

    model = GeologicalModel(boundary_points[0,:],boundary_points[1,:])
    model.set_model_data(data)
    strati = model.create_and_add_foliation('s0', #identifier in data frame
                                                        interpolatortype="FDI", #which interpolator to use
                                                        nelements=400000, # how many tetras/voxels
                                                        buffer=0.1, # how much to extend nterpolation around box
                                                        solver='external',
                                                        external=solve_pyamg
                                                       )
    #viewer = LavaVuModelViewer()
    viewer = LavaVuModelViewer(model)
    viewer.add_data(strati['feature'])
    viewer.add_isosurface(strati['feature'],
    #                       nslices=10,
                          slices= strat_val.values(),
    #                     voxet={'bounding_box':boundary_points,'nsteps':(100,100,50)},
                          paint_with=strati['feature'],
                          cmap='tab20'

                         )
    #viewer.add_scalar_field(model.bounding_box,(100,100,100),
   #                           'scalar',
    ##                             norm=True,
    #                         paint_with=strati['feature'],
    #                         cmap='tab20')
    viewer.add_scalar_field(strati['feature'])
    viewer.set_viewer_rotation([-53.8190803527832, -17.1993350982666, -2.1576387882232666])
    #viewer.save("fdi_surfaces.png")
    viewer.interactive()

##########################################################################
# Import outputs from map2loop to gempy and view with pyvtk
# loop2gempy(test_data_name,tmp_path,vtk_pth,orientations_file,contacts_file,groups_file,dtm_reproj_file,bbox,model_base, model_top,vtk)
# Args:
# test_data_name root name of project
# tmp_path path of temp files directory
# vtk_pth path of vtk output directory
# orientations_file path of orientations file
# contacts_file path of contacts file
# groups_file path of groups file
# dtm_reproj_file path of dtm file
# bbox model bounding box
# model_base z value ofbase of model
# model_top z value of top of model
# vtk flag as to wether to save out model to vtk
#
# Calculates model and displays in external vtk viewer
##########################################################################
def loop2gempy(test_data_name: str, tmp_path: str, vtk_path: str, orientations_file: str,
               contacts_file: str, groups_file:str,
               bbox: tuple, model_base: float, model_top: float, vtk: bool, dtm_reproj_file:str = None,
               va=None,
               verbose: bool = False, compute: bool = True):
    """

    :param test_data_name:
    :param tmp_path:
    :param vtk_path:
    :param orientations_file:
    :param contacts_file:
    :param groups_file:
    :param bbox:
    :param model_base:
    :param model_top:
    :param vtk:
    :param dtm_reproj_file:
    :param va: vertical anisotropy. Factor by which all Z coordinates are multiplied by
    :param verbose:
    :param compute:
    :return:
    """
    import gempy as gp
    from gempy import plot

    geo_model = gp.create_model(test_data_name)

    # If depth coordinates are much smaller than XY the whole system of equations becomes very unstable. Until
    # I fix it properly in gempy this is a handcrafted hack
    if va is None:
        va = (float(bbox[0]) - float(bbox[2])) / (model_base - model_top)/2

        if va < 3:
            va = 0
        else:
            print('The vertical exageration is: ', va)

    gp.init_data(geo_model, extent=[bbox[0], bbox[2], bbox[1], bbox[3], model_base * va, model_top * va],
                 resolution=(50, 50, 50),
                 path_o=orientations_file,
                 path_i=contacts_file)

    geo_model.modify_surface_points(geo_model.surface_points.df.index, Z=geo_model.surface_points.df['Z'] * va)

    if dtm_reproj_file is not None:
        # Load reprojected topography to model

        fp = dtm_reproj_file
        geo_model.set_topography(source='gdal', filepath=fp)

        # Rescaling topography:
        geo_model.grid.topography.values[:, 2] *= va
        geo_model.grid.update_grid_values()
        geo_model.update_from_grid()

    # Pile processing:
    contents = np.genfromtxt(groups_file,
                             delimiter=',', dtype='U100')

    # Init dictionary Series:Surfaces
    map_series_to_surfaces = {}
    choice = 0
    for group in contents:
        # Reading surfaces groups
        surfaces_g = np.atleast_2d(np.genfromtxt(tmp_path + group + '.csv', delimiter=',', dtype='U100'))

        # Check if there are several choices
        if surfaces_g.shape[1] > 1:
            surfaces_g = surfaces_g[choice]
        # Deleting the first element since it is not a surface
        surfaces_g = surfaces_g[1:]
        # Creating the mapping dictionary
        map_series_to_surfaces[group] = surfaces_g.tolist()

    if verbose is True:
        print(map_series_to_surfaces)

    # Setting pile to model
    gp.map_series_to_surfaces(geo_model, map_series_to_surfaces, remove_unused_series=False)

    #    Removing related data
    del_surfaces = geo_model.surfaces.df.groupby('series').get_group('Default series')['surface']
    geo_model.delete_surfaces(del_surfaces, remove_data=True)

    # Removing series that have not been mapped to any surface
    geo_model.delete_series('Default series')

    if compute is True:
        gp.set_interpolator(geo_model, theano_optimizer='fast_run', dtype='float64')
        gp.compute_model(geo_model)

    # Visualise Model
    gp.plot.plot_3D(geo_model, render_data=False)

    # Save model as vtk
    if vtk:
        gp.plot.export_to_vtk(geo_model, path=vtk_path, name=test_data_name + '.vtk', voxels=False, block=None,
                              surfaces=True)

    return geo_model

def loop2gempy_(test_data_name, tmp_path, vtk_path, orientations_file, contacts_file, groups_file, dtm_reproj_file,
                bbox, model_base, model_top, vtk):
    import gempy as gp
    from gempy import plot
    geo_model = gp.create_model(test_data_name)

    # If depth coordinates are much smaller than XY the whole system of equations becomes very unstable. Until
    # I fix it properly in gempy this is a handcrafted hack
    ve = (bbox[0] - bbox[2]) / (model_base - model_top)

    if ve < 3:
        ve = 0
    else:
        print('The vertical exageration is: ', ve)

    gp.init_data(geo_model, extent=[bbox[0], bbox[2], bbox[1], bbox[3], model_base*ve, model_top*ve],
        resolution = (50,50,50),
          path_o = orientations_file,
          path_i = contacts_file, default_values=True);

    # Show example lithological points
    #gp.get_data(geo_model, 'surface_points').head()

    # Show example orientations
    #gp.get_data(geo_model, 'orientations').head()

    # Plot some of this data
    #gp.plot.plot_data(geo_model, direction='z')

    geo_model.modify_surface_points(geo_model.surface_points.df.index, Z=geo_model.surface_points.df['Z']*ve)

    # Load reprojected topgraphy to model

    fp = dtm_reproj_file
    geo_model.set_topography(source='gdal',filepath=fp)

    contents=np.genfromtxt(groups_file,delimiter=',',dtype='U100')
    ngroups=len(contents)

    faults = gp.Faults()
    series = gp.Series(faults)
    #series.df

    #display(ngroups,contents)
    groups = []

    for i in range (0,ngroups):
        groups.append(contents[i].replace("\n",""))
        series.add_series(contents[i].replace("\n",""))
        print(contents[i].replace("\n",""))

    series.delete_series('Default series')

    #series

    # Load surfaces and assign to series
    surfaces = gp.Surfaces(series)

    print(ngroups,groups)
    for i in range(0,ngroups):
        contents=np.genfromtxt(tmp_path+groups[i]+'.csv',delimiter=',',dtype='U100')
        nformations=len(contents.shape)

        if(nformations==1):
            for j in range (1,len(contents)):
                surfaces.add_surface(str(contents[j]).replace("\n",""))
                d={groups[i]:str(contents[j]).replace("\n","")}
                surfaces.map_series({groups[i]:(str(contents[j]).replace("\n",""))}) #working but no gps
        else:
            for j in range (1,len(contents[0])):
                surfaces.add_surface(str(contents[0][j]).replace("\n",""))
                d={groups[i]:str(contents[0][j]).replace("\n","")}
                surfaces.map_series({groups[i]:(str(contents[0][j]).replace("\n",""))}) #working but no gps

    # Set Interpolation Data
    id_only_one_bool = geo_model.surface_points.df['id'].value_counts() == 1
    id_only_one = id_only_one_bool.index[id_only_one_bool]
    single_vals = geo_model.surface_points.df[geo_model.surface_points.df['id'].isin(id_only_one)]
    for idx, vals in single_vals.iterrows():
        geo_model.add_surface_points(vals['X'], vals['Y'], vals['Z'], vals['surface'])

    geo_model.update_structure()

    gp.set_interpolation_data(geo_model,
                              compile_theano=True,
                              theano_optimizer='fast_compile',
                              verbose=[])

    # Provide summary data on model

    #geo_model.additional_data.structure_data

    #Calculate Model
    gp.compute_model(geo_model)

    # Extract surfaces to visualize in 3D renderers
    #gp.plot.plot_section(geo_model, 49, direction='z', show_data=False)

    ver, sim = gp.get_surfaces(geo_model)

    # import winsound
    # duration = 700  # milliseconds
    # freq = 1100  # Hz
    # winsound.Beep(freq, duration)
    # winsound.Beep(freq, duration)
    # winsound.Beep(freq, duration)

    #Visualise Model
    gp.plot.plot_3D(geo_model, render_data=False)

    #Save model as vtk
    if(vtk):
        gp.plot.export_to_vtk(geo_model, path=vtk_path, name=test_data_name+'.vtk', voxels=False, block=None, surfaces=True)

    return geo_model


import os
import ast
import pystache
import json
import urllib3

from kubernetes import client as k_client
from kubernetes import config as k_config
from kubernetes.client.rest import ApiException
from base64 import b64encode, b64decode

urllib3.disable_warnings()

class ComputeParty:
    """
    Represents one party in a computation.
    Generates all Kubernetes objects.
    """

    def __init__(
            self, pid, all_pids, compute_id, protocol, app, protocol_config,
            namespace_map, jiff_server_ip, data_source="swift"
    ):

        self.pid = pid
        self.all_pids = all_pids
        self.compute_id = compute_id
        self.app = app
        self.data_source = data_source
        self.config = protocol_config
        self.mpc_backend = self.set_mpc_backend()
        self.endpoint = self.get_endpoint()
        self.data_source = self.resolve_data_source()
        self.namespace_map = namespace_map
        self.namespace = namespace_map[int(pid) - 1]
        self.jiff_server_ip = jiff_server_ip

        self.template_directory = "{}/templates/".format(os.path.dirname(os.path.realpath(__file__)))
        self.name = "conclave-{0}-{1}".format(compute_id, str(pid))
        self.config_map_name = "conclave-{0}-{1}-map".format(compute_id, str(pid))

        self.protocol = protocol
        self.protocol_main = self.gen_protocol_main()
        self.conclave_config = self.gen_conclave_config()
        self.config_map_body = self.define_config_map()
        self.pod_body = self.define_pod()
        self.service_body = self.define_service()
        self.network_policy_body = self.define_network_policy()

    def set_mpc_backend(self):

        if len(self.config["data"]["endpoints"]) == 2:
            self.app.logger.info("Using Obliv-C backend for MPC computation.\n")
            return "obliv-c"
        else:
            self.app.logger.info("Using JIFF backend for MPC computation.\n")
            return "jiff"

    def get_endpoint(self):

        return self.config["data"]["endpoints"][self.pid - 1]

    def resolve_data_source(self):

        if self.endpoint["backend"].lower() == "swift":
            return "swift"
        elif self.endpoint["backend"].lower() == "dataverse":
            return "dataverse"
        else:
            raise Exception("Data backend not recognized: {}\n".format(self.endpoint["backend"]))

    @staticmethod
    def format_protocol(protocol):
        """
        Format protocol code with tabs before injecting into template.
        """

        ret = []

        for line in protocol.split("\n"):
            ret.append("\t" + line)

        return "\n".join(ret)

    def gen_protocol_main(self):
        """
        Generate protocol code.
        """

        if self.config['protocol']['format'] == 'b64':
            protocol_decoded = self.format_protocol(b64decode(self.protocol).decode())
        else:
            protocol_decoded = self.format_protocol(self.protocol)

        params = {
            "PROTOCOL": protocol_decoded,
            "MPC_FRAMEWORK": self.mpc_backend
        }

        data_template = open("{}/protocol_main.tmpl".format(self.template_directory), 'r').read()
        rendered = pystache.render(data_template, params)

        self.app.logger.info("CC protocol: \n{}\n".format(rendered))

        return b64encode(rendered.encode()).decode()

    def gen_conclave_config(self):
        """
        Generate CC Config JSON.

        TODO: configurable PROJ_NAME (will probably need to resolve with a db call to the input data)
        """

        net_list = self.gen_net_config()

        params = \
            {
                "PID": self.pid,
                "ALL_PIDS": self.all_pids,
                "WORKFLOW_NAME": "conclave-{}".format(self.compute_id),
                "NET_CONFIG": net_list,
                "SPARK_AVAIL": 0,
                "SPARK_IP_PORT": "N/A",
                "OC_AVAIL": int(self.mpc_backend == "obliv-c"),
                "OC_IP_PORT":
                    "0.0.0.0:5001" if self.pid == 1 else "conclave-{0}-1-service.{1}.svc.cluster.local:5001"
                    .format(self.compute_id, self.namespace_map[1]),
                "JIFF_AVAIL": int(self.mpc_backend == "jiff"),
                "PARTY_COUNT": len(self.all_pids),
                "SERVER_SERVICE": self.jiff_server_ip,
                "DATA_BACKEND": self.data_source,
                "PROJ_NAME": "ccs-musketeer-demo",
                "SOURCE_CONTAINER_NAME": self.endpoint["containerName"] if self.data_source == "swift" else "",
                "FILENAME": self.endpoint["fileName"],
                "OUTPUT_AUTH_URL": open("/etc/swift-auth/auth_url", "r").read(),
                "OUTPUT_PROJ_DOMAIN": open("/etc/swift-auth/proj_domain", "r").read(),
                "OUTPUT_PROJ_NAME": open("/etc/swift-auth/proj_name", "r").read(),
                "OUTPUT_USER_NAME": open("/etc/swift-auth/username", "r").read(),
                "OUTPUT_PASS": open("/etc/swift-auth/password", "r").read(),
                "DEST_CONTAINER_NAME": self.compute_id,
                "SOURCE_ALIAS": self.endpoint["alias"] if self.data_source == "dataverse" else "",
                "SOURCE_DOI": self.endpoint["doi"] if self.data_source == "dataverse" else ""
            }

        data_template = open("{}/conclave_config.tmpl".format(self.template_directory), 'r').read()

        rendered = pystache.render(data_template, params)

        self.app.logger.info("CC JSON file:\n{}".format(rendered))

        return b64encode(rendered.encode()).decode()

    def gen_net_config(self):
        """
        Generate CC Net Config string that gets inserted into CC Config JSON.
        """

        ret_net = []

        for i in self.all_pids:
            if i == self.pid:
                ret_net.append({"host": "0.0.0.0", "port": 5000})
            else:
                ret_net.append({"host": "conclave-{0}-{1}-service.{2}.svc.cluster.local"
                               .format(self.compute_id, str(i), self.namespace_map[i-1]), "port": 5000})

        return json.dumps(ret_net)

    def define_config_map(self):
        """
        Populate ConfigMap template.
        """

        data_params = \
            {
                "NAME": "conclave-{0}-{1}-map".format(self.compute_id, str(self.pid)),
                "NAMESPACE": self.namespace,
                "PROTOCOL_MAIN": str(self.protocol_main),
                "CC-CONF": self.conclave_config
            }

        data_template = open("{}configmap.tmpl".format(self.template_directory), 'r').read()

        rendered = pystache.render(data_template, data_params)

        return ast.literal_eval(rendered)

    def setup_data_params(self):
        """
        Construct key/path values for input data configmap on pod.
        """

        if self.data_source == "swift":
            return [
                {"key": "osAuthUrl", "path": "auth_url"},
                {"key": "username", "path": "username"},
                {"key": "password", "path": "password"}
            ]

        elif self.data_source == "dataverse":
            return [
                {"key": "host", "path": "dv_host"},
                {"key": "token", "path": "dv_token"}
            ]
        else:
            raise Exception("Data backend not recognized: {}\n".format(self.data_source))

    def define_pod(self):
        """
        Populate Pod template.
        """

        params = \
            {
                "POD_NAME": self.name,
                "CONFIGMAP_NAME": self.config_map_name,
                "NAMESPACE": self.namespace,
                "COMPUTE_ID": self.compute_id,
                "IMAGE": "docker.io/bengetch/conclave:jiff" if self.mpc_backend == "jiff"
                else "docker.io/bengetch/conclave:oc",
                "BACKEND": self.data_source,
                "BACKEND_PARAMS": self.setup_data_params(),
                "FILENAME": self.endpoint["fileName"],
                "UID": self.config["uid"]
            }

        data_template = open("{}/pod.tmpl".format(self.template_directory), 'r').read()

        rendered = pystache.render(data_template, params)

        return ast.literal_eval(rendered)

    def define_service(self):
        """
        Populate Service template for CC Pods and MPC backends.
        """

        params = \
            {
                "SERVICE_NAME": "{}-service".format(self.name),
                "APP_NAME": self.name,
                "COMPUTE_ID": self.compute_id
            }

        data_template = open("{}/service.tmpl".format(self.template_directory), 'r').read()

        rendered = pystache.render(data_template, params)

        return ast.literal_eval(rendered)

    def define_network_policy(self):

        params = \
            {
                "NAME": self.name,
                "POD_NAME": self.name,
                "OTHER_PROJECT": "ccd-two" if self.namespace == "ccd-one" else "ccd-one"
            }

        data_template = open("{}/network_policy.tmpl".format(self.template_directory), 'r').read()

        rendered = pystache.render(data_template, params)

        return ast.literal_eval(rendered)

    def launch(self):
        """
        Launch all Kubernetes objects.
        """

        k_config.load_incluster_config()
        kube_client = k_client.CoreV1Api()
        # kube_client_networking = k_client.NetworkingV1Api()

        try:
            api_response = \
                kube_client.create_namespaced_config_map(self.namespace, self.config_map_body, pretty='true')
            self.app.logger.info(
                "ConfigMap created successfully with response: \n{}\n"
                    .format(api_response))
        except ApiException as e:
            self.app.logger.error(
                "Error creating ConfigMap: \n{}\n"
                    .format(e))

        try:
            api_response = \
                kube_client.create_namespaced_service(self.namespace, body=self.service_body, pretty='true')
            self.app.logger.info(
                "Service created successfully with response: \n{}\n"
                    .format(api_response))
        except ApiException as e:
            self.app.logger.error(
                "Error creating Service: \n{}\n"
                    .format(e))

        try:
            api_response = kube_client.create_namespaced_pod(self.namespace, body=self.pod_body, pretty='true')
            self.app.logger.info(
                "Pod created successfully with response: \n{}\n"
                    .format(api_response))
        except ApiException as e:
            self.app.logger.error(
                "Error creating Pod: \n{}\n"
                    .format(e))

        # try:
        #     api_response = kube_client_networking.create_namespaced_network_policy(
        #         self.namespace, body=self.network_policy_body, pretty='true')
        #     self.app.logger.info(
        #         "NetworkPolicy successfully created with response: \n{}\n"
        #             .format(api_response))
        # except ApiException as e:
        #     self.app.logger.error(
        #         "Error creating NetworkPolicy: \n{}\n"
        #             .format(e))

        return


from typing import Optional, List

import pyodbc

from src.data_access.RecordNotFoundException import RecordNotFoundException
from conftest import reset_test_database
from src.data_access.sql_command_executor import SqlCommandExecutor
from src.book_drop.repository.i_person import IPersonRepository
from src.book_drop.models.person import Person

class SqlPersonRepository(IPersonRepository):

    def __init__(self, driver: str = "{SQL Server}", server: str = "", database: str = "", trusted: bool = True):
        self.executor = SqlCommandExecutor(driver=driver, server=server, database=database, trusted=trusted)

    def create_person(self, name: str, user_name: str) -> Optional[Person]:
        if name is None or name == "":
            raise ValueError("First name cannot be empty.")

        sp_name = "BookDrop.CreatePerson"
        inp_param_names = ['Name', 'UserName']
        inp_param_values = [name, user_name]
        out_param = {
            'sp_local': ['PersonID'],
            'sp_local_types': ['int'],
            'sp_out': ["PersonID"]
        }

        results = self.executor.execute_stored_procedure(sp_name,
                                                         input_param_names=inp_param_names,
                                                         input_param_values=inp_param_values,
                                                         output_param=out_param)

        print(results)
        if len(results) == 1:
            return Person(results[0].PersonID_var, name)
        else:
            return None

    def retrieve_persons(self) -> Optional[List[Person]]:
        sp_name = "BookDrop.RetrievePersons"
        results = self.executor.execute_stored_procedure(sp_name)

        if len(results) >= 1:
            return results
            # return self.translate_persons(results)
        else:
            return None

    def fetch_person(self, user_name: str) -> Optional[Person]:
        sp_name = "BookDrop.FetchPerson"
        inp_param_names = ['UserName']
        inp_param_values = [user_name]

        results = self.executor.execute_stored_procedure(sp_name,
                                                         input_param_names=inp_param_names,
                                                         input_param_values=inp_param_values)
        if len(results) == 1:
            print(results)
            return results[0][0]
        else:
            raise RecordNotFoundException(user_name)

    def translate_person(self, row: pyodbc.Row) -> Person:
        return Person(row.PersonId, row.Name)

    def translate_persons(self, rows: List[pyodbc.Row]) -> List[Person]:
        persons = []
        for row in rows:
            persons.append(self.translate_person(row))
        return persons

if __name__ == "__main__":

    person_repo = SqlPersonRepository(server="(local)\\SQLEXPRESS", database="cc520")
    person = person_repo.create_person("Doug", "D_Man55")

    dougs = person_repo.retrieve_persons()
    print(dougs)


#!/usr/bin/python

from pygame.locals import *

from Player import *
from level import *
from Camera import *
from npc import *

WIN_WIDTH = 800
WIN_HEIGHT = 600
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
BACKGROUND_COLOR = "#eeeeee"

HERO_X = 55
HERO_Y = 55

def create_level(screen):
    file = open("level.txt", "r")
    arFile = file.read().split("\n")
    entities = create(screen, arFile)
    level_width = len(arFile[0])
    level_height = len(arFile)
    return level_width, level_height, entities

def main():
    # Initialise screen
    pygame.init()
    screen = display.set_mode(DISPLAY)
    display.set_caption("Game")

   # create npc
    npc_list = []
    npc = Npc(200, 120)
    npc1 = Npc(300, 160)
    npc_list.append(npc)
    npc_list.append(npc1)

    # create player
    player = Player(HERO_X, HERO_X, npc_list)

    # Fill background
    background = pygame.Surface(screen.get_size())
    background.fill(Color(BACKGROUND_COLOR))

    #create level
    ar_level_size = create_level(background)
    level_width = ar_level_size[0] * 32
    level_height = ar_level_size[1] * 32

    entities = ar_level_size[2]
    timer = time.Clock()

    camera = Camera(level_width, level_height)

    while 1:
        timer.tick(240)

        if player.rect.x + player.rect.width >= level_width:
            right = False
        if player.rect.x <= 0:
            left = False
        if player.rect.y + player.rect.height >= level_height:
            down = False
        if player.rect.y <= 0:
            up = False

        for event in pygame.event.get():
            if event.type == QUIT:
                return

        # Move the player if an arrow key is pressed
        key_press = pygame.key.get_pressed()
        if key_press[pygame.K_LEFT]:
            player.update(-1, 0)
        if key_press[pygame.K_RIGHT]:
            player.update(1, 0)
        if key_press[pygame.K_UP]:
            player.update(0, -1)
        if key_press[pygame.K_DOWN]:
            player.update(0, 1)

        camera.update(player)

        for e in entities:
            screen.blit(e.image, camera.apply(e))

        player.draw(screen, camera)

        for npc in npc_list:
            npc.draw(screen, camera)

        display.flip()

if __name__ == '__main__': main()