#
# BSD 3-Clause License
#
# This file is part of the Basalt project.
# https://gitlab.com/VladyslavUsenko/basalt.git
#
# Copyright (c) 2019-2021, Vladyslav Usenko and Nikolaus Demmel.
# All rights reserved.
#
import os
from collections import Mapping

from .log import Log

from .util import load_json_if_exists
from .util import load_text_if_exists
from .util import load_trajectory_tum_if_exists


class Run:
    """Loads files from a single run of an experiment from a folder (config, status, output, log, ...)
    
    A single run is one invocation of odometry with a specific config on a specific sequence.
    This is meant to be used on directories created with the 'generate-batch-configs' and 'run-all-in' scripts.
    It's best-effort, loading as many of the files as are present.
    """

    def __init__(self, dirpath, seq_name_mapping):
        self.dirpath = dirpath

        self.config = load_json_if_exists(os.path.join(dirpath, 'basalt_config.json'))
        self.output = load_text_if_exists(os.path.join(dirpath, 'output.log'))
        self.status = load_text_if_exists(os.path.join(dirpath, 'status.log'))
        self.traj_est = load_trajectory_tum_if_exists(os.path.join(dirpath, 'trajectory.txt'))
        self.traj_gt = load_trajectory_tum_if_exists(os.path.join(dirpath, 'groundtruth.txt'))

        self.log = Log.load(dirpath)

        self.seq_name = self._infer_sequence_name(self.config, dirpath, seq_name_mapping)

        print("loaded {} from '{}'".format(self.seq_name, dirpath))

    def is_imu(self):
        return self.config.batch_run.args["use-imu"] in [1, "1", True, "true"]

    def is_failed(self):
        if self.log is None:
            return True
        else:
            return "Completed" not in self.status

    def failure_str(self):
        if not self.is_failed():
            return ""
        if self.output:
            if "Some of your processes may have been killed by the cgroup out-of-memory handler" in self.output:
                return "OOM"
            if "DUE TO TIME LIMIT" in self.output:
                return "OOT"
        return "x"

    @staticmethod
    def _infer_sequence_name(config, dirpath, name_mapping):
        """Tries to infer the sequence name from the config, or falls back to the parent folder name"""
        seq_name = ""
        try:
            type = config.batch_run.args["dataset-type"]
            path = config.batch_run.args["dataset-path"]
            seq_name = os.path.basename(path)
            if type == "euroc":
                if seq_name.startswith("dataset-"):
                    # assume tumvi
                    seq_name = seq_name.replace("dataset-", "tumvi-").split("_")[0]
                else:
                    # assume euroc
                    s = seq_name.split("_")
                    seq_name = "euroc{}{}".format(s[0], s[1])
            elif type == "kitti":
                # assume kitti
                seq_name = "kitti{}".format(seq_name)
        except:
            pass

        # Fallback to detecting the sequence name base on the last component of the parent folder. This is intended
        # to work for run folders created with the 'generate-batch-configs' script, assuming the sequence is the
        # last component in '_batch.combinations'.
        if seq_name == "":
            seq_name = os.path.basename(dirpath).split("_")[-1]

        # optionally remap the sequence name to something else as defined in the experiments config
        if isinstance(name_mapping, Mapping) and seq_name in name_mapping:
            seq_name = name_mapping[seq_name]

        return seq_name

    @staticmethod
    def is_run_dir(dirpath):
        """Returns True if the folder may be a run directory, based on the present files
        
        This is intended to be used for auto-detecting run directories in a file tree.
        """

        files = [
            'status.log',
            'slurm-output.log',
            'output.log',
            'stats_all.ubjson',
            'stats_all.json',
            'stats_sums.ubjson',
            'stats_sums.json',
            'stats_vio.ubjson',
            'stats_vio.json',
        ]
        for f in files:
            if os.path.isfile(os.path.join(dirpath, f)):
                return True
        return False
