''' Manage AMITT metadata
Create a page for each of the AMITT objects, if it doesn't already exist.
If it does exist, update the metadata on it, and preserve any hand-
created notes below the metadata area in it.
* todo: add all framework comments to the repo issues list
'''
import pandas as pd
import numpy as np
import os
class Amitt:
    
    def __init__(self, infile = 'amitt_metadata_v3.xlsx'):
        
        # Load metadata from file
        metadata = {}
        xlsx = pd.ExcelFile(infile)
        for sheetname in xlsx.sheet_names:
            metadata[sheetname] = xlsx.parse(sheetname)
        # Create individual tables and dictionaries
        self.phases = metadata['phases']
        self.techniques = metadata['techniques']
        self.tasks = metadata['tasks']
        self.incidents = metadata['incidents']
        self.it = self.create_incident_technique_crosstable(metadata['incidenttechniques'])
        tactechs = self.techniques.groupby('tactic')['id'].apply(list).reset_index().rename({'id':'techniques'}, axis=1)
        self.tactics = metadata['tactics'].merge(tactechs, left_on='id', right_on='tactic', how='left').fillna('').drop('tactic', axis=1)
        self.phasedict = self.make_object_dict(self.phases)
        self.tacdict   = self.make_object_dict(self.tactics)
        self.techdict  = self.make_object_dict(self.techniques)
        self.ngridrows = max(tactechs['techniques'].apply(len)) +2
        self.ngridcols = len(self.tactics)
        self.grid = self.create_display_grid()
    def create_incident_technique_crosstable(self, it_metadata):
        # Generate full cross-table between incidents and techniques
        it = it_metadata
        it.index=it['id']
        it = it['techniques'].str.split(',').apply(lambda x: pd.Series(x)).stack().reset_index(level=1, drop=True).to_frame('technique').reset_index().merge(it.drop('id', axis=1).reset_index()).drop('techniques', axis=1)
        it = it.merge(self.incidents[['id','name']], 
                      left_on='incident', right_on='id',
                      suffixes=['','_incident']).drop('incident', axis=1)
        it = it.merge(self.techniques[['id','name']], 
                      left_on='technique', right_on='id',
                      suffixes=['','_technique']).drop('technique', axis=1)
        return(it)
    def make_object_dict(self, df):
        return(pd.Series(df.name.values,index=df.id).to_dict())
    
    
    def create_display_grid(self, tofile=True):
        # Create the master grid that we make all the framework visuals from
        # cols = number of tactics
        # rows = max number of techniques per tactic + 2
        arr = [['' for i in range(self.ngridcols)] for j in range(self.ngridrows)] 
        for index, tactic in self.tactics.iterrows():
            arr[0][index] = tactic['phase']
            arr[1][index] = tactic['id']
            if tactic['techniques'] == '':
                continue
            for index2, technique in enumerate(tactic['techniques']):
                arr[index2+2][index] = technique
        #Save grid to file
        if tofile:
            matrixdir = '../matrices'
            if not os.path.exists(matrixdir):
                os.makedirs(matrixdir)
            pd.DataFrame(arr).to_csv(matrixdir + '/matrix_arr.csv', index=False, header=False)
        return(arr)
    def create_incidentstring(self, techniqueid):
        incidentstr = '''
| Incident | Descriptions given for this incident |
| -------- | -------------------- |
'''
        incirow = '| [{0} {1}](../incidents/{0}.md) | {2} |\n'
        its = self.it[self.it['id_technique']==techniqueid]
        for index, row in its[['id_incident', 'name_incident']].drop_duplicates().sort_values('id_incident').iterrows():
            techstring = ', '.join(its[its['id_incident']==row['id_incident']]['name'].to_list())
            incidentstr += incirow.format(row['id_incident'], row['name_incident'], techstring)
        return incidentstr
    def create_techstring(self, incidentid):
        techstr = '''
| Technique | Description given for this incident |
| --------- | ------------------------- |
'''
        techrow = '| [{0} {1}](../techniques/{0}.md) | {2} {3} |\n'
        techlist = self.it[self.it['id_incident'] == incidentid]
        for index, row in techlist.sort_values('id_technique').iterrows():
            techstr += techrow.format(row['id_technique'], row['name_technique'], 
                                      row['id'], row['name'])
        return techstr
    def create_taskstring(self, tacticid):
        taskstr = '''
| Task |
| ---- |
'''
        tasklist = self.tasks[self.tasks['tactic']==tacticid]
        taskrow = '| [{0} {1}](../tasks/{0}.md) |\n'
        for index, row in tasklist.sort_values('id').iterrows():
            taskstr += taskrow.format(row['id'], row['name'])
        return taskstr
    def create_techtacstring(self, tacticid):
        techstr = '''
| Technique |
| --------- |
'''
        techlist = self.techniques[self.techniques['tactic']==tacticid]
        techrow = '| [{0} {1}](../techniques/{0}.md) |\n'
        for index, row in techlist.sort_values('id').iterrows():
            techstr += techrow.format(row['id'], row['name'])
        return techstr
    def generate_datasheets(self):
        # Generate datafiles
        warntext = 'DO NOT EDIT ABOVE THIS LINE - PLEASE ADD NOTES BELOW'
        warnlen = len(warntext)
        
        metadata = {
            'phase': self.phases,
            'tactic': self.tactics,
            'technique': self.techniques,
            'task': self.tasks,
            'incident': self.incidents
        }
        
        for entity, df in metadata.items():
            entities = entity + 's'
            entitydir = '../{}'.format(entities)
            if not os.path.exists(entitydir):
                os.makedirs(entitydir)
            template = open('template_{}.md'.format(entity)).read()
            for index, row in df[df['name'].notnull()].iterrows():
                # First read in the file - if it exists - and grab everything 
                # below the "do not write about this line". Will write this 
                # out below new metadata. 
                datafile = '../{}/{}.md'.format(entities, row['id'])
                oldmetatext = ''
                if os.path.exists(datafile):
                    with open(datafile) as f:
                        filetext = f.read()
                    warnpos = filetext.find(warntext)
                    if warnpos == -1:
                        print('no warning text found in {}: adding to file'.format(datafile))
                        usertext = filetext
                    else:
                        oldmetatext = filetext[:warnpos+warnlen]
                        usertext = filetext[warnpos+warnlen:]
                else:
                    usertext = ''
                # Now populate datafiles with new metadata plus old userdata
                if entity == 'phase':
                    metatext = template.format(id=row['id'], name=row['name'], summary=row['summary'])
                if entity == 'tactic':
                    metatext = template.format(id=row['id'], name=row['name'],
                                               phase=row['phase'], summary=row['summary'],
                                               tasks=self.create_taskstring(row['id']),
                                               techniques=self.create_techtacstring(row['id']))
                if entity == 'task':
                    metatext = template.format(id=row['id'], name=row['name'],
                                               tactic=row['tactic'], summary=row['summary'])
                if entity == 'technique':
                    metatext = template.format(id=row['id'], name=row['name'],
                                               tactic=row['tactic'], summary=row['summary'],
                                               incidents=self.create_incidentstring(row['id']))
                if entity == 'incident':
                    metatext = template.format(id=row['id'], name=row['name'],
                                               type=row['type'], summary=row['summary'],
                                               yearstarted=row['Year Started'], 
                                               fromcountry=row['From country'],
                                               tocountry=row['To country'],
                                               foundvia=row['Found via'],
                                               dateadded=row['When added'],
                                               techniques=self.create_techstring(row['id']))
                # Make sure the user data goes in
                if (metatext + warntext) != oldmetatext:
                    print('Updating {}'.format(datafile))
                    with open(datafile, 'w') as f:
                        f.write(metatext)
                        f.write(warntext)
                        f.write(usertext)
                        f.close()
        return
    def write_grid_markdown(self, outfile = '../matrix.md'):
        # Write HTML version of framework diagram to markdown file
        # Needs phasedict, tacdict, techdict, grid
        html = '''# AMITT Latest Framework:
'''
        for col in range(self.ngridcols):
            html += '| {0} {1}\n'.format(
                self.grid[0][col], self.phasedict[self.grid[0][col]])
        html += ' | 
