#!/usr/bin/env python
from __future__ import print_function

import argparse
import codecs
import datetime
import json
import sys
import types

# Python 2 compat
try:
    BrokenPipeError
except NameError:
    import socket
    BrokenPipeError = socket.error

import fitparse


def format_message(num, message, options):
    s = ["{}. {}".format(num, message.name)]
    if options.with_defs:
        s.append(' [{}]'.format(message.type))
    s.append('\n')

    if message.type == 'data':
        for field_data in message:
            s.append(' * {}: {}'.format(field_data.name, field_data.value))
            if field_data.units:
                s.append(' [{}]'.format(field_data.units))
            s.append('\n')

    s.append('\n')
    return "".join(s)


def parse_args(args=None):
    parser = argparse.ArgumentParser(
        description='Dump .FIT files to various formats',
        epilog='python-fitparse version %s' % fitparse.__version__,
    )
    parser.add_argument('-v', '--verbose', action='count', default=0)
    parser.add_argument(
        '-o', '--output', type=argparse.FileType(mode='w'), default="-",
        help='File to output data into (defaults to stdout)',
    )
    parser.add_argument(
        # TODO: csv
        '-t', '--type', choices=('readable', 'json'), default='readable',
        help='File type to output. (DEFAULT: %(default)s)',
    )
    parser.add_argument(
        '-n', '--name', action='append', help='Message name (or number) to filter',
    )
    parser.add_argument(
        'infile', metavar='FITFILE', type=argparse.FileType(mode='rb'),
        help='Input .FIT file (Use - for stdin)',
    )
    parser.add_argument(
        '--ignore-crc', action='store_const', const=True, help='Some devices seem to write invalid crc\'s, ignore these.'
    )

    options = parser.parse_args(args)

    # Work around argparse.FileType not accepting an `encoding` kwarg in
    # Python < 3.4 by closing and reopening the file (unless it's stdout)
    if options.output is not sys.stdout:
        options.output.close()
        options.output = codecs.open(options.output.name, 'w', encoding='UTF-8')

    options.verbose = options.verbose >= 1
    options.with_defs = (options.type == "readable" and options.verbose)
    options.as_dict = (options.type != "readable" and options.verbose)

    return options


class RecordJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, types.GeneratorType):
            return list(obj)
        if isinstance(obj, (datetime.datetime, datetime.time)):
            return obj.isoformat()
        if isinstance(obj, fitparse.DataMessage):
            return {
                "type": obj.name,
                "data": {
                    data.name: data.value for data in obj
                }
            }
        # Fall back to original to raise a TypeError
        return super(RecordJSONEncoder, self).default(obj)


def main(args=None):
    options = parse_args(args)

    fitfile = fitparse.FitFile(
        options.infile,
        data_processor=fitparse.StandardUnitsDataProcessor(),
        check_crc = not(options.ignore_crc),
    )
    records = fitfile.get_messages(
        name=options.name,
        with_definitions=options.with_defs,
        as_dict=options.as_dict
    )

    try:
        if options.type == "json":
            json.dump(records, fp=options.output, cls=RecordJSONEncoder)
        elif options.type == "readable":
            options.output.writelines(format_message(n, record, options)
                                      for n, record in enumerate(records, 1))
    finally:
        try:
            options.output.close()
        except IOError:
            pass

if __name__ == '__main__':
    try:
        main()
    except BrokenPipeError:
        pass
