#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Create REDCap csv report for a redcap project.

Created on November 5, 2013
Edited on February 22, 2017

@author: Benjamin Yvernault, Electrical Engineering, Vanderbilt University
'''

from __future__ import print_function

from builtins import filter
from builtins import str
from builtins import range

import os
import sys
import redcap

from dax.errors import XnatToolsError
import dax.xnat_tools_utils as utils


__copyright__ = 'Copyright 2013 Vanderbilt University. All Rights Reserved'
__exe__ = os.path.basename(__file__)
__author__ = 'byvernault'
__purpose__ = 'Create REDCap report for a redcap project.'
__logger__ = utils.setup_info_logger(__exe__)
__description__ = '''What is the script doing :
   *Extract data from REDCap as a csv file.

Examples:
   *Save the data in a csv file: Redcapreport -k KEY -c extract_redcap.csv
   *print the libraries name: Redcapreport -k KEY -L
   *print all fields name and label: Redcapreport -k KEY -F
   *Extract values for all record: Redcapreport -k KEY --all
   *Filter for specific project/subject/session/assessor type:
    Redcapreport -k KEY -p PID -s 109387 -e 109387_1,109387_2 -t \
FS,TRACULA_v1,dtiQA_v2
   *Extract for specific assessor: Redcapreport -k KEY -p PID -a \
PID-x-109387-x-109387_1-x-FS
   *Extract for specific libraries type: Redcapreport -k KEY -p PID \
-l library_name
   *Extract only the fields described in the txt file: Redcapreport \
