import argparse
import json
import os
import shutil
import sys

def write_legacy_projectfile(args, simulation_code, att_resource, model_resource):
    """Writes legacy .smtkproject file for debug/test."""
    project_dict = dict(
        fileVersion = 1,
        projectDirectory = args.output_folder,
        projectName = os.path.basename(args.output_folder),
        resources=[],
        simulationCode=simulation_code
    )

    att_path = att_resource.location()
    att_resource_dict = dict(
        filename=os.path.basename(att_path),
        identifier='default',
        importLocation=args.template_filepath,
        typeName=att_resource.typeName(),
        uuid=str(att_resource.id())
    )
    project_dict['resources'].append(att_resource_dict)

    if model_resource is not None:
        model_path = model_resource.location()
        model_resource_dict = dict(
            filename=os.path.basename(model_path),
            identifier='default',
            importLocation=args.model_filepath,
            typeName=model_resource.typeName(),
            uuid=str(model_resource.id())
        )
        project_dict['resources'].append(model_resource_dict)

    project_filepath = os.path.join(args.output_folder, '.smtkproject')
    with open(project_filepath, 'w') as proj_fp:
        json.dump(project_dict, proj_fp, indent=2)
        proj_fp.write('\n')
        print(f'Wrote {project_filepath}')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Generate attribute resource from yml description')
    # Required arguments
    parser.add_argument('template_filepath', help='Attribute template filename/path (.sbt)')
    parser.add_argument('yml_filepath', help='YAML file specifying the attributes to generate')
    # Optional arguments
    parser.add_argument('-m', '--model_filepath', help='path to model file (.gen, .ncdf)')
    parser.add_argument('-o', '--output_folder', help='output folder for writing resource files')
    parser.add_argument('-s', '--smtktools_path', help='path to smtk_tools module')
    # Todo path to validation spec file
    # parser.add_argument('-s', '--skip_instances', action='store_true', help='skip initializing instanced attributes')

    args = parser.parse_args()
    # print(args)

    # Check filesystem paths
    if not os.path.exists(args.template_filepath):
        raise RuntimeError('template file not found: {}'.format(args.template_filepath))
    if args.model_filepath is not None and not os.path.exists(args.model_filepath):
        raise RuntimeError('model file not found: {}'.format(args.model_filepath))
    if not os.path.exists(args.yml_filepath):
        raise RuntimeError('yml spec not found: {}'.format(yml_filepath))
    if args.smtktools_path is not None and not os.path.exists(args.smtktools_path):
        raise RuntimeError('smtk_tools directory not found: {}'.format(args.smtktools_path))
    if args.output_folder is not None:
        if os.path.exists(args.output_folder):
            print('Deleting existing output folder', args.output_folder)
            shutil.rmtree(args.output_folder)
        os.makedirs(args.output_folder)

    # Add path to smtk_tools
    tools_path = args.smtktools_path
    if tools_path is None:
        # Default path from the repository
        source_path = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(source_path, os.pardir, os.pardir, 'thirdparty', 'smtk-tools')
        tools_path = os.path.normpath(path)

    assert os.path.exists(tools_path), 'smtk-tools path not found at {}'.format(tools_path)
    sys.path.insert(0, tools_path)

    import smtk_tools
    from smtk_tools.resource_io import ResourceIO
    from smtk_tools.attribute_builder import AttributeBuilder

    # Load yml file as the attribute specification
    import yaml
    spec = None
    print('Loading yaml file:', args.yml_filepath)
    with open(args.yml_filepath) as fp:
        content = fp.read()
        spec = yaml.safe_load(content)
    assert spec is not None

    # Initialize ResourceIO and load resources
    model_resource = None
    res_io = ResourceIO()
    if args.model_filepath is not None:
        # Support model or resource input
        basename, ext = os.path.splitext(args.model_filepath)
        if ext == '.smtk':
            print('Loading model resource:', args.model_filepath)
            model_resource = res_io.read_resource(args.model_filepath)
        else:
            print('Importing model file:', args.model_filepath)
            model_resource = res_io.import_resource(args.model_filepath)
        assert model_resource is not None, 'failed to load model file {}'.format(args.model_filepath)
    print('Importing attribute template', args.template_filepath)
    att_resource = res_io.import_resource(args.template_filepath)
    assert att_resource is not None, 'failed to import attribute template from {}'.format(args.template_filepath)

    # Associate the model resource
    if model_resource is not None:
        att_resource.associate(model_resource)

    # Initialize builder and populate the attributes
    builder = AttributeBuilder()
    builder.build_attributes(att_resource, spec, model_resource=model_resource)

    # Write the resource files
    if args.output_folder:
        att_path = os.path.join(args.output_folder, 'attributes.smtk')
        res_io.write_resource(att_resource, att_path)
        print('Wrote', att_path)

        # Only write model resource if it was imported
        if args.model_filepath is not None:
            basename, ext = os.path.splitext(args.model_filepath)
            if ext != '.smtk':
                model_path = os.path.join(args.output_folder, 'model.smtk')
                res_io.write_resource(model_resource, model_path)
                print('Wrote', model_path)

        # Write legacy project file for debug/test
        write_legacy_projectfile(args, 'ace3p', att_resource, model_resource)

        # Export the ACE3P input file
        basepath, ext = os.path.splitext(args.template_filepath)
        op_path = '{}.py'.format(basepath)
        if not os.path.exists(op_path):
            print('Export operation not found at {}'.format(op_path))
        export_op = res_io.import_python_operation(op_path)

        # Get categories from att_resource and set export spec
        analysis_att = att_resource.findAttribute('analysis')
        assert analysis_att is not None, 'attribute resource missing analysis attribute'
        sim_categories = att_resource.analyses().getAnalysisAttributeCategories(analysis_att)
        export_category = 'Rf-Postprocess' if 'Rf-Postprocess' in sim_categories else 'Simulation'
        export_op.specification().setActiveCategoriesEnabled(True)
        export_op.specification().setActiveCategories(set([export_category]))

        params_att = export_op.parameters()
        params_att.findVoid('TestMode').setIsEnabled(True)
        params_att.findResource('attributes').setValue(att_resource)
        params_att.findResource('model').setValue(model_resource)
        if args.model_filepath:
            params_att.findFile('MeshFile').setValue(args.model_filepath)
        params_att.findDirectory('OutputFolder').setValue(args.output_folder)

        # Use the yml file base as the output file prefix
        yml_filename = os.path.basename(args.yml_filepath)
        prefix, ext = os.path.splitext(yml_filename)
        params_att.findString('OutputFilePrefix').setValue(prefix)

        result = export_op.operate()
        outcome = result.findInt('outcome')
        OPERATION_SUCCEEDED = 3  # defined in smtk/operation/Operation.h
        assert outcome.value() == OPERATION_SUCCEEDED, \
            'export operation failed, outcome: {}'.format(outcome.value())

        # Check that expected file was created
        file_item = result.findFile('OutputFile')
        assert file_item is not None, 'Result missing OutputFile item'
        assert file_item.isSet(), 'Result OutputFile item not set'
        expected_path = file_item.value()
        print('output file:', expected_path)
        assert os.path.exists(expected_path), 'Output file not found at {}'.format(expected_path)

        # Dump out any warnings
        if export_op.log().numberOfRecords():
            print('EXPORT LOG RECORDS:')
            print(export_op.log().convertToString())

        # Check for baseline path to compare
        output_filename = os.path.basename(expected_path)
        yml_path = os.path.dirname(args.yml_filepath)
        baseline_filepath = os.path.join(yml_path, 'baseline', output_filename)
        if os.path.exists(baseline_filepath):
            print('Comparing output file with baseline')
            with open(baseline_filepath) as bf:
                baseline_lines = bf.readlines()
            with open(expected_path) as of:
                output_lines = of.readlines()

            import difflib
            diff = difflib.unified_diff(baseline_lines, output_lines, fromfile='baseline', tofile=output_filename, n=0)
            delta = [l for l in diff]
            if delta:
                print('Output file does not match baseline')
                print('Baseline file', baseline_filepath)
                print('Output file:', expected_path)
                sys.stderr.write('========== diff baseline output ==========\n')
                sys.stderr.writelines(delta)
                sys.stderr.write('==========================================\n')
                raise RuntimeError('Output file does NOT match baseline')
            else:
                print('Output file MATCHES baseline file')
