# Copyright (c) 2018, <NAME> All rights reserved.
# See the LICENSE in the main repository at:
#    https://www.github.com/openschemas/openschemas-python

from openschemas.main.map2model.file_manager import FolderDigger
from openschemas.main.map2model.mapping import MappingParser
from openschemas.main.map2model.validator import FolderValidator
from datetime import datetime
import time
import frontmatter
import os
import sys
from io import BytesIO

here = os.path.abspath(os.path.dirname(__file__))

class FrontMatterParser:
    '''the FrontEndMatterParser takes an input folder of specification
       subfolders, and a configuration.yml file, and generates a set of
       specification folders, each with a <Specification>.html file that
       can be contributed as a specification to the openschemas.github.io
       repository.
    '''

    def __init__(self, input_folder='specifications', 
                       output_folder = None,
                       config_file_path= None,
                       template = None,
                       repo = None):

        '''defaults here are intended for running in spec2map repository. 
           Use via run.py to edit for your needs.

           Parameters
           ==========
           input_folder: a folder of specification subfolders, each with .tsv
           output_folder: base to write matching output subfolders
           config_file_path: path to configuration.yml
           template.html is the template for openschemas.github.io
           repo: github <org>/<repository> for specifications 

        '''
        if config_file_path is None:
            config_file_path = '%s/configuration.yml' % here

        if template is None:
            template = '%s/templates/template.html' % here

        self.input_folder = self._check_input_folder(input_folder)
        self.template = self._check_input_folder(template)
        self.md_files_path = output_folder or 'docs/spec_files/'

        self.file_manager = FolderDigger(config_file_path)
        self.parser = MappingParser()
        self._parse_repo(repo)


    def _parse_repo(self, repo = None):
        '''parse an <ORG>/<REPO> into the full Github name, along with the
           username and reponame, and github pages. This should be for where
           the specification is to be published.

           Parameters
           ==========
           repo: should correspond to the <ORG>/<REPONAME>
        '''
        if repo is None:
            repo = 'openschemas/specifications'

        username, reponame = repo.split('/')
        self.repo = "https://www.github.com/%s" %(repo)
        self.username = username
        self.reponame = reponame
        self.ghpages = "https://%s.github.io/%s" %(username, reponame)

    def _check_input_folder(self, folder):
        '''check for the existence of the input folder, ensure full path,
           and return if exists. Exit if doesn't!
           
           Parameters
           ==========
           input_folder: path (relative or full) to input folder with specification
           subdirectories.
        '''
        folder = os.path.abspath(folder)
        if not os.path.exists(folder):
            print('Cannot find %s' % folder)
            sys.exit(1)
        print('Found folder %s' % folder)
        return folder        

    def _get_specs_list(self):
        '''return listing of specs, meaning loaded workbooks. The workbooks should
           already be validated by the file manager, and so we don't do it here.
           Each entry in the specs_list is a dictionary that includes paths to:
           mapping_file, bioschemas_file, specification_file, and authors_file.
        '''
        all_specs = dict()

        for name, params in self.specs_list.items():
            self.parser.set_metadata(params)
            all_specs[name] = self.parser.get_mapping()

        return all_specs

    def _get_specification_post(self, 
                                spec_dict, 
                                skip_fields=None,
                                info_fields=None,
                                info_key='spec_info'):
        '''from a spec_dict, derive the post material, a dictionary that
           will be converted to yaml.
 
           Parameters
           ==========
           spec_dict: a dictionary with:
               name: the name of the folder (and Specification)
               workbook: the loaded workbook (or file to it)
               params: the original values in the configuration.yml for the folder
           skip_fields: don't include subset of fields (list)
           info_fields: fields to add to "spec_info" (info_key)
           info_key: key to use for info_fields into metadata
        '''
        metadata = {}
        post = frontmatter.Post('')
        name = spec_dict['name']
        version_date = datetime.now().strftime('%Y%m%dT%H%M%S')
        info = {'version_date': version_date}

        # Skip over set of pre-defined fields
        if not skip_fields:
            skip_fields = []

        if not info_fields:
            info_fields = ['description',
                           'full_example',
                           'offical_type'
                           'subtitle',
                           'title',
                           'version',
                           "version_date"]

        # Fields specifically for openschemas template
        info['full_example'] = "%s/tree/master/%s/examples/" % (self.repo, name)

        if not isinstance(skip_fields, list):
            skip_fields = [skip_fields]

        for spec_field in spec_dict:

            # Info gets added to the metadata at the end under info_key
            if spec_field in info_fields:
                info[spec_field] = spec_dict[spec_field]
            if spec_field not in skip_fields:
                metadata[spec_field] = spec_dict[spec_field]

        metadata[info_key] = info
        post.metadata = metadata
        return post

    def _create_spec_folder_struct(self, spec_name):
        '''create a spec folder and subdirectory for examples for a 'spec_name'
           only if it doesn't exist.

           Parameters
           ==========
           spec_name: the name of the specification
        '''
        # Individual specification folder under "docs/spec_files"
        spec_dir = os.path.join(self.md_files_path, spec_name)

        # Create if doesn't exist
        if not os.path.exists(spec_dir):
            os.makedirs(spec_dir)

        # Equivalent for "examples" subfolder
        spec_exp_dir = os.path.join(spec_dir, 'examples')
        if not os.path.exists(spec_exp_dir):
            os.makedirs(spec_exp_dir)

            with open(os.path.join(spec_exp_dir, "README.md"), "w") as example_file:
                example_file.write("## %s coding examples. \n" % spec_name)
                example_file.write("Folder that stores JSON-LD, RDFa or microdata examples.\n")
                print("%s file structure created." % spec_name)

        # Either way, return the specification directory
        return spec_dir

    def _write_README(self, md_folder, spec_dict):
        '''write a README for a particular spec_md_folder
 
           Parameters
           ==========
           md_folder: a folder where a specification README should be written
           spec_dict: a dictionary with:
               name: the name of the folder (and Specification)
               workbook: the loaded workbook (or file to it)
               params: the original values in the configuration.yml for the folder
        '''
        md_file_path = os.path.join(md_folder, 'README.md')
        with open(md_file_path, "w") as readme:
 
            # Look up some fields
            name = spec_dict['name']
            version = spec_dict['version']
            spec_type = spec_dict['spec_type']
            hierarchy = spec_dict['hierarchy']
            description = spec_dict['description'] 
            subtitle = spec_dict['subtitle'].strip()

            readme.write("## %s specification v. %s \n\n" % (name, version))
            readme.write("**%s** \n\n" % spec_type)

            for i_pos, step_hier in enumerate(reversed(hierarchy)):
                readme.write(step_hier)
                if i_pos < len(hierarchy)-1:
                    readme.write(" > ")
            if spec_type == "Type":
                readme_file.write(" > %s" % name)
            readme.write("\n\n**%s** \n" % subtitle)
            readme.write("\n# Description \n")
            readme.write("%s \n" % description)
            readme.write("# Links \n")
            readme.write("- [Specification](%s/%s/)\n" % (self.ghpages, name))
            readme.write("- [Specification source](%s.html)\n" % name)
            readme.write("- [Coding Examples](%s/tree/master/examples)\n" % spec_dict['gh_folder'])
            readme.write("- [GitHUb Issues](%s)\n" % spec_dict['gh_tasks'])
            readme.write("> These files were generated using [map2model](https://github.com/openschemas/map2model) Python Module.")
        

    def save_html_template(self, data, output_name, template_file=None):
        '''save an html template, meaning a jekyll template with 
           {{OPENSCHEMAS_FRONTEND_MATTER}} to replace with front matter.
           If a template isn't defined, the default provided by the package
           is used

           Parameters
           ==========
           template_file: the jekyll template to use, provided if not defined
           output_name: the name for the output file, should end in .html
        '''
        if template_file is None:
            template_file = '%s/templates/template.html' % here

        if not output_name.endswith('.html'):
            output_name = "%s.html" % output_name

        md_fm_bytes = BytesIO()
        frontmatter.dump(data, md_fm_bytes)
        content =  str(md_fm_bytes.getvalue(), 'utf-8')

        # Read in the template, do replace
        with open(template_file, 'r') as tfile:
            template = tfile.read()
        template = template.replace('{{OPENSCHEMAS_FRONTEND_MATTER}}', content)

        # Write to file
        with open(output_name, 'w') as outfile:
            outfile.write(template)
        return output_name
    

    def parse_front_matter(self):
        '''the primary function to parse the provided front matter, the tsv
           files, and generate yml specifications from the templates
        '''
        
        # Dictionary of the entries in configuration.yml with folder name as index
        self.specs_list = self.file_manager.get_specification_list(self.input_folder)
        all_specs = self._get_specs_list()

        for spec_name, spec_dict in all_specs.items():

            # Prepare frontmatter post object with basic metadata
            post = self._get_specification_post(spec_dict)
            post.metadata['version'] = str(post.metadata['version'])
            spec_name = post.metadata['name']

            # Create folder structure (examples) and README.md
            spec_dir = self._create_spec_folder_struct(spec_name)
            self._write_README(spec_dir, spec_dict)

            # Write as output a yml and html file
            output_name =  os.path.join(spec_dir, '%s' % spec_name)
            self.save_html_template(post, output_name)
            self.file_manager.yml_config.save_yml(output_name, post.metadata)

            print('%s MarkDown file generated.' % spec_name)

        print('Generation Process Complete. Output files are in %s' % self.md_files_path)

