# -*- mode:python; coding:utf-8 -*-

# Copyright (c) 2020 IBM Corp. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Script to generate python models from oscal using datamodel-code-generator."""
import logging
import re
import sys
from pathlib import Path
from subprocess import CalledProcessError, check_call

from oscal_normalize import normalize_files

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())


def load_git():
    """Load git submodule for oscal."""
    # NOTE: this should only be done if the latest nist content is desired
    # otherwise trestle 1.0.0 should track the corresponding submodule it is bound to
    # nist source checkout is bound to tags/v1.0.0 for trestle 1.0.0
    logger.info('git add and update oscal modules')
    try:
        check_call('git submodule add https://github.com/usnistgov/OSCAL.git nist-source'.split())
    except CalledProcessError:
        # silently ignore already existing module
        pass
    # Add second module
    try:
        check_call('git submodule add https://github.com/usnistgov/oscal-content.git nist-content'.split())
    except CalledProcessError:
        # silently ignore already existing module
        pass
    try:
        check_call('git submodule update --init'.split())
    except CalledProcessError as error:
        logger.error(f'Error updating the oscal git submodule {error}')
    try:
        check_call('git submodule update --remote --merge'.split())
    except CalledProcessError as error:
        logger.error(f'Error updating the oscal git submodule {error}')


def generate_model(full_name, out_full_name):
    """Generate a single model with datamodel-codegen."""
    logger.info(f'generate python model with datamodel-codegen: {str(full_name)} -> {str(out_full_name)}')
    args = [
        'datamodel-codegen',
        '--disable-timestamp',
        '--disable-appending-item-suffix',
        '--use-schema-description',
        '--input-file-type',
        'jsonschema',
        '--input',
        str(full_name),
        '--base-class',
        'trestle.core.base_model.OscalBaseModel',
        '--output',
        str(out_full_name)
    ]
    try:
        check_call(args)
    except CalledProcessError as error:
        logger.error(f'Error calling datamodel-codegen for file {full_name} error {error}')


def generate_models():
    """
    Generate all models including 3rd party.

    IMPORTANT NOTE: This script will leave the temporary classes in oscal/tmp for reference, but they should be deleted
    when no longer needed because some of the operations in the build will seek all .py files, and will try to act
    on these temporary files - giving an error.  So be sure to delete the oscal/tmp directory when done generating
    new classes.
    """
    logger.info('generating models')
    out_dir = Path('trestle/oscal')
    out_dir.mkdir(exist_ok=True, parents=True)
    tmp_dir = out_dir / 'tmp'
    tmp_dir.mkdir(exist_ok=True, parents=True)
    out_init = out_dir / '__init__.py'
    out_init.touch(exist_ok=True)

    in_dir = Path('nist-source/json/schema')
    for full_name in in_dir.glob('oscal_*_schema.json'):
        if 'complete' in str(full_name):
            continue
        try:
            obj = re.search('oscal_(.+?)_schema.json', str(full_name)).group(1)
        except AttributeError:
            logger.error(f'Warning: filename did not parse properly: {full_name}')
            obj = None
            continue
        oscal_name = obj.replace('-', '_')
        out_fname = oscal_name + '.py'
        out_full_name = tmp_dir / out_fname
        generate_model(full_name, out_full_name)
    # all .py files are first generated into oscal/tmp to be normalized'
    normalize_files()


def main():
    """Load git and generate models."""
    # at this point should load_git() if latest oscal schemas are needed
    generate_models()
    logger.info('DONE')


if __name__ == '__main__':
    sys.exit(main())