-k KEY -x fields.txt
'''

DEFAULT_API_URL = 'https://redcap.vanderbilt.edu/api/'


def print_lib(redcap_proj):
    """
    Print all the libraries name in your REDCap project

    :param redcap_proj: REDCap project ID
    :return: None
    """
    all_forms = list()
    field_names, _ = redcap_proj.names_labels()
    field_name = None
    for field_name in field_names:
        try:
            field = filter(lambda x: x['field_name'] == field_name,
                           redcap_proj.metadata)[0]
            all_forms.append(field['form_name'])
        except IndexError as e:
            print('ERROR: IndexError when checking the libraries.')
            print(e)
            sys.exit()

    # print the forms
    unique_forms = set(all_forms)
    print('INFO: Printing REDCap libraries name')
    print('------------------------------------')
    for form in unique_forms:
        print(form)


def get_records(args, redcap_proj):
    """
    Method to get records from REDCap

    :param args: parser.parse_args()
    :param redcap_proj: REDCap project ID
    :return: list of records
    """
    list_records = utils.get_option_list(args.assessor)
    projects_list = utils.get_option_list(args.project)
    subjects_list = utils.get_option_list(args.subject)
    sessions_list = utils.get_option_list(args.session)
    proctypes_list = utils.get_option_list(args.proctype)

    if args.all:
        print('INFO: Export ALL records from redcap project...')
        record_list = redcap_proj.export_records(
            fields=[redcap_proj.def_field])
        list_records = [r[redcap_proj.def_field] for r in record_list]
    elif not list_records:
        list_records = list()
        print('INFO: Export SPECIFIC records from redcap project...')
        rc_fields = ['record_id', 'project_xnat', 'subject_xnat',
                     'experiment_xnat', 'process_name_xnat']
        rc_list = redcap_proj.export_records(fields=rc_fields)
        # Filter:
        rc_list = [x for x in rc_list if is_good_record(x, projects_list,
                                                        subjects_list,
                                                        sessions_list,
                                                        proctypes_list)]
        # Get list:
        list_records = [r['record_id'] for r in rc_list]
    return list_records


def is_good_record(obj_dict, projects_list, subjects_list, sessions_list,
                   proctypes_list):
    """
    Method to check if a record is requested by user meaning
     good project/subject/session/proctype

    :param obj_dict: record on REDCap to check
    :param projects_list: list of projects IDs from XNAT
    :param subjects_list: list of subjects labels from XNAT
    :param sessions_list: list of sessions labels from XNAT
    :param proctypes_list: list of proctypes from XNAT
    :return: True if record meet criteria, False otherwise
    """
    if projects_list and obj_dict['project_xnat'] not in projects_list:
        return False
    if subjects_list and obj_dict['subject_xnat'] not in subjects_list:
        return False
    if sessions_list and obj_dict['experiment_xnat'] not in sessions_list:
        return False
    if proctypes_list and obj_dict['process_name_xnat'] not in proctypes_list:
        return False
    return True


def extract_redcap_data(redcap_proj, records, forms, fields):
    """
    Method to get the information out of REDCap Project

    :param redcap_proj: REDCap project ID
    :param records: list of records return by redcap project
    :param forms: list of libraries on redcap
    :param fields: list of fields for libraries from redcap
    :return: csv string representing the data
    """
    if len(records) < 100:
        msg = 'INFO: Export data from REDCap for the %s records that need \
to be download...'
        print(msg % (str(len(records))))
        try:
            csv_data = redcap_proj.export_records(records=records,
                                                  forms=forms,
                                                  fields=fields,
                                                  format='csv')
            return True, csv_data
        except redcap.RedcapError as err:
            msg = 'Error from PyCap: %s'
            raise XnatToolsError(msg % err)
        except Exception:
            err = 'Connection to REDCap stopped.'
            raise XnatToolsError(err)
    else:
        return chunked_export()


def chunks(l, n):
    """
    Yield successive n-sized chunks from list l

    :param l: list to chunk
    :param n: size of each chunk
    :return: None
    """
    for i in range(0, len(l), n):
        yield l[i:i + n]


def chunked_export(redcap_proj, records, forms, fields, chunk_size=100):
    """
    Method to chunck the export of records into smaller batch of records
    (default 100).

    :param redcap_proj: REDCap project ID
    :param records: list of records return by redcap project
    :param forms: list of libraries on redcap
    :param fields: list of fields for libraries from redcap
    :param chunk_size: size for each chunk (default: 100)
    :return: None
    """
    msg = 'INFO: Extracting data from REDCap 100 by 100 records for the \
%s records that need to be download...'
    print(msg % (str(len(records))))
    try:
        response = []
        for index, record_chunk in enumerate(chunks(records, chunk_size)):
            print(' > index: {}x100'.format(str(index)))
            chunked_response = redcap_proj.export_records(
                records=record_chunk,
                forms=forms,
                fields=fields,
                format='csv')
            response.extend(chunked_response)
    except redcap.RedcapError:
        msg = "Chunked export failed for chunk_size={:d}".format(chunk_size)
        raise XnatToolsError(msg)

    return True, response


def run_redcapreport(args):
    """
    Main function for xnat check.xnat

    :param args: arguments parse by argparse
    """
    # variables:
    fields = utils.read_txt(args.txtfile)
    if args.libraries:
        forms = args.libraries.strip()\
                              .replace(' ', '_')\
                              .lower()\
                              .split(',')

    try:
        print('INFO: Loading REDCap project...')
        redcap_proj = redcap.Project(DEFAULT_API_URL, args.key)
    except redcap.RedcapError:
        err = 'Connection to REDCap failed. Check the key, options \
-k/--key passed to the executable'
        raise XnatToolsError(err)

    utils.print_separators()
    if args.names:
        fnames, flabels = redcap_proj.names_labels(True)
        print('INFO: Printing fields name and label')
        print('------------------------------------')
        for ind, fname in enumerate(fnames):
            print("%s : %s" % (fname, flabels[ind]))
        utils.print_separators()
    if args.lib:
        print_lib(redcap_proj)
        utils.print_separators()
    else:
        records = get_records(args)
        succeed, csv_data = extract_redcap_data(redcap_proj, records, forms,
                                                fields)

        if not succeed:
            err = 'No values to write. Failed extracting data from REDCap.'
            raise XnatToolsError(err)
        else:
            # write data
            utils.write_csv(csv_data, args.csvfile, __exe__)

    utils.print_end(__exe__)


def add_to_parser(parser):
    """
    Method to add arguments to default parser for xnat_tools in utils.

    :param parser: parser object
    :return: parser object with new arguments
    """
    from argparse import ArgumentParser, RawDescriptionHelpFormatter
    argp = ArgumentParser(prog=__exe__,
                          description=__description__,
                          formatter_class=RawDescriptionHelpFormatter)
    argp.add_argument("-k", "--key", dest="key", required=True,
                      help="API Token for REDCap project.")
    argp.add_argument("-c", "--csvfile", dest="csvfile", default=None,
                      help="csv file path where the report will be save.")
    _h = 'txt file path with per line, the name of the variable on REDCap \
you want to extract.'
    argp.add_argument("-x", "--txtfile", dest="txtfile", default=None, help=_h)
    _h = 'Extract values for processes for the projects chosen. E.G: \
project1,project2'
    argp.add_argument("-p", "--project", dest="project", default=None, help=_h)
    _h = "Extract values for processes for the subjects chosen. E.G: \
subject1,subject2"
    argp.add_argument("-s", "--subject", dest="subject", default=None, help=_h)
    _h = "Extract values for processes for the sessions chosen. E.G: \
session1,session2"
    argp.add_argument("-e", "--session", dest="session", default=None, help=_h)
    _h = "Extract values for processors chosen. E.G: processor1,processor2"
    argp.add_argument("-a", "--assessor", dest="assessor", default=None,
                      help=_h)
    _h = "Extract values for processes types chosen. E.G: fMRIQA,dtiQA"
    argp.add_argument("-t", "--proctype", dest="proctype", default=None,
                      help=_h)
    _h = "file path with each line one processor label. Extract values for \
processes types chosen."
    argp.add_argument("-f", "--procfile", dest="procfile", default=None,
                      help=_h)
    _h = "Extract values for only the libraries specify. Check the project \
for the libraries name. Switch spaces by '_' and everything lower case. E.G: \
dti_quality_assurance. By default: all libraries"
    argp.add_argument("-l", "--libraries", dest="libraries", default=None,
                      help=_h)
    argp.add_argument("-F", "--fields", dest="names", action="store_true",
                      help="Print all field names and labels")
    argp.add_argument("-L", "--printlib", dest="lib", action="store_true",
                      help="Print all libraries names for the project.")
    argp.add_argument("--all", dest="all", action="store_true",
                      help="Extract values for all records.")
    return argp


if __name__ == '__main__':
    utils.run_tool(__exe__, __description__, add_to_parser, __purpose__,
                   run_redcapreport)