# Copyright (c) 2018, <NAME> All rights reserved.
# See the LICENSE in the main repository at:
#    https://www.github.com/openschemas/openschemas-python

from openschemas.main.map2model.file_manager import FolderDigger
from openschemas.main.map2model.mapping import MappingParser
from openschemas.main.map2model.validator import FolderValidator
from datetime import datetime
import time
import frontmatter
import os
import sys
from io import BytesIO

here = os.path.abspath(os.path.dirname(__file__))

class FrontMatterParser:
    '''the FrontEndMatterParser takes an input folder of specification
       subfolders, and a configuration.yml file, and generates a set of
       specification folders, each with a <Specification>.html file that
       can be contributed as a specification to the openschemas.github.io
       repository.
    '''

    def __init__(self, input_folder='specifications', 
                       output_folder = None,
                       config_file_path= None,
                       template = None,
                       repo = None):

        '''defaults here are intended for running in spec2map repository. 
           Use via run.py to edit for your needs.

           Parameters
           ==========
           input_folder: a folder of specification subfolders, each with .tsv
           output_folder: base to write matching output subfolders
           config_file_path: path to configuration.yml
           template.html is the template for openschemas.github.io
           repo: github <org>/<repository> for specifications 

        '''
        if config_file_path is None:
            config_file_path = '%s/configuration.yml' % here

        if template is None:
            template = '%s/templates/template.html' % here

        self.input_folder = self._check_input_folder(input_folder)
        self.template = self._check_input_folder(template)
        self.md_files_path = output_folder or 'docs/spec_files/'

        self.file_manager = FolderDigger(config_file_path)
        self.parser = MappingParser()
        self._parse_repo(repo)


    def _parse_repo(self, repo = None):
        '''parse an <ORG>/<REPO> into the full Github name, along with the
           username and reponame, and github pages. This should be for where
           the specification is to be published.

           Parameters
           ==========
           repo: should correspond to the <ORG>/<REPONAME>
        '''
        if repo is None:
            repo = 'openschemas/specifications'

        username, reponame = repo.split('/')
        self.repo = "https://www.github.com/%s" %(repo)
        self.username = username
        self.reponame = reponame
        self.ghpages = "https://%s.github.io/%s" %(username, reponame)

    def _check_input_folder(self, folder):
        '''check for the existence of the input folder, ensure full path,
           and return if exists. Exit if doesn't!
           
           Parameters
           ==========
           input_folder: path (relative or full) to input folder with specification
           subdirectories.
        '''
        folder = os.path.abspath(folder)
        if not os.path.exists(folder):
            print('Cannot find %s' % folder)
            sys.exit(1)
        print('Found folder %s' % folder)
        return folder        

    def _get_specs_list(self):
        '''return listing of specs, meaning loaded workbooks. The workbooks should
           already be validated by the file manager, and so we don't do it here.
           Each entry in the specs_list is a dictionary that includes paths to:
           mapping_file, bioschemas_file, specification_file, and authors_file.
        '''
        all_specs = dict()

        for name, params in self.specs_list.items():
            self.parser.set_metadata(params)
            all_specs[name] = self.parser.get_mapping()

        return all_specs

    def _get_specification_post(self, 
                                spec_dict, 
                                skip_fields=None,
                                info_fields=None,
                                info_key='spec_info'):
        '''from a spec_dict, derive the post material, a dictionary that
           will be converted to yaml.
 
           Parameters
           ==========
           spec_dict: a dictionary with:
               name: the name of the folder (and Specification)
               workbook: the loaded workbook (or file to it)
               params: the original values in the configuration.yml for the folder
           skip_fields: don't include subset of fields (list)
           info_fields: fields to add to "spec_info" (info_key)
           info_key: key to use for info_fields into metadata
        '''
        metadata = {}
        post = frontmatter.Post('')
        name = spec_dict['name']
        version_date = datetime.now().strftime('%Y%m%dT%H%M%S')
        info = {'version_date': version_date}

        # Skip over set of pre-defined fields
        if not skip_fields:
            skip_fields = []

        if not info_fields:
            info_fields = ['description',
                           'full_example',
                           'offical_type'
                           'subtitle',
                           'title',
                           'version',
                           "version_date"]

        # Fields specifically for openschemas template
        info['full_example'] = "%s/tree/master/%s/examples/" % (self.repo, name)

        if not isinstance(skip_fields, list):
            skip_fields = [skip_fields]

        for spec_field in spec_dict:

            # Info gets added to the metadata at the end under info_key
            if spec_field in info_fields:
                info[spec_field] = spec_dict[spec_field]
            if spec_field not in skip_fields:
                metadata[spec_field] = spec_dict[spec_field]

        metadata[info_key] = info
        post.metadata = metadata
        return post

    def _create_spec_folder_struct(self, spec_name):
        '''create a spec folder and subdirectory for examples for a 'spec_name'
           only if it doesn't exist.

           Parameters
           ==========
           spec_name: the name of the specification
        '''
        # Individual specification folder under "docs/spec_files"
        spec_dir = os.path.join(self.md_files_path, spec_name)

        # Create if doesn't exist
        if not os.path.exists(spec_dir):
            os.makedirs(spec_dir)

        # Equivalent for "examples" subfolder
        spec_exp_dir = os.path.join(spec_dir, 'examples')
        if not os.path.exists(spec_exp_dir):
            os.makedirs(spec_exp_dir)

            with open(os.path.join(spec_exp_dir, "README.md"), "w") as example_file:
                example_file.write("## %s coding examples. \n" % spec_name)
                example_file.write("Folder that stores JSON-LD, RDFa or microdata examples.\n")
                print("%s file structure created." % spec_name)

        # Either way, return the specification directory
        return spec_dir

    def _write_README(self, md_folder, spec_dict):
        '''write a README for a particular spec_md_folder
 
           Parameters
           ==========
           md_folder: a folder where a specification README should be written
           spec_dict: a dictionary with:
               name: the name of the folder (and Specification)
               workbook: the loaded workbook (or file to it)
               params: the original values in the configuration.yml for the folder
        '''
        md_file_path = os.path.join(md_folder, 'README.md')
        with open(md_file_path, "w") as readme:
 
            # Look up some fields
            name = spec_dict['name']
            version = spec_dict['version']
            spec_type = spec_dict['spec_type']
            hierarchy = spec_dict['hierarchy']
            description = spec_dict['description'] 
            subtitle = spec_dict['subtitle'].strip()

            readme.write("## %s specification v. %s \n\n" % (name, version))
            readme.write("**%s** \n\n" % spec_type)

            for i_pos, step_hier in enumerate(reversed(hierarchy)):
                readme.write(step_hier)
                if i_pos < len(hierarchy)-1:
                    readme.write(" > ")
            if spec_type == "Type":
                readme_file.write(" > %s" % name)
            readme.write("\n\n**%s** \n" % subtitle)
            readme.write("\n# Description \n")
            readme.write("%s \n" % description)
            readme.write("# Links \n")
            readme.write("- [Specification](%s/%s/)\n" % (self.ghpages, name))
            readme.write("- [Specification source](%s.html)\n" % name)
            readme.write("- [Coding Examples](%s/tree/master/examples)\n" % spec_dict['gh_folder'])
            readme.write("- [GitHUb Issues](%s)\n" % spec_dict['gh_tasks'])
            readme.write("> These files were generated using [map2model](https://github.com/openschemas/map2model) Python Module.")
        

    def save_html_template(self, data, output_name, template_file=None):
        '''save an html template, meaning a jekyll template with 
           {{OPENSCHEMAS_FRONTEND_MATTER}} to replace with front matter.
           If a template isn't defined, the default provided by the package
           is used

           Parameters
           ==========
           template_file: the jekyll template to use, provided if not defined
           output_name: the name for the output file, should end in .html
        '''
        if template_file is None:
            template_file = '%s/templates/template.html' % here

        if not output_name.endswith('.html'):
            output_name = "%s.html" % output_name

        md_fm_bytes = BytesIO()
        frontmatter.dump(data, md_fm_bytes)
        content =  str(md_fm_bytes.getvalue(), 'utf-8')

        # Read in the template, do replace
        with open(template_file, 'r') as tfile:
            template = tfile.read()
        template = template.replace('{{OPENSCHEMAS_FRONTEND_MATTER}}', content)

        # Write to file
        with open(output_name, 'w') as outfile:
            outfile.write(template)
        return output_name
    

    def parse_front_matter(self):
        '''the primary function to parse the provided front matter, the tsv
           files, and generate yml specifications from the templates
        '''
        
        # Dictionary of the entries in configuration.yml with folder name as index
        self.specs_list = self.file_manager.get_specification_list(self.input_folder)
        all_specs = self._get_specs_list()

        for spec_name, spec_dict in all_specs.items():

            # Prepare frontmatter post object with basic metadata
            post = self._get_specification_post(spec_dict)
            post.metadata['version'] = str(post.metadata['version'])
            spec_name = post.metadata['name']

            # Create folder structure (examples) and README.md
            spec_dir = self._create_spec_folder_struct(spec_name)
            self._write_README(spec_dir, spec_dict)

            # Write as output a yml and html file
            output_name =  os.path.join(spec_dir, '%s' % spec_name)
            self.save_html_template(post, output_name)
            self.file_manager.yml_config.save_yml(output_name, post.metadata)

            print('%s MarkDown file generated.' % spec_name)

        print('Generation Process Complete. Output files are in %s' % self.md_files_path)