\n'
        html += '\n'
        for col in range(self.ngridcols):
            html += '| {0} {1}\n'.format(
                self.grid[1][col], self.tacdict[self.grid[1][col]])
        html += ' | 
\n\n'
        for row in range(2,self.ngridrows):
            for col in range(self.ngridcols):
                if self.grid[row][col] == '':
                    html += '| \n'
                else:
                    html += ' | {0} {1}\n'.format(
                        self.grid[row][col], self.techdict[self.grid[row][col]])
            html += ' | 
\n\n'
        html += '
\n
\n'
        with open(outfile, 'w') as f:
            f.write(html)
            print('updated {}'.format(outfile))
        return
    def write_incidentlist_markdown(self, outfile='../incidents.md'):
        # Write HTML version of incident list to markdown file
        html = '''# AMITT Incidents:
'''
        cols = ['name', 'type', 'Year Started', 'From country', 'To country',
                'Found via']
        html += '| {}\n'.format('id')
        for col in cols:
            html += ' | {}\n'.format(col)
        html += ' | 
\n'
        for index, row in self.incidents[self.incidents['name'].notnull()].iterrows():
            html += '\n'
            html += '| {0}\n'.format(row['id'])
            for col in cols:
                    html += ' | {}\n'.format(row[col])
            html += ' | 
\n'
        html += '
\n'
        with open(outfile, 'w') as f:
            f.write(html)
            print('updated {}'.format(outfile))
        return
    def write_grid_message_generator(self, outfile='../matrix_to_message.html'):
        # Write clickable html version of the matrix grid to html file
        html = '''
    AMITT
AMITT
\n
\n'
        html += '\n'.format(liststr)
        html += '''
'''
        with open(outfile, 'w') as f:
            f.write(html)
            print('updated {}'.format(outfile))
        return
        
    def print_technique_incidents(self):
        for id_technique in self.techniques['id'].to_list():
            print('{}\n{}'.format(id_technique, 
                                  self.create_incidentstring(id_technique)))
        return
    def print_incident_techniques(self):
        for id_incident in self.incidents['id'].to_list():
            print('{}\n{}'.format(id_incident, 
                                  self.create_techstring(id_incident)))
        return
    
    def generate_datafiles(self):
        
        self.generate_datasheets()
        self.write_grid_markdown()
        self.write_incidentlist_markdown()
        self.write_grid_message_generator()
        
        return
 
def main():
    amitt = Amitt()
    amitt.generate_datafiles()
if __name__ == "__main__":
    main()