# Source code for binoculars

    from __future__ import print_function, with_statement, division

    import os

    def run(args):
        """Parameters
            args: string
                String as if typed in terminal. The string must consist
                of the location of the configuration file and the command
                for specifying the jobs that need to be processed.
                All additonal configuration file overides can be included

            Returns
            A tuple of binoculars spaces

            Examples:
            >>> space = binoculars.run('config.txt 10')
            >>> space[0]
            Axes (3 dimensions, 2848 points, 33.0 kB) {
                Axis qx (min=-0.01, max=0.0, res=0.01, count=2)
                Axis qy (min=-0.04, max=-0.01, res=0.01, count=4)
                Axis qz (min=0.48, max=4.03, res=0.01, count=356)
            }
        """

        import binoculars.main

        binoculars.util.register_python_executable(__file__)
        main = binoculars.main.Main.from_args(args.split(" "))

        if isinstance(main.result, binoculars.space.Multiverse):
            return main.result.spaces
        if isinstance(main.result, bool):
            filenames = main.dispatcher.config.destination.final_filenames()
            return tuple(binoculars.space.Space.fromfile(fn) for fn in filenames)

    def load(filename, key=None):
        """ Parameters
            filename: string
                Only hdf5 files are acceptable
            key: a tuple with slices in as much dimensions as the space is

            Returns
            A binoculars space

            Examples:
            >>> space = binoculars.load('test.hdf5')
            >>> space
            Axes (3 dimensions, 2848 points, 33.0 kB) {
                Axis qx (min=-0.01, max=0.0, res=0.01, count=2)
                Axis qy (min=-0.04, max=-0.01, res=0.01, count=4)
                Axis qz (min=0.48, max=4.03, res=0.01, count=356)
            }
        """
        import binoculars.space

        if os.path.exists(filename):
            return binoculars.space.Space.fromfile(filename, key=key)
        else:
            raise OSError(f"File '{filename}' does not exist")

    def save(filename, space):
        """
            Save a space to file

            Parameters
            filename: string
                filename to which the data is saved. '.txt', '.hdf5' are supported.
            space: binoculars space
                the space containing the data that needs to be saved

            Examples:
            >>> space
            Axes (3 dimensions, 2848 points, 33.0 kB) {
                Axis qx (min=-0.01, max=0.0, res=0.01, count=2)
                Axis qy (min=-0.04, max=-0.01, res=0.01, count=4)
                Axis qz (min=0.48, max=4.03, res=0.01, count=356)
            }
            >>> binoculars.save('test.hdf5', space)
        """

        import binoculars.space
        import binoculars.util

        if isinstance(space, binoculars.space.Space):
            ext = os.path.splitext(filename)[-1]
            if ext == ".txt":
                binoculars.util.space_to_txt(space, filename)
            elif ext == ".edf":
                binoculars.util.space_to_edf(space, filename)
            else:
                space.tofile(filename)
        else:
            raise TypeError(f"'{space!r}' is not a binoculars space")

    def plotspace(
        space,
        log=True,
        clipping=0.0,
        fit=None,
        norm=None,
        colorbar=True,
        labels=True,
        **plotopts
    ):
        """plots a space with the correct axes. The space can be either one
            or two dimensinal.

            Parameters
            space: binoculars space
                the space containing the data that needs to be plotted
            log: bool
                axes or colorscale logarithmic
            clipping: 0 < float < 1
                cuts a lowest and highst value on the color scale
            fit: numpy.ndarray

                same shape and the space. If one dimensional the fit will
                be overlayed.
            norm: matplotlib.colors
                object defining the colorscale
            colorbar: bool
                show or not show the colorbar
            labels: bool
                show or not show the labels
            plotopts: keyword arguments
                keywords that will be accepted by matplotlib.pyplot.plot
                or matplotlib.pyplot.imshow

            Examples:
            >>> space
            Axes (3 dimensions, 2848 points, 33.0 kB) {
                Axis qx (min=-0.01, max=0.0, res=0.01, count=2)
                Axis qy (min=-0.04, max=-0.01, res=0.01, count=4)
                Axis qz (min=0.48, max=4.03, res=0.01, count=356)
            }
            >>> binoculars.plotspace('test.hdf5')
        """

        import matplotlib.pyplot as pyplot
        import binoculars.plot
        import binoculars.space

        if isinstance(space, binoculars.space.Space):
            if space.dimension == 3:
                from mpl_toolkits.mplot3d import Axes3D  # noqa

                ax = pyplot.gcf().gca(projection="3d")
                return binoculars.plot.plot(
                    space,
                    pyplot.gcf(),
                    ax,
                    log=log,
                    clipping=clipping,
                    fit=None,
                    norm=norm,
                    colorbar=colorbar,
                    labels=labels,
                    **plotopts
                )
            if fit is not None and space.dimension == 2:
                ax = pyplot.gcf().add_subplot(121)
                binoculars.plot.plot(
                    space,
                    pyplot.gcf(),
                    ax,
                    log=log,
                    clipping=clipping,
                    fit=None,
                    norm=norm,
                    colorbar=colorbar,
                    labels=labels,
                    **plotopts
                )
                ax = pyplot.gcf().add_subplot(122)
                return binoculars.plot.plot(
                    space,
                    pyplot.gcf(),
                    ax,
                    log=log,
                    clipping=clipping,
                    fit=fit,
                    norm=norm,
                    colorbar=colorbar,
                    labels=labels,
                    **plotopts
                )
            else:
                return binoculars.plot.plot(
                    space,
                    pyplot.gcf(),
                    pyplot.gca(),
                    log=log,
                    clipping=clipping,
                    fit=fit,
                    norm=norm,
                    colorbar=colorbar,
                    labels=labels,
                    **plotopts
                )
        else:
            raise TypeError(f"'{space!r}' is not a binoculars space")

    def transform(space, labels, resolutions, exprs):
        """transformation of the coordinates.

            Parameters
            space: binoculars space
            labels: list
                a list of length N with the labels
            resolutions: list
                a list of length N with the resolution per label
            exprs: list
                a list of length N with strings containing the expressions
                that will be evaluated.  all numpy funtions can be called
                without adding 'numpy.' to the functions.
            Returns
            A binoculars space of dimension N with labels and resolutions
            specified in the input

            Examples:
            >>> space = binoculars.load('test.hdf5')
            >>> space
            Axes (3 dimensions, 2848 points, 33.0 kB) {
                Axis qx (min=-0.01, max=0.0, res=0.01, count=2)
                Axis qy (min=-0.04, max=-0.01, res=0.01, count=4)
                Axis qz (min=0.48, max=4.03, res=0.01, count=356)
            }
            >>> newspace = binoculars.transform(space, ['twotheta'], [0.003], ['2 * arcsin(0.51 * (sqrt(qx**2 + qy**2 + qz**2) / (4 * pi)) / (pi * 180))'])  # noqa
            >>> newspace
            Axes (1 dimensions, 152 points, 1.0 kB) {
                Axis twotheta (min=0.066, max=0.519, res=0.003, count=152)
            }

        """
        import binoculars.util
        import binoculars.space

        if isinstance(space, binoculars.space.Space):
            transformation = \
                binoculars.util.transformation_from_expressions(space, exprs)
            newspace = \
                space.transform_coordinates(resolutions, labels, transformation)
        else:
            raise TypeError(f"'{space!r}' is not a binoculars space")
        return newspace

    def fitspace(space, function, guess=None):
        """
            fit the space data.

            Parameters
            space: binoculars space
            function: list
                a string with the name of the desired function. supported are:
                lorentzian (automatically selects 1d or 2d), gaussian1d and voigt1d
            guess: list
                a list of length N with the resolution per label

            Returns
            A binoculars fit object.

            Examples:
            >>> fit = binoculars.fitspace(space, 'lorentzian')
            >>> print(fit.summary)
                I: 1.081e-07 +/- inf
                loc: 0.3703 +/- inf
                gamma: 0.02383 +/- inf
                slope: 0.004559 +/- inf
                offset: -0.001888 +/- inf
            >>> parameters = fit.parameters
            >>> data = fit.fitdata
            >>> binoculars.plotspace(space, fit = data)
        """

        import binoculars.fit

        if isinstance(space, binoculars.space.Space):
            fitclass = binoculars.fit.get_class_by_name(function)
            return fitclass(space, guess)
        else:
            raise TypeError(f"'{space!r}' is not a binoculars space")

    def info(filename):
        """
            Explore the file without loading the file, or after loading the file

            Parameters
            filename: filename or space

            Examples:
            >>> print binoculars.info('test.hdf5')
            Axes (3 dimensions, 46466628 points, 531.0 MB) {
                Axis H (min=-0.1184, max=0.0632, res=0.0008, count=228)
                Axis K (min=-1.1184, max=-0.9136, res=0.0008, count=257)
                Axis L (min=0.125, max=4.085, res=0.005, count=793)
            }
            ConfigFile{
               [dispatcher]
               [projection]
               [input]
            }
            origin = test.hdf5
            >>> space = binoculars.load('test.hdf5')
            >>> print binoculars.info(space)
            Axes (3 dimensions, 46466628 points, 531.0 MB) {
                Axis H (min=-0.1184, max=0.0632, res=0.0008, count=228)
                Axis K (min=-1.1184, max=-0.9136, res=0.0008, count=257)
                Axis L (min=0.125, max=4.085, res=0.005, count=793)
            }
            ConfigFile{
               [dispatcher]
               [projection]
               [input]
            }
            origin = test.hdf5

        """

        import binoculars.space

        ret = ""
        if isinstance(filename, binoculars.space.Space):
            ret += f"{filename!r}\n{filename.config!r}"
        elif isinstance(filename, str):
            if os.path.exists(filename):
                try:
                    axes = binoculars.space.Axes.fromfile(filename)
                except Exception as e:
                    raise OSError(
                        f"{filename}: unable to load Space: {e!r}"
                    )
                ret += f"{axes!r}\n"
                try:
                    config = binoculars.util.ConfigFile.fromfile(filename)
                except Exception as e:
                    raise OSError(
                        f"{filename}: unable to load util.ConfigFile: {e!r}"
                    )
                ret += f"{config!r}"
            else:
                raise OSError(f"File '{filename}' does not exist")
        return ret