# Copyright (c) 2018, <NAME> All rights reserved. # See the LICENSE in the main repository at: #    https://www.github.com/openschemas/openschemas-python the FrontEndMatterParser takes an input folder of specification        subfolders, and a configuration.yml file, and generates a set of        specification folders, each with a <Specification>.html file that        can be contributed as a specification to the openschemas.github.io        repository. defaults here are intended for running in spec2map repository.             Use via run.py to edit for your needs.             Parameters            ==========            input_folder: a folder of specification subfolders, each with .tsv            output_folder: base to write matching output subfolders            config_file_path: path to configuration.yml            template.html is the template for openschemas.github.io            repo: github <org>/<repository> for specifications parse an <ORG>/<REPO> into the full Github name, along with the            username and reponame, and github pages. This should be for where            the specification is to be published.             Parameters            ==========            repo: should correspond to the <ORG>/<REPONAME> check for the existence of the input folder, ensure full path,            and return if exists. Exit if doesn't!                        Parameters            ==========            input_folder: path (relative or full) to input folder with specification            subdirectories. return listing of specs, meaning loaded workbooks. The workbooks should            already be validated by the file manager, and so we don't do it here.            Each entry in the specs_list is a dictionary that includes paths to:            mapping_file, bioschemas_file, specification_file, and authors_file. from a spec_dict, derive the post material, a dictionary that            will be converted to yaml.              Parameters            ==========            spec_dict: a dictionary with:                name: the name of the folder (and Specification)                workbook: the loaded workbook (or file to it)                params: the original values in the configuration.yml for the folder            skip_fields: don't include subset of fields (list)            info_fields: fields to add to "spec_info" (info_key)            info_key: key to use for info_fields into metadata # Skip over set of pre-defined fields # Fields specifically for openschemas template # Info gets added to the metadata at the end under info_key create a spec folder and subdirectory for examples for a 'spec_name'            only if it doesn't exist.             Parameters            ==========            spec_name: the name of the specification # Individual specification folder under "docs/spec_files" # Create if doesn't exist # Equivalent for "examples" subfolder # %s coding examples. \n" % spec_name) # Either way, return the specification directory write a README for a particular spec_md_folder              Parameters            ==========            md_folder: a folder where a specification README should be written            spec_dict: a dictionary with:                name: the name of the folder (and Specification)                workbook: the loaded workbook (or file to it)                params: the original values in the configuration.yml for the folder # Look up some fields # %s specification v. %s \n\n" % (name, version)) # Description \n") save an html template, meaning a jekyll template with             {{OPENSCHEMAS_FRONTEND_MATTER}} to replace with front matter.            If a template isn't defined, the default provided by the package            is used             Parameters            ==========            template_file: the jekyll template to use, provided if not defined            output_name: the name for the output file, should end in .html # Read in the template, do replace # Write to file the primary function to parse the provided front matter, the tsv            files, and generate yml specifications from the templates # Dictionary of the entries in configuration.yml with folder name as index # Prepare frontmatter post object with basic metadata # Create folder structure (examples) and README.md # Write as output a yml and html file