"""
Main execution script.
"""
import argparse
import json
import os
import sys
from copy import copy

import moviepy.editor as mpy
from PIL import Image, ImageDraw
from moviepy.video.io.bindings import PIL_to_npimage
from tqdm import tqdm

from replayenhancer.DefaultCards \
    import SeriesChampion, SeriesStandings, StartingGrid
from replayenhancer.GTStandings import GTStandings
from replayenhancer.RaceData import RaceData
from replayenhancer.RaceResultsWithChange import RaceResultsWithChange
from replayenhancer.SeriesStandingsWithChange \
    import SeriesStandingsWithChange


def make_video(config_file, *, framerate=None, sync=False):
    configuration = json.load(open(config_file))
    try:
        race_data = RaceData(configuration['source_telemetry'])
        result_data = RaceData(configuration['source_telemetry'])
    except KeyError:
        sys.exit("Configuration Error: Source Telemetry not found.")

    try:
        output_prefix = os.path.splitext(configuration['output_video'])[0]
    except KeyError:
        sys.exit("Configuration Error: Output Video not found.")

    try:
        champion = configuration['show_champion']
    except KeyError:
        champion = False

    if os.environ.get('HEADINGFONTOVERRIDE') is not None:
        configuration['heading_font'] = \
            os.environ['HEADINGFONTOVERRIDE']
    if os.environ.get('DISPLAYFONTOVERRIDE') is not None:
        configuration['font'] = os.environ['DISPLAYFONTOVERRIDE']

    try:
        video_skipstart = configuration['video_skipstart']
    except KeyError:
        video_skipstart = 0

    try:
        video_skipend = configuration['video_skipend']
    except KeyError:
        video_skipend = None

    if 'source_video' in configuration \
            and configuration['source_video'] is not None:
        source_video = mpy.VideoFileClip(
            configuration['source_video']
        ).subclip(
            video_skipstart,
            video_skipend)
        if framerate is None:
            framerate = source_video.fps
    else:
        time_data = RaceData(configuration['source_telemetry'])
        with tqdm(desc="Detecting Telemetry Duration") as progress:
            while True:
                try:
                    _ = time_data.get_data()
                    progress.update()
                except StopIteration:
                    break
        source_video = mpy.ColorClip((1280, 1024)).set_duration(
            time_data.elapsed_time)

        if framerate is None:
            framerate = 30

    pcre_standings = GTStandings(
        race_data,
        ups=framerate,
        **configuration)
    standings_clip_mask = mpy.VideoClip(
        make_frame=pcre_standings.make_mask_frame,
        ismask=True)
    standings_clip = mpy.VideoClip(
        make_frame=pcre_standings.make_frame
    ).set_mask(standings_clip_mask)

    if sync:
        def timecode_frame(time):
            """
            Custom make frame for timecode.
            """
            timecode_image = Image.new('RGB', (100, 40))
            draw = ImageDraw.Draw(timecode_image)
            draw.text((10, 10), "%.02f" % time)
            return PIL_to_npimage(timecode_image)

        timecode_clip = mpy.VideoClip(
            timecode_frame,
            duration=source_video.duration
        ).set_position(('center', 'top'))

        first_lap_data = RaceData(configuration['source_telemetry'])
        with tqdm(desc="Detecting Video Start") as progress:
            while not any(
                    [x.laps_complete > 0
                     for x in first_lap_data.drivers.values()]):
                _ = first_lap_data.get_data()
                progress.update()

        start_time = first_lap_data.elapsed_time - 10
        end_time = None

        with tqdm(desc="Detecting Video End") as progress:
            while not all(
                    [x.laps_complete > 0
                     for x in first_lap_data.drivers.values()]):
                try:
                    _ = first_lap_data.get_data()
                    progress.update()
                except StopIteration:
                    end_time = start_time + 60
                    break

        if end_time is None:
            end_time = first_lap_data.elapsed_time + 10

        main_event = mpy.CompositeVideoClip(
            [source_video, standings_clip, timecode_clip]
        ).set_duration(
            source_video.duration
        ).subclip(start_time, end_time)

    else:
        main_event = mpy.CompositeVideoClip(
            [source_video, standings_clip]
        ).set_duration(source_video.duration)

    pcre_starting_grid = StartingGrid(
        sorted(race_data.starting_grid, key=lambda x: x.position),
        size=source_video.size,
        **configuration)
    Image.fromarray(pcre_starting_grid.to_frame()).save(
        output_prefix + "_starting_grid.png")
    starting_grid = mpy.ImageClip(
        pcre_starting_grid.to_frame()).set_duration(5)

    while True:
        try:
            result_data.get_data()
        except StopIteration:
            break

    end_titles = list()

    if 'car_classes' in configuration and len(configuration['car_classes']):
        no_points_config = copy(configuration)
        try:
            del no_points_config['point_structure']
        except KeyError:
            pass

        pcre_results = RaceResultsWithChange(
            sorted(result_data.all_driver_classification,
                   key=lambda x: x.position),
            result_data.starting_grid,
            size=source_video.size,
            **no_points_config)
        Image.fromarray(pcre_results.to_frame()).save(
            output_prefix + '_results.png')
        results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)

        end_titles.append(results)

        for car_class_filter in [
                car_class for car_class in configuration['car_classes']
                if car_class != ""]:
            class_cars = [
                car for car_class, car_class_data
                in configuration['car_classes'].items()
                if car_class == car_class_filter
                for car in car_class_data['cars']]
            class_drivers = [
                driver for driver, data
                in configuration['participant_config'].items()
                if data['car'] in class_cars]

            class_classification = [
                classification for classification
                in result_data.all_driver_classification
                if classification.driver_name in class_drivers]
            for position, classification in enumerate(sorted(
                    class_classification,
                    key=lambda x: x.position), 1):
                classification.position = position

            class_starting_grid = [
                grid for grid
                in result_data.starting_grid
                if grid.driver_name in class_drivers]
            for position, grid in enumerate(sorted(
                    class_starting_grid,
                    key=lambda x: x.position), 1):
                grid.position = position

            pcre_results = RaceResultsWithChange(
                sorted(class_classification, key=lambda x: x.position),
                class_starting_grid,
                size=source_video.size,
                **configuration)
            Image.fromarray(pcre_results.to_frame()).save(
                output_prefix + '_' + car_class_filter + '_results.png')
            results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)
            end_titles.append(results)
    else:
        pcre_results = RaceResultsWithChange(
            sorted(result_data.all_driver_classification,
                   key=lambda x: x.position),
            result_data.starting_grid,
            size=source_video.size,
            **configuration)
        Image.fromarray(pcre_results.to_frame()).save(
            output_prefix + '_results.png')
        results = mpy.ImageClip(pcre_results.to_frame()).set_duration(20)
        end_titles.append(results)

    try:
        if any(configuration['point_structure']):
            if not any([
                    x['points'] for x
                    in configuration['participant_config'].values()]):
                if 'car_classes' in configuration \
                        and len(configuration['car_classes']):
                    for car_class_filter in [car_class for car_class in
                                             configuration['car_classes'] if
                                             car_class != ""]:
                        class_cars = [
                            car for car_class, car_class_data
                            in configuration['car_classes'].items()
                            if car_class == car_class_filter
                            for car in car_class_data['cars']]
                        class_drivers = [
                            driver for driver, data
                            in configuration['participant_config'].items()
                            if data['car'] in class_cars]

                        class_classification = [
                            classification for classification
                            in result_data.all_driver_classification
                            if classification.driver_name in class_drivers]
                        for position, classification in enumerate(sorted(
                                class_classification,
                                key=lambda x: x.position), 1):
                            classification.position = position

                        pcre_series_standings = SeriesStandings(
                            class_classification,
                            size=source_video.size,
                            **configuration)
                        Image.fromarray(pcre_series_standings.to_frame()).save(
                            output_prefix
                            + '_'
                            + car_class_filter
                            + '_series_standings.png')
                        series_standings = mpy.ImageClip(
                            pcre_series_standings.to_frame()).set_duration(20)
                        end_titles.append(series_standings)
                else:
                    pcre_series_standings = SeriesStandings(
                        result_data.all_driver_classification,
                        size=source_video.size,
                        **configuration)
                    Image.fromarray(pcre_series_standings.to_frame()).save(
                        output_prefix + '_series_standings.png')
                    series_standings = mpy.ImageClip(
                        pcre_series_standings.to_frame()).set_duration(20)
                    end_titles.append(series_standings)
            else:
                if 'car_classes' in configuration \
                        and len(configuration['car_classes']):
                    for car_class_filter in [car_class for car_class in
                                             configuration['car_classes'] if
                                             car_class != ""]:
                        class_cars = [
                            car for car_class, car_class_data
                            in configuration['car_classes'].items()
                            if car_class == car_class_filter
                            for car in car_class_data['cars']]
                        class_drivers = [
                            driver for driver, data
                            in configuration['participant_config'].items()
                            if data['car'] in class_cars]

                        class_classification = [
                            classification for classification
                            in result_data.all_driver_classification
                            if classification.driver_name in class_drivers]
                        for position, classification in enumerate(sorted(
                                class_classification,
                                key=lambda x: x.position), 1):
                            classification.position = position

                        pcre_series_standings = SeriesStandingsWithChange(
                            class_classification,
                            size=source_video.size,
                            **configuration)
                        Image.fromarray(pcre_series_standings.to_frame()).save(
                            output_prefix
                            + '_'
                            + car_class_filter
                            + '_series_standings.png')
                        series_standings = mpy.ImageClip(
                            pcre_series_standings.to_frame()).set_duration(20)
                        end_titles.append(series_standings)

                else:
                    pcre_series_standings = SeriesStandingsWithChange(
                        result_data.all_driver_classification,
                        size=source_video.size,
                        **configuration)
                    Image.fromarray(pcre_series_standings.to_frame()).save(
                        output_prefix + '_series_standings.png')
                    series_standings = mpy.ImageClip(
                        pcre_series_standings.to_frame()).set_duration(20)
                    end_titles.append(series_standings)
    except KeyError:
        try:
            _ = configuration['point_structure']
            pcre_series_standings = SeriesStandings(
                result_data.all_driver_classification,
                size=source_video.size,
                **configuration)

            Image.fromarray(pcre_series_standings.to_frame()).save(
                output_prefix + '_series_standings.png')
            series_standings = mpy.ImageClip(
                pcre_series_standings.to_frame()).set_duration(20)

            end_titles.append(series_standings)
        except KeyError:
            pass

    if champion:
        if 'car_classes' in configuration and len(configuration['car_classes']):
            for car_class_filter in [car_class for car_class in
                                     configuration['car_classes'] if
                                     car_class != ""]:
                class_cars = [
                    car for car_class, car_class_data
                    in configuration['car_classes'].items()
                    if car_class == car_class_filter
                    for car in car_class_data['cars']]
                class_drivers = [
                    driver for driver, data
                    in configuration['participant_config'].items()
                    if data['car'] in class_cars]

                class_classification = [
                    classification for classification
                    in result_data.all_driver_classification
                    if classification.driver_name in class_drivers]
                for position, classification in enumerate(sorted(
                        class_classification,
                        key=lambda x: x.position), 1):
                    classification.position = position

                pcre_series_standings = SeriesChampion(
                    class_classification,
                    size=source_video.size,
                    **configuration)
                Image.fromarray(pcre_series_standings.to_frame()).save(
                    output_prefix
                    + '_'
                    + car_class_filter
                    + '_series_champion.png')
                series_standings = mpy.ImageClip(
                    pcre_series_standings.to_frame()).set_duration(20)
                end_titles.append(series_standings)
        else:
            pcre_series_champion = SeriesChampion(
                result_data.all_driver_classification,
                size=source_video.size,
                **configuration)
            Image.fromarray(pcre_series_champion.to_frame()).save(
                output_prefix + '_series_champion.png')
            series_champion = mpy.ImageClip(
                pcre_series_champion.to_frame()).set_duration(20)
            end_titles.append(series_champion)

    output = mpy.concatenate_videoclips(
        [starting_grid.fadeout(1), main_event]
        + [clip.fadein(1).fadeout(1) for clip in end_titles[:-1]]
        + [end_titles[-1].fadein(1)], method="compose")

    output.write_videofile(configuration['output_video'], fps=float(framerate))


def main():
    parser = argparse.ArgumentParser(
        description="Project CARS Replay Enhancer")

    parser.add_argument('config')

    parser.add_argument(
        '-f',
        '--framerate',
        action='store',
        default=None)

    parser.add_argument(
        '-s',
        '--sync',
        action='store_true')

    parser.add_argument(
        '-v',
        '--version',
        action='version',
        version='Version 0.6')

    parser.add_argument(
        '-p',
        '--profile',
        action='store_true',
        help=argparse.SUPPRESS)

    args = parser.parse_args()

    if args.profile:
        import cProfile
        from os.path import splitext, basename
        filename = splitext(basename(args.config))[0] + '.profile'
        
        cProfile.runctx(
            'make_video(args.config, framerate=args.framerate, sync=args.sync)',
            globals(),
            locals(),
            filename)
    else:
        make_video(
            args.config,
            framerate=args.framerate,
            sync=args.sync)

if __name__ == '__main__':
    main()
