import itertools
import struct
from functools import reduce
from _collections import defaultdict

import Orange
import numpy as np
import spectral.io.envi
from Orange.data import \
    ContinuousVariable, StringVariable, TimeVariable, Domain, Table
from Orange.data.io import FileFormat
import Orange.data.io
from scipy.interpolate import interp1d
from scipy.io import matlab
import numbers

from .pymca5 import OmnicMap
from .agilent import agilentImage, agilentMosaic

class SpectralFileFormat:

    def read_spectra(self):
        """ Fast reading of spectra. Return spectral information
        in two arrays (wavelengths and values). Only additional
        attributes (usually metas) are returned as a Table.

        Return a triplet:
            - 1D numpy array,
            - 2D numpy array with the same last dimension as xs,
            - Orange.data.Table with only meta or class attributes
        """
        pass

    def read(self):
        domvals, data, additional_table = self.read_spectra()
        data = np.asarray(data, dtype=np.float64)  # Orange assumes X to be float64
        features = [Orange.data.ContinuousVariable.make("%f" % f) for f in domvals]
        if additional_table is None:
            domain = Orange.data.Domain(features, None)
            return Orange.data.Table(domain, data)
        else:
            domain = Orange.data.Domain(features,
                                        class_vars=additional_table.domain.class_vars,
                                        metas=additional_table.domain.metas)
            ret_data = additional_table.transform(domain)
            ret_data.X = data
            return ret_data

class DatReader(FileFormat):
    """ Reader for files with multiple columns of numbers. The first column
    contains the wavelengths, the others contain the spectra. """
    EXTENSIONS = ('.dat', '.dpt', '.xy',)
    DESCRIPTION = 'Spectra ASCII'

    def read(self):
        tbl = np.loadtxt(self.filename, ndmin=2)
        domvals = tbl.T[0]  # first column is attribute name
        from orangecontrib.spectroscopy.preprocess import features_with_interpolation
        domain = Orange.data.Domain(features_with_interpolation(domvals), None)
        datavals = tbl.T[1:]
        return Orange.data.Table(domain, datavals)

    @staticmethod
    def write_file(filename, data):
        xs = getx(data)
        xs = xs.reshape((-1, 1))
        table = np.hstack((xs, data.X.T))
        np.savetxt(filename, table, delimiter="\t", fmt="%g")

class AsciiMapReader(FileFormat):
    """ Reader ascii map files.

    First row contains wavelengths, then each row describes a spectrum, starting with (x, y)
    coordinates: http://www.cytospec.com/file.php#FileASCII3 """
    EXTENSIONS = ('.xyz',)
    DESCRIPTION = 'Hyperspectral map ASCII'

    def read(self):
        with open(self.filename, "rb") as f:
            # read first row separately because of two empty columns
            header = f.readline().decode("ascii").rstrip().split("\t")
            header = [a.strip() for a in header]
            assert header[0] == header[1] == ""
            dom_vals = [float(v) for v in header[2:]]
            from orangecontrib.spectroscopy.preprocess import features_with_interpolation
            domain = Orange.data.Domain(features_with_interpolation(dom_vals), None)
            tbl = np.loadtxt(f, ndmin=2)
            data = Orange.data.Table(domain, tbl[:, 2:])
            metas = [ContinuousVariable.make('map_x'), ContinuousVariable.make('map_y')]
            domain = Orange.data.Domain(domain.attributes, None, metas=metas)
            data = data.transform(domain)
            data[:, metas[0]] = tbl[:, 0].reshape(-1, 1)
            data[:, metas[1]] = tbl[:, 1].reshape(-1, 1)
            return data

    @staticmethod
    def write_file(filename, data):
        wavelengths = getx(data)
        try:
            ndom = Domain([data.domain["map_x"], data.domain["map_y"]] + list(data.domain.attributes))
        except KeyError:
            raise RuntimeError('Data needs to include meta variables "map_x" and "map_y"')
        data = data.transform(ndom)
        with open(filename, "wb") as f:
            header = ["", ""] + [("%g" % w) for w in wavelengths]
            f.write(('\t'.join(header) + '\n').encode("ascii"))
            np.savetxt(f, data.X, delimiter="\t", fmt="%g")

def _spectra_from_image(X, features, x_locs, y_locs):
    """
    Create a spectral format (returned by SpectralFileFormat.read_spectra)
    from 3D image organized [ rows, columns, wavelengths ]
    """
    X = np.asarray(X)
    x_locs = np.asarray(x_locs)
    y_locs = np.asarray(y_locs)

    # each spectrum has its own row
    spectra = X.reshape((X.shape[0]*X.shape[1], X.shape[2]))

    # locations
    y_loc = np.repeat(np.arange(X.shape[0]), X.shape[1])
    x_loc = np.tile(np.arange(X.shape[1]), X.shape[0])
    metas = np.array([x_locs[x_loc], y_locs[y_loc]]).T

    domain = Orange.data.Domain([], None,
                                metas=[Orange.data.ContinuousVariable.make("map_x"),
                                       Orange.data.ContinuousVariable.make("map_y")]
                                )
    data = Orange.data.Table.from_numpy(domain, X=np.zeros((len(spectra), 0)),
                                        metas=np.asarray(metas, dtype=object))
    return features, spectra, data

class MatlabReader(FileFormat):
    EXTENSIONS = ('.mat',)
    DESCRIPTION = "Matlab"

    # Matlab 7.3+ files are not handled by scipy reader

    def read(self):
        who = matlab.whosmat(self.filename)
        if not who:
            raise IOError("Couldn't load matlab file " + self.filename)
        else:
            ml = matlab.loadmat(self.filename, chars_as_strings=True)

            ml = {a: b for a, b in ml.items() if isinstance(b, np.ndarray)}

            # X is the biggest numeric array
            numarrays = []
            for name, con in ml.items():
                 if issubclass(con.dtype.type, numbers.Number):
                    numarrays.append((name, reduce(lambda x, y: x*y, con.shape, 1)))
            X = None
            if numarrays:
                nameX = max(numarrays, key=lambda x: x[1])[0]
                X = ml.pop(nameX)

            # find an array with compatible shapes
            attributes = []
            if X is not None:
                nameattributes = None
                for name, con in ml.items():
                    if con.shape in [(X.shape[1],), (1, X.shape[1])]:
                        nameattributes = name
                        break
                attributenames = ml.pop(nameattributes).ravel() if nameattributes else range(X.shape[1])
                attributenames = [str(a).strip() for a in attributenames]  # strip because of numpy char array
                attributes = [ContinuousVariable.make(a) for a in attributenames]

            metas = []
            metaattributes = []

            sizemetas = None
            if X is None:
                counts = defaultdict(list)
                for name, con in ml.items():
                    counts[len(con)].append(name)
                if counts:
                    sizemetas = max(counts.keys(), key=lambda x: len(counts[x]))
            else:
                sizemetas = len(X)
            if sizemetas:
                for name, con in ml.items():
                    if len(con) == sizemetas:
                        metas.append(name)

            metadata = []
            for m in sorted(metas):
                f = ml[m]
                metaattributes.append(StringVariable.make(m))
                f.resize(sizemetas, 1)
                metadata.append(f)

            metadata = np.hstack(tuple(metadata))

            domain = Domain(attributes, metas=metaattributes)
            if X is None:
                X = np.zeros((sizemetas, 0))
            return Orange.data.Table.from_numpy(domain, X, Y=None, metas=metadata)

class EnviMapReader(FileFormat, SpectralFileFormat):
    EXTENSIONS = ('.hdr',)
    DESCRIPTION = 'Envi'

    def read_spectra(self):
        a = spectral.io.envi.open(self.filename)
        X = np.array(a.load())
        try:
            lv = a.metadata["wavelength"]
            features = np.array(list(map(float, lv)))
        except KeyError:
            #just start counting from 0 when nothing is known
            features = np.arange(X.shape[-1])

        x_locs = np.arange(X.shape[1])
        y_locs = np.arange(X.shape[0])

        return _spectra_from_image(X, features, x_locs, y_locs)

class HDF5Reader_HERMES(FileFormat, SpectralFileFormat):
    """ A very case specific reader for HDF5 files from the HEREMES beamline in SOLEIL"""
    EXTENSIONS = ('.hdf5',)
    DESCRIPTION = 'HDF5 file @HERMRES/SOLEIL'

    def read_spectra(self):
        import h5py
        hdf5_file = h5py.File(self.filename)
        if hdf5_file['entry1/collection/beamline'].value.astype('str') == 'Hermes':
            x_locs = np.array(hdf5_file['entry1/Counter0/sample_x'])
            y_locs = np.array(hdf5_file['entry1/Counter0/sample_y'])
            energy = np.array(hdf5_file['entry1/Counter0/energy'])
            intensities = np.array(hdf5_file['entry1/Counter0/data']).T
        return _spectra_from_image(intensities, energy, x_locs, y_locs)

class OmnicMapReader(FileFormat, SpectralFileFormat):
    """ Reader for files with two columns of numbers (X and Y)"""
    EXTENSIONS = ('.map',)
    DESCRIPTION = 'Omnic map'

    def read_spectra(self):
        om = OmnicMap.OmnicMap(self.filename)
        info = om.info
        X = om.data

        try:
            lv = info['OmnicInfo']['Last X value']
            fv = info['OmnicInfo']['First X value']
            features = np.linspace(fv, lv, num=X.shape[-1])
        except KeyError:
            #just start counting from 0 when nothing is known
            features = np.arange(X.shape[-1])

        try:
            loc_first = info['OmnicInfo']["First map location"]
            loc_last = info['OmnicInfo']["Last map location"]
            x_locs = np.linspace(min(loc_first[0], loc_last[0]),
                                 max(loc_first[0], loc_last[0]), X.shape[1])
            y_locs = np.linspace(min(loc_first[1], loc_last[1]),
                                 max(loc_first[1], loc_last[1]), X.shape[0])
        except KeyError:
            x_locs = np.arange(X.shape[1])
            y_locs = np.arange(X.shape[0])

        return _spectra_from_image(X, features, x_locs, y_locs)

class AgilentImageReader(FileFormat, SpectralFileFormat):
    """ Reader for Agilent FPA single tile image files"""
    EXTENSIONS = ('.seq',)
    DESCRIPTION = 'Agilent Single Tile Image'

    def read_spectra(self):
        ai = agilentImage(self.filename)
        info = ai.info
        X = ai.data

        try:
            features = info['wavenumbers']
        except KeyError:
            #just start counting from 0 when nothing is known
            features = np.arange(X.shape[-1])

        try:
            px_size = info['FPA Pixel Size'] * info['PixelAggregationSize']
        except KeyError:
            # Use pixel units if FPA Pixel Size is not known
            px_size = 1
        x_locs = np.linspace(0, X.shape[1]*px_size, num=X.shape[1], endpoint=False)
        y_locs = np.linspace(0, X.shape[0]*px_size, num=X.shape[0], endpoint=False)

        return _spectra_from_image(X, features, x_locs, y_locs)

class agilentMosaicReader(FileFormat, SpectralFileFormat):
    """ Reader for Agilent FPA mosaic image files"""
    EXTENSIONS = ('.dms',)
    DESCRIPTION = 'Agilent Mosaic Image'

    def read_spectra(self):
        am = agilentMosaic(self.filename)
        info = am.info
        X = am.data

        try:
            features = info['wavenumbers']
        except KeyError:
            #just start counting from 0 when nothing is known
            features = np.arange(X.shape[-1])

        try:
            px_size = info['FPA Pixel Size'] * info['PixelAggregationSize']
        except KeyError:
            # Use pixel units if FPA Pixel Size is not known
            px_size = 1
        x_locs = np.linspace(0, X.shape[1]*px_size, num=X.shape[1], endpoint=False)
        y_locs = np.linspace(0, X.shape[0]*px_size, num=X.shape[0], endpoint=False)

        return _spectra_from_image(X, features, x_locs, y_locs)

class SPCReader(FileFormat):
    EXTENSIONS = ('.spc', '.SPC',)
    DESCRIPTION = 'Galactic SPC format'

    def read(self):
        try:
            import spc
        except ImportError:
            raise RuntimeError("To load spc files install spc python module (https://github.com/rohanisaac/spc)")

        spc_file = spc.File(self.filename)
        if spc_file.talabs:
            table = self.multi_x_reader(spc_file)
        else:
            table = self.single_x_reader(spc_file)
        return table

    def single_x_reader(self, spc_file):
        domvals = spc_file.x  # first column is attribute name
        domain = Orange.data.Domain([Orange.data.ContinuousVariable.make("%f" % f) for f in domvals], None)
        y_data = [sub.y for sub in spc_file.sub]
        y_data = np.array(y_data)
        table = Orange.data.Table.from_numpy(domain, y_data.astype(float, order='C'))
        return table

    def multi_x_reader(self, spc_file):
        # use x-values as domain
        all_x = []
        for sub in spc_file.sub:
            x = sub.x
            # assume values in x do not repeat
            all_x = np.union1d(all_x, x)
        domain = Orange.data.Domain([Orange.data.ContinuousVariable.make("%f" % f) for f in all_x], None)

        instances = []
        for sub in spc_file.sub:
            x, y = sub.x, sub.y
            newinstance = np.ones(len(all_x))*np.nan
            ss = np.searchsorted(all_x, x)  # find positions to set
            newinstance[ss] = y
            instances.append(newinstance)

        y_data = np.array(instances).astype(float, order='C')
        return Orange.data.Table.from_numpy(domain, y_data)

class OPUSReader(FileFormat):
    """Reader for OPUS files"""

    EXTENSIONS = (".0*", ".1*", ".2*", ".3*", ".4*", ".5*", ".6*", ".7*", ".8*", ".9*")
    DESCRIPTION = 'OPUS Spectrum'

    @property
    def sheets(self):
        import opusFC
        dbs = []
        for db in opusFC.listContents(self.filename):
            dbs.append(db[0] + " " + db[1] + " " + db[2])
        return dbs

    def read(self):
        import opusFC

        if self.sheet:
            db = self.sheet
        else:
            db = self.sheets[0]

        db = tuple(db.split(" "))
        dim = db[1]

        try:
            data = opusFC.getOpusData(self.filename, db)
        except Exception:
            raise IOError("Couldn't load spectrum from " + self.filename)

        attrs, clses, metas = [], [], []

        attrs = [ContinuousVariable.make(repr(data.x[i]))
                    for i in range(data.x.shape[0])]

        y_data = None
        meta_data = None

        if type(data) == opusFC.MultiRegionDataReturn:
            y_data = []
            meta_data = []
            metas.extend([ContinuousVariable.make('map_x'),
                          ContinuousVariable.make('map_y'),
                          StringVariable.make('map_region'),
                          TimeVariable.make('start_time')])
            for region in data.regions:
                y_data.append(region.spectra)
                mapX = region.mapX
                mapY = region.mapY
                map_region = np.full_like(mapX, region.title, dtype=object)
                start_time = region.start_time
                meta_region = np.column_stack((mapX, mapY,
                                               map_region, start_time))
                meta_data.append(meta_region.astype(object))
            y_data = np.vstack(y_data)
            meta_data = np.vstack(meta_data)

        elif type(data) == opusFC.MultiRegionTRCDataReturn:
            y_data = []
            meta_data = []
            metas.extend([ContinuousVariable.make('map_x'),
                          ContinuousVariable.make('map_y'),
                          StringVariable.make('map_region')])
            attrs = [ContinuousVariable.make(repr(data.labels[i]))
                        for i in range(len(data.labels))]
            for region in data.regions:
                y_data.append(region.spectra)
                mapX = region.mapX
                mapY = region.mapY
                map_region = np.full_like(mapX, region.title, dtype=object)
                meta_region = np.column_stack((mapX, mapY, map_region))
                meta_data.append(meta_region.astype(object))
            y_data = np.vstack(y_data)
            meta_data = np.vstack(meta_data)

        elif type(data) == opusFC.ImageDataReturn:
            metas.extend([ContinuousVariable.make('map_x'),
                          ContinuousVariable.make('map_y')])

            data_3D = data.spectra

            for i in np.ndindex(data_3D.shape[:1]):
                map_y = np.full_like(data.mapX, data.mapY[i])
                coord = np.column_stack((data.mapX, map_y))
                if y_data is None:
                    y_data = data_3D[i]
                    meta_data = coord.astype(object)
                else:
                    y_data = np.vstack((y_data, data_3D[i]))
                    meta_data = np.vstack((meta_data, coord))

        elif type(data) == opusFC.ImageTRCDataReturn:
            metas.extend([ContinuousVariable.make('map_x'),
                          ContinuousVariable.make('map_y')])

            attrs = [ContinuousVariable.make(repr(data.labels[i]))
                        for i in range(len(data.labels))]
            data_3D = data.traces

            for i in np.ndindex(data_3D.shape[:1]):
                map_y = np.full_like(data.mapX, data.mapY[i])
                coord = np.column_stack((data.mapX, map_y))
                if y_data is None:
                    y_data = data_3D[i]
                    meta_data = coord.astype(object)
                else:
                    y_data = np.vstack((y_data, data_3D[i]))
                    meta_data = np.vstack((meta_data, coord))

        elif type(data) == opusFC.TimeResolvedTRCDataReturn:
            y_data = data.traces

        elif type(data) == opusFC.TimeResolvedDataReturn:
            metas.extend([ContinuousVariable.make('z')])

            y_data = data.spectra
            meta_data = data.z

        elif type(data) == opusFC.SingleDataReturn:
            y_data = data.y[None,:]

        else:
            raise ValueError("Empty or unsupported opusFC DataReturn object: " + type(data))

        import_params = ['SRT', 'SNM']

        for param_key in import_params:
            try:
                param = data.parameters[param_key]
            except KeyError:
                pass # TODO should notify user?
            else:
                try:
                    param_name = opusFC.paramDict[param_key]
                except KeyError:
                    param_name = param_key
                if param_key == 'SRT':
                    var = TimeVariable.make(param_name)
                elif type(param) is float:
                    var = ContinuousVariable.make(param_name)
                elif type(param) is str:
                    var = StringVariable.make(param_name)
                else:
                    raise ValueError #Found a type to handle
                metas.extend([var])
                params = np.full((y_data.shape[0],), param, np.array(param).dtype)
                if meta_data is not None:
                    # NB dtype default will be np.array(fill_value).dtype in future
                    meta_data = np.column_stack((meta_data, params.astype(object)))
                else:
                    meta_data = params

        domain = Orange.data.Domain(attrs, clses, metas)

        meta_data = np.atleast_2d(meta_data)

        table = Orange.data.Table.from_numpy(domain,
                                             y_data.astype(float, order='C'),
                                             metas=meta_data)

        return table

class SPAReader(FileFormat, SpectralFileFormat):
    #based on code by Zack Gainsforth

    EXTENSIONS = (".spa", ".SPA", ".srs")
    DESCRIPTION = 'SPA'

    saved_sections = None
    type = None

    def sections(self):
        if self.saved_sections is None:
            with open(self.filename, 'rb') as f:
                name = struct.unpack("30s", f.read(30))[0].decode("ascii")
                extended = "Exte" in name
                f.seek(288)
                ft, v, _, n = struct.unpack('<hhhh', f.read(8))
                self.saved_sections = []
                self.type = ft
                for i in range(n):
                    # Go to the section start.
                    f.seek(304 + (22 if extended else 16) * i)
                    t, offset, length = struct.unpack('<hqi' if extended else '<hii',
                                                      f.read(14 if extended else 10))
                    self.saved_sections.append((i, t, offset, length))
        return self.saved_sections

    SPEC_HEADER = 2

    def find_indextype(self, t):
        for i, a, _, _ in self.sections():
            if a == t:
                return i

    @property
    def sheets(self):
        return ()

    def read_spec_header(self):
        info = self.find_indextype(self.SPEC_HEADER)
        _, _, offset, length = self.sections()[info]
        with open(self.filename, 'rb') as f:
            f.seek(offset)
            dataType, numPoints, xUnits, yUnits, firstX, lastX, noise = struct.unpack('<iiiifff', f.read(28))
            return numPoints, firstX, lastX,

    def read_spectra(self):

        self.sections()

        if self.type == 1:
            type = 3
        else:
            type = 3  # TODO handle others differently

        numPoints, firstX, lastX = self.read_spec_header()

        _, _, offset, length = self.sections()[self.find_indextype(type)]

        with open(self.filename, 'rb') as f:
            f.seek(offset)
            data = np.fromfile(f, dtype='float32', count=length//4)

        if len(data) == numPoints:
            domvals = np.linspace(firstX, lastX, numPoints)
        else:
            domvals = np.arange(len(data))

        data = np.array([data])
        return domvals, data, None

class GSFReader(FileFormat):

    EXTENSIONS = (".gsf",)
    DESCRIPTION = 'Gwyddion Simple Field'

    def read(self):
        with open(self.filename, "rb") as f:
            #print(f.readline())
            if not (f.readline() == b'Gwyddion Simple Field 1.0\n'):
                raise ValueError('Not a correct file')

            meta = {}

            term = False #there are mandatory fileds
            while term != b'\x00':
                l = f.readline().decode('utf-8')
                name, value = l.split("=")
                name = name.strip()
                value = value.strip()
                meta[name] = value
                term = f.read(1)
                f.seek(-1, 1)

            f.read(4 - f.tell() % 4)

            meta["XRes"] = XR = int(meta["XRes"])
            meta["YRes"] = YR = int(meta["YRes"])
            meta["XReal"] = float(meta.get("XReal", 1))
            meta["YReal"] = float(meta.get("YReal", 1))
            meta["XOffset"] = float(meta.get("XOffset", 0))
            meta["YOffset"] = float(meta.get("YOffset", 0))
            meta["Title"] = meta.get("Title", None)
            meta["XYUnits"] = meta.get("XYUnits", None)
            meta["ZUnits"] = meta.get("ZUnits", None)

            X = np.fromfile(f, dtype='float32', count=XR*YR).reshape(XR, YR)

            metas = [Orange.data.ContinuousVariable.make("x"),
                     Orange.data.ContinuousVariable.make("y")]

            XRr = np.arange(XR)
            YRr = np.arange(YR)
            indices = np.transpose([np.tile(XRr, len(YRr)), np.repeat(YRr, len(XRr))])

            domain = Orange.data.Domain([Orange.data.ContinuousVariable.make("value")], None, metas=metas)
            data = Orange.data.Table(domain,
                                     X.reshape(meta["XRes"]*meta["YRes"], 1),
                                     metas=np.array(indices, dtype="object"))
            data.attributes = meta
            return data

class NeaReader(FileFormat):

    EXTENSIONS = (".nea", ".txt")
    DESCRIPTION = 'NeaSPEC'

    def read(self):

        with open(self.filename, "rt") as f:
            next(f)  # skip header
            l = next(f)
            l = l.strip()
            l = l.split("\t")
            ncols = len(l)

            f.seek(0)
            next(f)
            datacols = np.arange(4, ncols)
            data = np.loadtxt(f, dtype="float", usecols=datacols)

            f.seek(0)
            next(f)
            metacols = np.arange(0, 4)
            meta = np.loadtxt(f,
                              dtype={'names': ('row', 'column', 'run', 'channel'),
                                     'formats': (np.int, np.int, np.int, "S10")},
                              usecols=metacols)

            # ASSUMTION: runs start with 0
            runs = np.unique(meta["run"])

            # ASSUMPTION: there is one M channel and multiple O?A and O?P channels,
            # both with the same number, both starting with 0
            channels = np.unique(meta["channel"])
            maxn = -1

            def channel_type(a):
                if a.startswith(b"O") and a.endswith(b"A"):
                    return "OA"
                elif a.startswith(b"O") and a.endswith(b"P"):
                    return "OP"
                else:
                    return "M"

            for a in channels:
                if channel_type(a) in ("OA", "OP"):
                    maxn = max(maxn, int(a[1:-1]))
            numharmonics = maxn+1

            rowcols = np.vstack((meta["row"], meta["column"])).T
            uniquerc = set(map(tuple, rowcols))

            di = {}  # dictionary of indices for each row and column

            min_intp, max_intp = None, None

            for i, (row, col, run, chan) in enumerate(meta):
                if (row, col) not in di:
                    di[(row, col)] = \
                        {"M": np.zeros((len(runs), len(datacols))) * np.nan,
                         "OA": np.zeros((numharmonics, len(runs), len(datacols))) * np.nan,
                         "OP": np.zeros((numharmonics, len(runs), len(datacols))) * np.nan}
                if channel_type(chan) == "M":
                    di[(row, col)][channel_type(chan)][run] = data[i]
                    if min_intp is None:  # we need the limits of common X for all
                        min_intp = np.min(data[i])
                        max_intp = np.max(data[i])
                    else:
                        min_intp = max(min_intp, np.min(data[i]))
                        max_intp = min(max_intp, np.max(data[i]))
                elif channel_type(chan) in ("OA", "OP"):
                    di[(row, col)][channel_type(chan)][int(chan[1:-1]), run] = data[i]

            X = np.linspace(min_intp, max_intp, num=len(datacols))

            final_metas = []
            final_data = []

            for row, col in uniquerc:
                cur = di[(row, col)]
                M, OA, OP = cur["M"], cur["OA"], cur["OP"]

                OAn = np.zeros(OA.shape) * np.nan
                OPn = np.zeros(OA.shape) * np.nan
                for run in range(len(M)):
                    f = interp1d(M[run], OA[:, run])
                    OAn[:, run] = f(X)
                    f = interp1d(M[run], OP[:, run])
                    OPn[:, run] = f(X)

                OAmean = np.mean(OAn, axis=1)
                OPmean = np.mean(OPn, axis=1)
                final_data.append(OAmean)
                final_data.append(OPmean)
                final_metas += [[row, col, "O%dA" % i] for i in range(numharmonics)]
                final_metas += [[row, col, "O%dP" % i] for i in range(numharmonics)]

            final_data = np.vstack(final_data)

            metas = [Orange.data.ContinuousVariable.make("row"),
                     Orange.data.ContinuousVariable.make("column"),
                     Orange.data.StringVariable.make("channel")]

            domain = Orange.data.Domain(
                [Orange.data.ContinuousVariable.make("%f" % f) for f in X],
                None, metas=metas)
            final_metas = np.array(final_metas, dtype=object)
            return Orange.data.Table(domain, final_data, metas=final_metas)

def build_spec_table(wavenumbers, intensities):
    """
    Converts numpy arrays of wavenumber and intensity into an
    Orange.data.Table spectra object.

    Args:
        wavenumbers (np.array): 1D array of wavenumbers
        intensities (np.array): 2D array of (multi-spectra) intensities

    Returns:
        table: Orange.data.Table object, spectra format
    """

    # Add dimension to 1D array if necessary
    if intensities.ndim == 1:
        intensities = intensities[None,:]

    # Convert the wavenumbers array into a list of ContinousVariables
    wn_vars = [Orange.data.ContinuousVariable.make(repr(wavenumbers[i]))
                for i in range(wavenumbers.shape[0])]

    # Build an Orange.data.Domain object with wn_vars as
    # independant variables (or "attributes" as Orange calls them)
    domain = Orange.data.Domain(wn_vars)

    # Finally, build the table using the damain and intensity arrays:
    table = Orange.data.Table.from_numpy(domain, intensities)
    return table

def getx(data):
    """
    Return x of the data. If all attribute names are numbers,
    return their values. If not, return indices.
    """
    x = np.arange(len(data.domain.attributes), dtype="f")
    try:
        x = np.array([float(a.name) for a in data.domain.attributes])
    except:
        pass
    return x

def spectra_mean(X):
    return np.nanmean(X, axis=0, dtype=np.float64)


# Raman loves Mathematics a lot. One day his maths teacher gave him an interesting problem.
#
# He was given an array 'A' consisting of 'n' integers,
# he was needed to find the maximum value of the following expression:
#
# |Ai - Aj| + |i - j|
#
# where, 0<= i,j <n and Ai, Aj are the Array elements.

import sys

def getMaxOfExpr(num):
    max1, max2 = -(sys.maxsize+1), -(sys.maxsize+1)
    min1, min2 = (sys.maxsize), (sys.maxsize)

    for i in range(len(array)):
        if((num[i] + i) > max1):
            max1 = (num[i] + i)
        if((num[i] + i) < min1):
            min1 = (num[i] + i)
        if((num[i] - i) > max2):
            max2 = (num[i] - i)
        if((num[i] - i) < min2):
            min2 = (num[i] - i)
    print(max(max1-min1, max2-min2))

if __name__ == "__main__":
    # array = [13, 58, 31, 80, 48, 4, 51, 92, 67, 6, 2, 67, 94, 97, 76, 57, 55, 19, 98, 63]  # 107
    array = [1,2,3,4,5]
    getMaxOfExpr(array)
# END


import sys

d = {
	"January": 1,
	"February": 2,
	"March": 3,
	"April": 4,
	"May": 5,
	"June": 6,
	"July": 7,
	"August": 8,
	"September": 9,
	"October": 10,
	"November": 11,
	"December": 12,
}

for k, v in d.items():
	month = v

for word in sys.stdin:
	word = word.strip().split()
	if word[1] in d.keys():
		print(word[0] + "/" + str(d[word[1]]) + "/" + word[2][2:])


from xai.brain.wordbase.nouns._collier import _COLLIER

#calss header
class _COLLIERS(_COLLIER, ):
	def __init__(self,):
		_COLLIER.__init__(self)
		self.name = "COLLIERS"
		self.specie = 'nouns'
		self.basic = "collier"
		self.jsondata = {}


from distutils.core import setup
from boxie.version import __version__

from os import path

with open(
    path.join(path.abspath(path.dirname(__file__)), "README.rst"), encoding="utf-8"
) as readme_file:
    long_description = readme_file.read()

setup(
    name="boxie",
    version=__version__,
    author="UltiRequiem",
    author_email="eliaz.bobadilladev@gmail.com",
    url="https://github.com/UltiRequiem/boxie",
    long_description=long_description,
    include_package_data=True,
    license="MIT",
    scripts=["./bin/boxie"],
    packages=["boxie"],
)


import numpy as np
import torch
from tqdm import tqdm
from pro.Loss import criterion_KL,criterion_TV,criterion_L2

lsmx = torch.nn.LogSoftmax(dim=1)
dtype = torch.cuda.FloatTensor

def validate(model, val_loader, n_iter, val_loss, params, logWriter):

    model.eval()

    l_all = []
    l_kl = []
    l_tv = []
    l_rmse = []

    for sample in tqdm(val_loader):
        M_mea = sample["spad"].type(dtype)
        M_gt = sample["rates"].type(dtype)
        dep = sample["bins"].type(dtype)

        M_mea_re, dep_re = model(M_mea)
        M_mea_re_lsmx = lsmx(M_mea_re).unsqueeze(1)
        loss_kl = criterion_KL(M_mea_re_lsmx, M_gt).data.cpu().numpy()
        loss_tv = criterion_TV(dep_re).data.cpu().numpy()
        rmse = criterion_L2(dep_re, dep).data.cpu().numpy()
        loss = loss_kl + params["p_tv"]*loss_tv

        l_all.append(loss)
        l_kl.append(loss_kl)
        l_tv.append(loss_tv)
        l_rmse.append(rmse)

    # log the val losses
    logWriter.add_scalar("loss_val/all", np.mean(l_all), n_iter)
    logWriter.add_scalar("loss_val/kl", np.mean(l_kl), n_iter)
    logWriter.add_scalar("loss_val/tv", np.mean(l_tv), n_iter)
    logWriter.add_scalar("loss_val/rmse", np.mean(l_rmse), n_iter)
    val_loss["KL"].append(np.mean(l_kl))
    val_loss["TV"].append(np.mean(l_tv))
    val_loss["RMSE"].append(np.mean(l_rmse))

    return val_loss, logWriter


"""
1. Write a Python script to sort (ascending and descending) a dictionary by value.
"""
dict_1 = {}
num_entry = int(input("Enter the number of dictionary entry: \t"))
for i in range(num_entry):
    entry = input("Enter dictionary entry: ")
    dict_1[i] = entry
print(f"Original dictionary : {dict_1}")

# Method 1
"""
import operator
sorted_dict = sorted(dict_1.items(), key=operator.itemgetter(1))

# here (1) represents position of item in the dictionary that is being used to sort i.e. value of dictionary.

print(f"Ascending: {sorted_dict}")
rev_sorted = sorted(dict_1.items(), key=operator.itemgetter(1), reverse=True)
print(f"Descending: {rev_sorted}")
"""

# Method 2

sorted_dict = sorted(dict_1.items(), key=lambda x: x[1])
'x[1] represents that the item in position 1 is being used to sort i.e. value item of dictionary.'
print(f"Ascending: {sorted_dict}")
rev_sorted = sorted(dict_1.items(), key=lambda y: y[1], reverse=True)
print(f"Descending: {rev_sorted}")


# Robert Valladares COP4331C Simple Calculator using Python
# Copyright (c) 2016 ROBERT VALLADARES   All rights reserved.

# import math library to use with power function
import math

def multiplication(number1, number2):
    # return the product of two numbers
    return number1 * number2

def summation(number1, number2):
    # return the sum of two numbers
    return number1 + number2

def substraction(number1, number2):
    # return the 1st number minus second number
    return number1 - number2

def division(number1, number2):
    # return the division of two number, error handling done before call
    return number1 / number2

def isNumber(passed_str):
    # check if string is a number, return false if not
    try:
        float(passed_str)
        return True
    except ValueError:
        return False

def get_input():
    # get user input and parse it into tokens, number1, math operation, number2
    # set up variables to be used to read user input and process operation
    input_list = []
    temp_input = ""
    operation = 0
    tokens_added = 0
    bool1 = False
    bool2 = False
    bool3 = False
    number1 = 0
    number2 = 0

    factors = "*/^"
    addsub = "+-"
    exit_bol = False

    # ask for user input
    print ("Enter an operation or \"exit\" to end program ")
    user_input = input()
    exit_bol = (user_input == "exit")
    # check if user ask to exit program
    while (not exit_bol):
        temp_input = ""
        # if user entered incorrect symbol such as */^ as the first token
        # do not process the operation and ask for input again
        if (user_input[0] not in factors):
            # build each token and split when math operation is found
            # math operations are those included in "factors" and "addsub"
            temp_input += user_input[0]
            strlen = len(user_input)
            for i in range (1 , strlen):
                if ((user_input[i] in factors or user_input[i] in addsub)):
                    # build token until a math operation has been found
                    if (operation == 0):
                        input_list.append(temp_input)
                        input_list.append(user_input[i])
                        tokens_added += 2
                        operation = 1
                        temp_input = ""
                    else:
                        temp_input += user_input[i]

                else:
                    temp_input += user_input[i]
            # append the last number to the list
            input_list.append(temp_input)
            tokens_added += 1

            bool1 = (tokens_added == 3)
            # only if there are at least 3 tokens, verify if each number is a valid number
            if (bool1):
                bool2 = isNumber((str(input_list[0].strip())))
                bool3 = isNumber((str(input_list[2].strip())))
            # if both numbers are valid, perform math operation
            if (bool2 and bool3):
                number1 = float(input_list[0].strip())
                number2 = float(input_list[2].strip())
                # print the result of multiplication
                if (input_list[1] == "*"):
                    print(user_input + " = " + str(format(multiplication(number1, number2),'.6f')) + "\n")
                # print the result of summation
                elif (input_list[1] == "+"):
                    print(user_input + " = " + str(format(summation(number1, number2), '.6f')) + "\n")
                # print the result of substraction
                elif (input_list[1] == "-"):
                    print(user_input + " = " + str(format(substraction(number1, number2),'.6f')) + "\n")
                # print the result of division, check is divisor is 0
                elif (input_list[1] == "/"):
                    if (number2 == 0):
                        print("Error, division by 0 is not defined\n")
                    else:
                        print(user_input + " = " + str(format(division(number1, number2), '.6f')) + "\n")
                # print the result of raising first number to the power(2nd number)
                elif (input_list[1] == "^"):
                    try:
                        # handle power function errors such as negative number ^ 0.5
                        print(user_input + " = " + str(format(math.pow(number1, number2), '.6f')) + "\n")
                    except ValueError:
                        print("Error, operation is not defined\n")
                else:
                    # handle if operation is not in "factors" or "addsub"
                    print("Error, operation is not defined\n")
            else:
                # if either number is not correct, let user know,
                # ask for new input outside if statement
                print("Error, input contains incorrect operation or mistyped numbers\n")
        else:
            print ("Incorrect symbol at the beginning of input\n")

        # ask for user's imput again after performing operation,
        # reset all temporary variables
        print ("Enter an operation or \"exit\" to end program ")
        user_input = input()
        exit_bol = (user_input == "exit")
        input_list = []
        temp_input = ""
        operation = 0
        tokens_added = 0
    # exit loop asking for input
    print ("Goodbye!\n ")

# main function of program, similar to autoexecute
def main():
    get_input()
    return

# start the program
main()


#!/usr/bin/python

# colors
BLACK =     "#000000"
WHITE =     "#FFFFFF"

RED =       "#DD0000"
GREEN =     "#00DD00"
BLUE =      "#0000DD"

L_RED =     "#f67b7b"
L_ORANGE =  "#e7bc6a"
L_YELLOW =  "#eae573"
L_GREEN =   "#b7f08a"
L_BLUE =    "#8fade1"

SKY_BLUE =  "#c6fdfb"

# settings
WIN_WIDTH = 640
WIN_HEIGHT = 480
WIN_HALF_WIDTH = WIN_WIDTH/2
WIN_HALF_HEIGHT = WIN_HEIGHT/2
START_X = 32
START_Y = 32
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 32
FLAGS = 0
CAMERA_SLACK = 30
BLOCK_SIZE = 32


import logging
import fuel_fetcher
import bot_settings
import movie_fetcher
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, RegexHandler

def start(bot, update):
    keyboard = [
        [InlineKeyboardButton("Fuel Price (Petrol)", callback_data='1')],
                # [InlineKeyboardButton("Weather", callback_data='2')],
                [InlineKeyboardButton("Movie", callback_data='3')]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    update.message.reply_text('Please choose:', reply_markup=reply_markup)

def button(bot, update):
    query = update.callback_query

    # bot.editMessageText(text="Selected option: %s" % query.data,
    #                     chat_id=query.message.chat_id,
    #                     message_id=query.message.message_id)
    if query.data == '1':
        keyboard2 = [[InlineKeyboardButton("Home", callback_data='4')],
                     [InlineKeyboardButton("Work", callback_data='5')],
                     [InlineKeyboardButton("Tamworth", callback_data='6')],
                     [InlineKeyboardButton("Caste Vale", callback_data='7')],
                     [InlineKeyboardButton("Somewhere else", callback_data='8')]]
        reply_markup2 = InlineKeyboardMarkup(keyboard2)
        bot.editMessageText(text='Please choose where to check fuel:',
                            reply_markup=reply_markup2,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)

    if query.data == '3':
        result = 'Under development'
        bot.editMessageText(text=result,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)

    if query.data == '4':
        location = 'de735ax'
        result = str(fuel_fetcher.get_fuel_price(location))
        bot.editMessageText(text=result,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)

    if query.data == '5':
        location = 'b152tt'
        result = str(fuel_fetcher.get_fuel_price(location))
        bot.editMessageText(text=result,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)
    if query.data == '6':
        location = 'tamworth'
        result = str(fuel_fetcher.get_fuel_price(location))
        bot.editMessageText(text=result,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)
    if query.data == '7':
        location = 'castle+vale'
        result = str(fuel_fetcher.get_fuel_price(location))
        bot.editMessageText(text=result,
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)

    if query.data == '8':
        bot.editMessageText(text='Please reply me the postcode:',
                            chat_id=query.message.chat_id,
                            message_id=query.message.message_id)

def fuel_on_postcode(bot, update):
    location = update.message.text
    # fuelFetcher.set_location(location)
    result = str(fuel_fetcher.get_fuel_price(location))
    update.message.reply_text(result)

def help(bot, update):
    update.message.reply_text("Use /start to test this bot.")

def error(bot, update, error):
    logging.warning('Update "%s" caused error "%s"' % (update, error))

postcode_regex = '([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))\s?[0-9][A-Za-z]{2})'

# Create the Updater and pass it your bot's token.
updater = Updater(bot_settings.token)

updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(RegexHandler(postcode_regex, fuel_on_postcode))
updater.dispatcher.add_handler(CommandHandler('help', help))
updater.dispatcher.add_error_handler(error)

# Start the Bot
updater.start_polling()

# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()


from mock import patch

import pytest
from fakeredis import FakeStrictRedis
from nameko.containers import ServiceContainer
from nameko.testing.services import entrypoint_hook, worker_factory

from temp_messenger.dependencies.redis import RedisError
from temp_messenger.service import MessageService, WebServer

@pytest.fixture
def fake_strict_redis():
    with patch('temp_messenger.dependencies.redis.StrictRedis', FakeStrictRedis) as r:
        yield r

@pytest.fixture
def config(web_config):
    return dict(
        AMQP_URI='pyamqp://guest:guest@localhost',
        REDIS_URL='redis://redis:6379/0',
        **web_config
    )

def test_konnichiwa():
    service = worker_factory(MessageService)

    result = service.konnichiwa()

    assert result == 'Konnichiwa!'

def test_get_message_http(
    web_session, config, container_factory, fake_strict_redis
):
    fake_strict_redis().set('message-1', 'Test message here!')
    message_svc = container_factory(MessageService, config)
    web_server = container_factory(WebServer, config)
    message_svc.start()
    web_server.start()

    result = web_session.get('/message-1')

    assert result.text == 'Test message here!'

def test_gets_message(fake_strict_redis, config):
    fake_strict_redis().set('message-1', 'Test message here!')
    fake_strict_redis().set('message-2', '(╯°□°）╯︵ ┻━┻)')

    container = ServiceContainer(MessageService, config)
    container.start()

    with entrypoint_hook(container, 'get_message') as get_message:
        message_1 = get_message('message-1')
        message_2 = get_message('message-2')

    assert message_1 == 'Test message here!'
    assert message_2 == '(╯°□°）╯︵ ┻━┻)'

def test_raises_error_if_message_does_not_exist(
    fake_strict_redis, config
):
    container = ServiceContainer(MessageService, config)
    container.start()

    with entrypoint_hook(container, 'get_message') as get_message:
        with pytest.raises(RedisError) as err:
            get_message('message-x')

    err.match(r'Message not found: message-x')


import copy

from malaria.interventions.malaria_drug_campaigns import fmda_cfg
from dtk.interventions.irs import node_irs_config

def add_reactive_node_IRS(config_builder, start, duration=10000, trigger_coverage=1.0, irs_coverage=1.0,
                          node_selection_type='DISTANCE_ONLY',
                          reactive_radius=0, irs_ineligibility_duration=60,
                          delay=7, initial_killing=0.5, box_duration=90,
                          nodeIDs=[], node_property_restrictions=[]) :

    irs_config = copy.deepcopy(node_irs_config)
    irs_config['Killing_Config']['Decay_Time_Constant'] = box_duration
    irs_config['Killing_Config']['Initial_Effect'] = initial_killing
    irs_trigger_config = fmda_cfg(reactive_radius, node_selection_type, 'Spray_IRS')

    receiving_irs_event = {
        "class": "BroadcastEvent",
        "Broadcast_Event": "Node_Sprayed"
    }

    recent_irs = {"class": "NodePropertyValueChanger",
                  "Target_NP_Key_Value": "SprayStatus:RecentSpray",
                  "Daily_Probability": 1.0,
                  "Maximum_Duration": 0,
                  'Revert': irs_ineligibility_duration
                  }
    irs_config = [irs_config, receiving_irs_event, recent_irs]

    if not nodeIDs:
        nodes = { "class": "NodeSetAll" }
    else:
        nodes = { "class": "NodeSetNodeList", "Node_List": nodeIDs }

    no_spray = {'SprayStatus': 'None'}

    trigger_irs = { "Event_Name": "Trigger Reactive IRS",
                    "class": "CampaignEvent",
                    "Start_Day": start,
                    "Event_Coordinator_Config":
                    {
                        "class": "StandardInterventionDistributionEventCoordinator",
                        'Node_Property_Restrictions': [],
                        "Intervention_Config" : {
                            "class": "NodeLevelHealthTriggeredIV",
                            "Demographic_Coverage": trigger_coverage,
                            "Trigger_Condition_List": ["Received_Treatment"], # triggered by successful health-seeking
                            "Duration": duration,
                            "Actual_IndividualIntervention_Config" : {
                                "class": "DelayedIntervention",
                                "Delay_Distribution": "FIXED_DURATION",
                                "Delay_Period": delay,
                                "Actual_IndividualIntervention_Configs" : [irs_trigger_config]
                                }
                        }
                    },
                    "Nodeset_Config": nodes}

    distribute_irs = {  "Event_Name": "Distribute IRS",
                        "class": "CampaignEvent",
                        "Start_Day": start,
                        "Event_Coordinator_Config":
                        {
                            "class": "StandardInterventionDistributionEventCoordinator",
                            "Intervention_Config" : {
                                "class": "NodeLevelHealthTriggeredIV",
                                'Node_Property_Restrictions': [no_spray],
                                "Demographic_Coverage": irs_coverage,
                                "Trigger_Condition_List": ["Spray_IRS"],
                                "Blackout_Event_Trigger": "IRS_Blackout",
                                "Blackout_Period": 1,
                                "Blackout_On_First_Occurrence": 1,
                                "Actual_IndividualIntervention_Config" : {
                                    "Intervention_List" : irs_config,
                                    "class" : "MultiInterventionDistributor"
                                }
                            }
                        },
                        "Nodeset_Config": nodes
                    }

    if node_property_restrictions:
        trigger_irs['Event_Coordinator_Config']['Node_Property_Restrictions'].extend(node_property_restrictions)
        distribute_irs['Event_Coordinator_Config']['Intervention_Config']['Node_Property_Restrictions'] = [dict(no_spray.items() + x.items()) for x in node_property_restrictions]

    config_builder.add_event(trigger_irs)
    config_builder.add_event(distribute_irs)


import os
import requests
import PyPDF2

import typing

import logging
import daiquiri

from collections import defaultdict
from io import BytesIO
from bs4 import BeautifulSoup
from datetime import date

from bot.constants import DAYS_OF_WEEK

daiquiri.setup(level=logging.DEBUG)
logger = daiquiri.getLogger("RestaurantSadlerBrasserie")

EXCLUDED = "!"
BREAK = "__"
JOIN_NEXT_LINE = "->"
JOIN_PREVIOUS_LINE = "<-"

def get_menus(food_url):
    this_week = date.today().isocalendar()[1]

    food_site = requests.get(food_url)

    soup = BeautifulSoup(food_site.text, features="html.parser")

    menu_div = soup.findAll(
        "div", {"class": "twelve-5 nine-9 four-4 left menuDownload"}
    )[0]

    menu_links = menu_div.findChildren("ul", recursive=False)[0].findChildren(
        "li", recursive=False
    )

    pdf_links = {}
    week_number = this_week

    for link in menu_links:
        ref = link.findChildren("a", recursive=False)[0]["href"]

        if ref.split(".")[-1] == "pdf":
            cycle_index = ref.split(".")[2].split("/")[-1].split("-")[-3]
            pdf_links[week_number] = (cycle_index, ref)

            week_number += 1

    return pdf_links

def parse_dishes(food_url: str, dish_types: typing.List[str]):
    def is_line_break(line_of_menu: str, dish_types=dish_types) -> bool:
        for dish_type in dish_types:
            if line_of_menu.lower().startswith(dish_type.lower()):
                return True
        return False

    def lines_to_exclude(line_of_menu: str) -> bool:
        return (
            line_of_menu.startswith("£")
        )

    def join_next_line(line_of_menu: str) -> bool:
        return (
            line_of_menu.endswith("with")
            or line_of_menu.endswith("and")
            or line_of_menu.endswith("in")
        )

    def join_previous_line(line_of_menu: str) -> bool:
        return (
            line_of_menu.startswith("topped")
            or line_of_menu.startswith("served")
            or line_of_menu.startswith("with")
            or line_of_menu.startswith("and")
            or line_of_menu.startswith("in")
        )

    def construct_return_line(line_of_menu: str) -> str:

        return_line = line_of_menu

        if join_previous_line(return_line):
            return_line = JOIN_PREVIOUS_LINE + " " + return_line
        if join_next_line(return_line):
            return_line = return_line + " " + JOIN_NEXT_LINE

        return return_line

    def get_menu_item(line_of_menu: str) -> str:

        stripped_line = line_of_menu.strip()

        if lines_to_exclude(stripped_line):
            return EXCLUDED
        elif is_line_break(stripped_line):
            return BREAK
        else:
            return construct_return_line(stripped_line)

    def add_item_to_data_store(item: str, data_list: list) -> list:
        if not item == "":
            data_list = data_list.append(item)
        return data_list

    pdf_links = get_menus(food_url=food_url)

    dish_types = defaultdict(list)

    for i, (week_number, link_data) in enumerate(pdf_links.items()):
        # DOWNLOAD ONLY SECOND MENU - CURRENT WEEK
        if i == 1:
            # link_data IS A TUPLE - FIRST ENTRY IS INDEX OF CURRENT MENY IN AN ORDER 4 CYCLE.
            # THE SECOND IS THE LINK
            response = requests.get(link_data[1])

            pdf_file = BytesIO(response.content)

            pdf_reader = PyPDF2.PdfFileReader(pdf_file)

            menu_text = pdf_reader.getPage(0).extractText().split(os.linesep)

            # SKIP WEEK NUMBER AND WEEKDAY NAMES
            LINES_TO_SKIP = 6
            processed_items = [
                get_menu_item(menu_line) for menu_line in menu_text[LINES_TO_SKIP:]
            ]

            item_index = 0
            dish_index = 0
            parsed_menu_item = ""

            while item_index < len(processed_items):

                item = processed_items[item_index]

                if item == EXCLUDED:
                    pass
                elif item == BREAK:
                    dish_index += 1
                else:
                    parsed_menu_item = item

                    while parsed_menu_item.startswith(JOIN_PREVIOUS_LINE):
                        cut_start = len(JOIN_PREVIOUS_LINE) + 1
                        parsed_menu_item = (
                            dish_types[dish_index][-1]
                            + " "
                            + parsed_menu_item[cut_start:]
                        )
                        dish_types[dish_index].pop(-1)

                    while parsed_menu_item.endswith(JOIN_NEXT_LINE):
                        cut_end = len(JOIN_NEXT_LINE) + 1
                        parsed_menu_item = (
                            parsed_menu_item[:-cut_end]
                            + " "
                            + processed_items[item_index + 1]
                        )
                        item_index += 1

                add_item_to_data_store(parsed_menu_item, dish_types[dish_index])
                parsed_menu_item = ""

                item_index += 1

    return dish_types

def menu(food_url="https://www.oxfordsp.com/parklife/sadler-building/"):

    DISH_TYPES = [
        "Meat Main Course",
        "Vegetarian Main Course",
        "Carbohydrate",
        "Side",
    ]
    LAST_MENU_DAY_INDEX = 5

    menu = {}

    dishes = parse_dishes(food_url=food_url, dish_types=DISH_TYPES)

    logger.debug(dishes)

    restaurant_week = DAYS_OF_WEEK[:LAST_MENU_DAY_INDEX]

    incorrectly_parsed_dishes = []

    for day_index in range(len(restaurant_week)):
        day_name = restaurant_week[day_index]

        menu[day_name] = {
            f"Whole week": list(zip(dishes.values()))
        }

    return menu


# coding=utf-8

import json
import numpy as np
import os
import pandas as pd
import re
import torch
import torch.nn as nn
from gensim.models import KeyedVectors
from gensim.models import FastText
from nltk.corpus import stopwords
from torch.utils.data import Dataset
from torch.utils.data import TensorDataset
from tqdm import tqdm

from model.attention.abcnn1 import ABCNN1Attention
from model.attention.abcnn2 import ABCNN2Attention
from model.blocks.abcnn1 import ABCNN1Block
from model.blocks.abcnn2 import ABCNN2Block
from model.blocks.abcnn3 import ABCNN3Block
from model.blocks.bcnn import BCNNBlock
from model.convolution.conv import Convolution
from model.model import Model
from model.layers.layer import CNNLayer
from model.pooling.allap import AllAP
from model.pooling.widthap import WidthAP
from process import setup_dataset

# Use GPU if available, otherwise use CPU
USE_CUDA = torch.cuda.is_available()

def read_config(config_path):
    """ Reads in the configuration file from the given path.

        Args:
            config_path: string
                The path to the configuration file.

        Returns:
            config: dict
                Contains the information needed to initialize the
                datasets and model. See "config.json" for configuration
                details.
    """
    with open(config_path) as json_file:
        config = json.load(json_file)
        return config

def setup_v2(config):
    """ Handles all of the setup needed to run an ABCNN model.

        Args:
            config: dict
                Contains the information needed to initialize the datasets
                and model.

        Returns:
            datasets: dict of TensorDatasets
                Contains the datasets to be used for training/evaluation.
            model: Model
                The instantiated model.
    """
    # Read in the relevant config info
    data_paths = config["data_paths"]
    embeddings_size = config["embeddings"]["size"]
    max_length = config["model"]["max_length"]

    # Setup the datasets
    datasets = {name: pd.read_csv(data_path) for name, data_path in data_paths.items()}
    datasets, texts, word2index = setup_datasets_v2(datasets, embeddings_size, max_length)

    # Create embedding matrix
    word_vectors = setup_word_vectors(config)
    embeddings = setup_embedding_matrix_v2(word_vectors, word2index, embeddings_size)

    # Create the model
    model = setup_model_v2(embeddings, config)
    return datasets, model

def setup(config):
    """ Handles all of the setup needed to run an ABCNN model.

        Args:
            config: dict
                Contains the information needed to initialize the datasets
                and model. See "config.json" for configuration details.

        Returns:
            datasets: dict of TensorDatasets
                 Contains the datasets to be used for training/evaluation.
            model: nn.Module
                The instantiated model.
    """
    # Read in relevant config info
    data_paths = config["data_paths"]
    embeddings_size = config["embeddings"]["size"]
    max_length = config["model"]["max_length"]

    # Setup the datasets
    datasets = {name: pd.read_csv(data_path) for name, data_path in data_paths.items()}
    word_vectors = setup_word_vectors(config)
    for name, dataset in datasets.items():
        dataset, _ = setup_dataset(dataset, word_vectors, embeddings_size, max_length)
        datasets[name] = dataset

    # Setup the model
    model = setup_model(config)
    return datasets, model

def setup_model_v2(embeddings, config):
    """ Sets up the model for training/evaluation. The architecture here extends
        on the architecture introduced in the ABCNN paper by allowing for multiple
        convolutional layers with different window sizes (computed in parallel, not
        in series).

        Args:
            config: dict
                Contains the information needed to setup the model.

        Returns:
            model: ModelV2 module
                The instantiated model.
    """
    print("Creating the ABCNN model...")

    # Get relevant parameters
    embeddings_size = config["embeddings"]["size"]
    max_length = config["model"]["max_length"]
    layer_configs = config["model"]["layers"]
    use_all_layer_outputs = config["model"]["use_all_layer_outputs"]

    # Create the layers
    layers = []
    layer_sizes = [embeddings_size]
    for layer_config in layer_configs:
        layer, layer_size = setup_layer(max_length, layer_config)
        layers.append(layer)
        layer_sizes.append(layer_size)

    # Compute the size of the FC layer
    final_size = 2 * sum(layer_sizes) if use_all_layer_outputs else 2 * layer_sizes[-1]

    # Put it all together
    model = Model(embeddings, layers, use_all_layer_outputs, final_size).float()
    model = model.cuda() if USE_CUDA else model
    model.apply(weights_init)
    return model

def setup_word_vectors(config):
    """ Loads the pre-trained word vectors. The word vector file can be in
        Word2Vec or FastText formats.

        Args:
            config: dict
                Contains the information needed to initialize the embeddings
                model.

        Returns:
            word_vectors: KeyedVectors, FastTextKeyedVectors, or None
                The pretrained word embeddings. If the embeddings path
                is for a pre-trained Word2Vec model, then a KeyedVectors
                instance is returned. If the embeddings path is for a
                pre-trained FastText model, then a FastTextKeyedVectors
                instance is returned. Otherwise, None is returned.
    """
    # Get relevant parameters from config file
    embeddings_path = config["embeddings"]["path"]
    embeddings_format = config["embeddings"]["format"]
    is_binary = config["embeddings"]["is_binary"]

    # Load pre-trained word embeddings
    word_vectors = None
    if embeddings_format == "word2vec":
        if os.path.isfile(embeddings_path):
            print("Loading Word2Vec word vectors from: {}".format(embeddings_path))
            word_vectors = KeyedVectors.load_word2vec_format(embeddings_path, binary=is_binary)
    elif embeddings_format == "fasttext":
        if os.path.isfile(embeddings_path):
            print("Loading FastText word vectors from: {}".format(embeddings_path))
            embeddings_model = FastText.load_fasttext_format(embeddings_path)
            word_vectors = embeddings_model.wv
    else:
        raise Exception("Unsupported type. Must be one of 'word2vec' or 'fasttext'.")
    return word_vectors

def setup_dataset(examples, word_vectors, embeddings_size, max_length):
    """ Convert question pairs and labels into machine-readable datasets that
        can be used for training and evaluation.

        Args:
            examples: pd.DataFrame or list of lists
                Contains the examples. If examples is a pd.Dataframe instance,
                then the examples must be separated into columns named
                "question1", "question2", and "is_duplicate". If examples is a
                list of lists, then each example will be in the format
                [question1, question2, label]. Each question is a string of words,
                and the label is 0 or 1.
            word_vectors: KeyedVectors, FastTextKeyedVectors, or None
                The pre-trained word vectors. If word_vectors is a KeyedVectors
                instance, then only words in its vocabulary can be used.
                If word_vectors is a FastTextKeyedVectors instance, then OOV
                words can potentially be generated on the fly using n-gram word
                vectors. If word_vectors is None, then a random embedding is used.
            embeddings_size: int
                The dimension of the word embeddings
            max_length: int
                The maximum length of questions/sequences.

        Returns:
            dataset: TensorDataset
                Contains the feature maps and labels for all of the question pairs
                in the given set of examples.
            examples: list of tuples
                Contains the examples in the format (question1, question2, label)
                Each question is represented as a list of words/tokens. The
                label is a 0 or a 1 (integer). Padding tokens (resp. features) are
                added to the examples (resp. feature maps) so that each question
                has max_length tokens.
    """
    # Parse the data into the same format before processing
    if isinstance(examples, pd.DataFrame):
        pairs = examples[["question1", "question2"]].values.tolist()
        labels = examples["is_duplicate"].tolist()
    elif isinstance(examples, list):
        pairs = [(x[0], x[1]) for x in examples]
        labels = [int(x[2]) for x in examples]

    # Convert to tensors / clean up text
    features, texts = texts_to_features(pairs, word_vectors, embeddings_size, max_length)
    examples = [(text[0], text[1], label) for text, label in zip(texts, labels)]
    labels = torch.LongTensor(labels)
    dataset = TensorDataset(features, labels)
    return dataset, examples

def setup_datasets_v2(datasets, embeddings_size, max_length):
    """ Converts the examples from the datasets into a machine-readable format
        useful for training.

        To ensure that all words have a word embedding associated to them, we
        should have text from ALL datasets (note: this is NOT peeking at the
        dataset... this is just to prevent the model from crashing/complaining
        when it sees a word that is OOV.) OOV words are assigned random word
        embeddings.

        Args:
            datasets: dict of pd.DataFrame
                The text we would like to convert to indices into the embedding
                matrix.
            word2index: dict
                Contains the mapping from words to their indices in the
                embedding matrix.
            word_vectors: KeyedVectors, FastTextKeyedVectors, or None
                Contains the pre-trained word vectors, if available.
            embeddings_size: int
                The dimension of the word embeddings.
            max_length: int
                The maximum length of questions/sentences.

        Returns:
            index_mappings: dict of torch.LongTensors
                The keys are the names of the datasets. The values are
                LongTensors of shape (num_examples, 2, max_length).
                Each example is represented as a LongTensor of indices
                into the embedding matrix.
            word2index: dict
                Maps each word to an index of the embedding matrix.
            index2word: dict
                Maps each index of the embedding matrix to a word.
    """
    texts = dict()
    word2index = {"<PAD>": 0}
    question_cols = ["question1", "question2"]

    # Process each dataset
    for name, dataset in datasets.items():

        # Process texts
        labels = []
        indexed_examples = []
        parsed_texts = []
        num_examples = len(dataset)
        for index, example in tqdm(dataset.iterrows(), desc=name, total=num_examples):

            # Process each question separately
            index_map = []
            parsed_text = []
            for column in question_cols:

                # Parse and clean the text
                question = example[column]
                words = text_to_word_list(question)
                words = remove_stop_words(words)

                # Convert words to indices
                indexes = []
                for word in words:

                    # Update word-index lookup if necessary
                    if word not in word2index:
                        word2index[word] = len(word2index)

                    # Add the word's index to the list
                    indexes.append(word2index[word])

                # Truncate if necessary
                length = len(indexes) if len(indexes) < max_length else max_length
                indexes = indexes[:length]
                words = words[:length]

                # Pad if necessary
                if length < max_length:
                    num_padding = max_length - length
                    indexes.extend([0] * num_padding)
                    words.extend(["<PAD>"] * num_padding)

                # Store parsed text and index tensors
                index_map.append(indexes)
                parsed_text.append(words)

            # Store processed text and index tensor map and label
            labels.append(example["is_duplicate"])
            indexed_examples.append(index_map)
            parsed_texts.append(parsed_text)

        # Save the processed result
        labels = torch.LongTensor(labels)
        indexed_examples = torch.LongTensor(indexed_examples)
        dataset = TensorDataset(indexed_examples, labels)
        datasets[name] = dataset
        texts[name] = parsed_texts

    return datasets, texts, word2index

def setup_embedding_matrix_v2(word_vectors, word2index, embeddings_size):
    """ Creates the embedding matrix using the given word embeddings and mapping
        from words to indices.

        Args:
            word_vectors: KeyedVectors, FastTextKeyedVectors, or None
                The pre-trained word-vectors, if available.
            word2index: dict
                Maps words to indices in the embedding matrix.

        Returns
            embeddings: nn.Embedding
                The embedding matrix.
    """
    # Sanity check
    embeddings = np.random.uniform(-0.01, 0.01, (len(word2index) + 1, embeddings_size))
    embeddings[0] = 0   # Padding is just all 0s

    # Replace random vectors with pre-trained vectors if available
    if word_vectors is not None:
        for word, index in tqdm(word2index.items(), desc="embedding matrix"):
            try:
                embeddings[index] = word_vectors[word]
            except (RuntimeError, KeyError):
                pass

    # Convert to nn.Embedding
    embeddings = nn.Embedding.from_pretrained(torch.from_numpy(embeddings))
    return embeddings

def texts_to_features(pairs, word_vectors, embeddings_size, max_length):
    """ Converts a list of texts (strings) into their feature maps.

        Args:
            texts: list of tuple of  string
                The text we would like to featurize in the format (question1, question2).
            word_vectors: KeyedVectors, FastTextKeyedVectors, or None
                The pre-trained word vectors. If word_vectors is a KeyedVectors
                instance, then only words in its vocabulary can be used.
                If word_vectors is a FastTextKeyedVectors instance, then OOV
                words can potentially be generated on the fly using n-gram word
                vectors. If word_vectors is None, then a random embedding is used.
            embeddings_size: int
                The dimension of the word embeddings
            max_length: int
                The maximum length of questions/sequences.

        Returns:
            feature_maps: torch.Tensor of shape (batch_size, max_length, embedding_size)
                The feature maps for each text.
            processed_texts: list of lists
                Contains the processed text for each question. Stop words are removed
                and <unk> tokens are added to pad the sequence to max length.
    """
    # Process texts
    feature_maps = []
    processed_texts = []
    for pair in tqdm(pairs):

        # Process each question separately
        feature_map = []
        processed_text = []
        for question in pair:

            # Parse text
            words = text_to_word_list(question)
            words = remove_stop_words(words, word_vectors)

            # Convert words to features
            # If a word has no word vector, use a random word vector
            features = []
            for word in words:
                try:
                    feature = torch.from_numpy(word_vectors[word])
                except (RuntimeError, KeyError):
                    feature = torch.Tensor(embeddings_size).uniform_(-0.01, 0.01)
                feature = feature.view(1, embeddings_size)
                features.append(feature)

            # Truncate if necessary
            length = len(words) if len(words) < max_length else max_length
            features = features[:length]
            words = words[:length]

            # Pad if necessary
            if length < max_length:
                num_padding = max_length - length
                padding = torch.zeros((num_padding, embeddings_size), dtype=torch.float)
                unks = ["<unk>"] * num_padding
                features.append(padding)
                words.extend(unks)

            # Combine features into single feature map
            features = torch.cat(features, dim=0) # shape (max_length, embedding_size)
            feature_map.append(features.clone())
            processed_text.append(words)

        # Stack feature maps for the question pair
        feature_map = torch.stack(feature_map) # shape (2, batch_size, embedding_size)
        feature_maps.append(feature_map)
        processed_texts.append(processed_text)

    # Stack feature maps for all examples
    feature_maps = torch.stack(feature_maps)
    return feature_maps, processed_texts

def text_to_word_list(text):
    """ Preprocess and convert texts to a list of words. This code was taken
        from Elior Cohen's MaLSTM code, which can be found here:

        https://github.com/eliorc/Medium/blob/master/MaLSTM.ipynb

         Args:
             text: string
                 The text to parse.

         Returns:
             text: list of string
                 The parsed text.
     """
    text = str(text)
    text = text.lower()

    # Clean the text
    text = re.sub(r"[^A-Za-z0-9^,!.\/'+-=]", " ", text)
    text = re.sub(r"what's", "what is ", text)
    text = re.sub(r"\'s", " ", text)
    text = re.sub(r"\'ve", " have ", text)
    text = re.sub(r"can't", "cannot ", text)
    text = re.sub(r"n't", " not ", text)
    text = re.sub(r"i'm", "i am ", text)
    text = re.sub(r"\'re", " are ", text)
    text = re.sub(r"\'d", " would ", text)
    text = re.sub(r"\'ll", " will ", text)
    text = re.sub(r",", " ", text)
    text = re.sub(r"\.", " ", text)
    text = re.sub(r"!", " ! ", text)
    text = re.sub(r"\/", " ", text)
    text = re.sub(r"\^", " ^ ", text)
    text = re.sub(r"\+", " + ", text)
    text = re.sub(r"\-", " - ", text)
    text = re.sub(r"\=", " = ", text)
    text = re.sub(r"'", " ", text)
    text = re.sub(r"(\d+)(k)", r"\g<1>000", text)
    text = re.sub(r":", " : ", text)
    text = re.sub(r" e g ", " eg ", text)
    text = re.sub(r" b g ", " bg ", text)
    text = re.sub(r" u s ", " american ", text)
    text = re.sub(r"\0s", "0", text)
    text = re.sub(r" 9 11 ", "911", text)
    text = re.sub(r"e - mail", "email", text)
    text = re.sub(r"j k", "jk", text)
    text = re.sub(r"\s{2,}", " ", text)

    text = text.split()

    return text

def remove_stop_words(words):
    """ Removes all of the stop words.

        Args:
            words: list of string
                The words in the text.

        Returns:
            words: list of string
                The words in the text with stop words removed.
    """
    stops = set(stopwords.words("english"))
    return list(filter(lambda w: w not in stops, words))

def setup_embedding_matrix(word2index, word_vectors, config):
    """ Creates the embedding matrix.

        This code is based on code from Elior Cohen's MaLSTM notebook,
        which can be found here:

        https://github.com/eliorc/Medium/blob/master/MaLSTM.ipynb

        Args:
            word2index: dict
                Mapping from word/token to index in embeddings matrix.
            word_vectors: KeyedVectors or None
                The pretrained word embeddings. If a pre-trained
                model was provided, then this should be a KeyedVectors
                instance. Otherwise, this should be None.
            config: dict
                Contains the information needed to initialize the model.

        Returns:
            embeddings: nn.Embedding of shape (vocab_size, embeddings_size)
                The matrix of word embeddings.
    """
    print("Creating embedding matrix...")

    # Get relevant parameters
    embeddings_size = config["embeddings"]["size"]
    num_words = len(word2index)

    # Initialize the embedding matrix
    # Words without pre-trained embeddings will be assigned random embeddings
    embeddings = np.random.uniform(-0.01, 0.01, (num_words + 1, embeddings_size))
    embeddings[0] = 0  # So that the padding will be ignored

    # Load in the pre-trained word embeddings
    if word_vectors is not None:
        with tqdm(total=num_words) as pbar:
            for word, index in word2index.items():
                try:
                    embeddings[index] = word_vectors[word]
                except KeyError:
                    pass
                pbar.update(1)

    # Convert to an nn.Embedding layer
    embeddings = nn.Embedding.from_pretrained(torch.from_numpy(embeddings))
    return embeddings

def setup_layer(max_length, layer_config):
    """ Creates a single Layer for the CNN model.

        Args:
            max_length: int
                The maximum length of the input sequences.
            layer_config: dict
                Contains the information needed to create the layer.

        Returns:
            layer: Layer module
                The desired Layer module.
    """
    blocks = []
    output_sizes = []
    for block_config in layer_config:
        block, output_size = setup_block(max_length, block_config)
        blocks.append(block)
        output_sizes.append(output_size)
    layer = CNNLayer(blocks)
    layer_size = sum(output_sizes)
    return layer, layer_size

def setup_block(max_length, block_config):
    """ Creates a single block for the CNN model.

        Args:
            max_length: int
                The maximum length for each sequence/question.
            block_config: dict
                Contains the information needed to create the block.

        Returns:
            block: Block module
                The desired Block module.
    """
    input_size = block_config["input_size"]
    output_size = block_config["output_size"]
    width = block_config["width"]
    dropout_rate = block_config["dropout_rate"]
    match_score = block_config["match_score"]
    share_weights = block_config["share_weights"]

    if block_config["type"] == "bcnn":
        conv = Convolution(input_size, output_size, width, 1)
        pool = WidthAP(width)
        block = BCNNBlock(conv, pool, dropout_rate=dropout_rate)

    elif block_config["type"] == "abcnn1":
        attn = ABCNN1Attention(input_size, max_length, share_weights, match_score)
        conv = Convolution(input_size, output_size, width, 2)
        pool = WidthAP(width)
        block = ABCNN1Block(attn, conv, pool, dropout_rate=dropout_rate)

    elif block_config["type"] == "abcnn2":
        conv = Convolution(input_size, output_size, width, 1)
        attn = ABCNN2Attention(max_length, width, match_score)
        block = ABCNN2Block(conv, attn, dropout_rate=dropout_rate)

    elif block_config["type"] == "abcnn3":
        attn1 = ABCNN1Attention(input_size, max_length, share_weights, match_score)
        conv = Convolution(input_size, output_size, width, 2)
        attn2 = ABCNN2Attention(max_length, width, match_score)
        block = ABCNN3Block(attn1, conv, attn2, dropout_rate=dropout_rate)

    else:
        raise Exception("Unsupported type. Must be one of 'bcnn', 'abcnn1', 'abcnn2', 'abcnn3'.")

    return block, output_size

def weights_init(m):
    """ Initializes the weights for the modules in the CNN model. This function
        is applied recursively to all modules in the model via the "apply"
        function.

        Args:
            m: nn.Module
                The module to initialize.

        Returns:
            None
    """
    classname = m.__class__.__name__
    if classname.find("Conv2d") != -1:
        nn.init.xavier_normal_(m.weight)
        nn.init.constant_(m.bias, 0)
    elif classname.find("Linear") != -1:
        nn.init.xavier_normal_(m.weight)
        nn.init.constant_(m.bias, 0)
    elif classname.find("ABCNN1Attention") != -1:
        nn.init.xavier_normal_(m.W1)
        nn.init.xavier_normal_(m.W2)


from django.urls import path, include
from .views import ( UserRegister,
                     UserLogin,
                     GetUserData,
                     SetUserData,
                     GetUserCar,
                     SetUserCar,
                     UserTokenRefresh,
                     GetUserExtraDetails,
                     GetAnyUsersDisplayData,
                     SetNameAndPic,
                     DeleteUserCar
                   )

urlpatterns = [

    path('register/', UserRegister.as_view()), # Refactor to include() later
    path('login/', UserLogin.as_view()),
    path('refresh_token/', UserTokenRefresh.as_view()),
    path('get_user_data/', GetUserData.as_view()),
    path('set_user_data/', SetUserData.as_view()),
    path('set_car_details/', SetUserCar.as_view()),
    path('delete_car_details/', DeleteUserCar.as_view()),
    path('set_name_and_pic/', SetNameAndPic.as_view()),
    path('get_car_details/', GetUserCar.as_view()),
    path('get_all_details/', GetUserExtraDetails.as_view()),

    path('get_any_user_display_data/<str:user_id>', GetAnyUsersDisplayData.as_view()),

]


from django.urls import path, re_path, include
from django.contrib.auth import views as auth_views
from . import views

urlpatterns =[
    path('',views.index,name="index"),
    path('home',views.home,name="home"),
    path('signup',views.signup,name="signup"),
    path('activate/<uidb64>/<token>/', views.activate , name='activate'),
    #path('accounts/login',auth_views.login,name='login'),
    #path('accounts/logout',auth_views.logout,name='logout'),
    path('accounts/',include('django.contrib.auth.urls')),

]

"""
Line 10  will Implement All of these views

 login/ [name='login']
 logout/ [name='logout']
 password_change/ [name='password_change']
 password_change/done/ [name='password_change_done']
 password_reset/ [name='password_reset']
 password_reset/done/ [name='password_reset_done']
 reset/<uidb64>/<token>/ [name='password_reset_confirm']
 reset/done/ [name='password_reset_complete']
"""


#! /bin/python
import rados
import rbd
import os

#Need to define abstract class for Filesystem in a seperate file.

class CephException(Exception):
    def __init__(self, message = None, errors = None):
        super(CephException, self).__init__(message)
        self.errors = errors

class CephBase(object):
    def __init__(self, rid, r_conf, pool, debug=None):
        if not rid or not r_conf or not pool:
            raise CephException("one or more arguments for ceph is incorrect")
        if not os.path.isfile(r_conf):
            raise CephException("invalid configuration file")
        self.rid = rid
        self.r_conf = r_conf
        self.pool = pool
        self.cluster = self.__init_cluster()
        self.ctx = self.__init_context()
        self.rbd = self.__init_rbd()
        self.is_debug = False
        self.c_img_list = list()
        self.img_dict = dict()
        if debug:
            self.is_debug = debug

    def __repr__(self):
        return str([self.rid, self.r_conf, self.pool, self.is_debug])
#
# log in case of debug
#
    def __str__(self):
        return 'rid = {0}, conf_file = {1}, pool = {2},'\
                'is_debug? = {3}, current images {4}'\
                .format(self.rid,self.r_conf,\
                        self.pool, self.is_debug,\
                        str(self.img_dict.keys()))

    def __init_context(self):
        try:
            return  self.cluster.open_ioctx(self.\
                pool.encode('utf-8'))
        except Exception as e:
            raise e

    def __init_cluster(self):
        try:
            cluster = rados.Rados(rados_id = self.rid,\
                    conffile = self.r_conf)
            cluster.connect()
            return cluster
        except Exception as e:
            raise e

    def __init_rbd(self):
        try:
            return rbd.RBD()
        except Exception as e:
            raise e

    def init_image(self, name, ctx = None, \
            snapshot = None, read_only = False):
        try:
            if not ctx:
                ctx = self.ctx
            image_instance = rbd.Image(ctx, name, snapshot, read_only)
            self.img_dict[name] = image_instance
            ##return True We return True because we can already reference the image
            ##using the name as the key to the dict. The worklflos is something like
            return True
        except Exception as e:
            raise e
# define this function in the derivative class
# to be specific for the call.

    def run(self):
        pass

# this is the teardown section, we undo things here
    def __td_context(self):
        self.ctx.close()

    def __td_cluster(self):
        self.cluster.shutdown()

    def __td_images(self):
        if self.img_dict is None:
            return
        for key in self.img_dict.keys():
            self.img_dict[key].close()
            del(self.img_dict[key])

    def tear_down(self):
        try:
            self.__td_images()
            self.__td_context()
            self.__td_cluster()
        except Exception as e:
            raise e

class RBD(CephBase):
    def list_n(self):
        try:
            return self.rbd.list(self.ctx)
        except Exception as e:
            raise e

    def clone(self, p_name, p_snap, c_nm,\
            p_ctx = None, c_ctx = None):
        try:
            if not p_ctx:
                p_ctx = self.ctx
            if not c_ctx:
                c_ctx = self.ctx
            self.rbd.clone(p_ctx, p_name, p_snap, c_ctx,\
                    c_nm, features = 1 )
            if c_nm in self.list_n():
                self.c_img_list.append(c_nm)
                return True
            else:
                raise CephException("post image creation check failed")
        except Exception as e:
            raise e

    def remove(self, iname, ctx = None):
        if not ctx:
            ctx = self.ctx
        if iname in self.c_img_list:
            self._remove(iname, ctx)
        else:
            raise CephException("image not found in list,'\
                    ' use the unsafe remove if you are sure")
        if iname not in self.list_n():
            self.c_img_list.remove(iname)
            return True
        else:
            raise CephException("post image removal\
                    ' check failed")

    def _remove(self, iname, ctx = None):
            try:
                if not ctx:
                    ctx = self.ctx
                self.rbd.remove(ctx, iname)
            except CephException as e:
                raise e

    def write(img_name, data, offset):
        try:
            if not img_name in self.img_dict:
                raise CephException('Image not instantiated:'\
                        ' Open the image first')
            self.img_dict[img_name].write(data, offset)
        except Exception as e:
            raise e

    def close_image(self, name):
        if name in self.img_dict:
            self.img_dict[name].close()
            del(self.img_dict[name])
        else:
            raise CephException("{0} not found!".format(name))

    def snap_image(self, img_name, name):
        try:
            if img_name not in self.img_dict:
                raise CephException("Invalid image name"\
                        " or image not instantiated")
            self.img_dict[img_name].create_snap(name)
            return True
        except Exception as e:
            raise e

    def create_image(self, img_name, img_file,\
                 img_size, ctx = None):
        ctx = self.ctx
        self.rbd.create(ctx, img_name, img_size)
        return True

    def get_image(self, img_name):
         '''
            Gets image object for manipulation
         '''
         return self.img_dict[img_name]

if __name__ == "__main__" :
    pass
#    a = RBD(rid = "henn", r_conf = "/etc/ceph/ceph.conf", pool = 'boot-disk-prototype', debug=55)
#    b  = RBD(rid = "henn", r_conf = "/etc/ceph/ceph.conf", pool = 'boot-disk-prototype', debug=55)
#    #a.clone("hadoopMaster.img".encode('utf-8'), "HadoopMasterGoldenImage".encode('utf-8'),"ABLALdALA".encode('utf-8'))
#    a.init_image("test.img")
#    a.snap_image("test.img", "test_snap")
#    print a.c_img_list
#    print a.list_n()
#    #print a.remove("ABLALdALA")
#    print a.list_n()
#    print a
#    a.tear_down()
#    print a


import cv2
import os

"""
    This code is provided for Quiz3 of HZAU Vision Lab via @LucasX
"""

def read_img(source_imgpath):
    img = cv2.imread(source_imgpath, 1)

    return img

def crop_img(img, new_x, new_y):
    """
        crop image to a new size
    """
    res = cv2.resize(img, (new_x, new_y), interpolation=cv2.INTER_AREA)
    cv2.imwrite('/home/lucasx/snail' + str(new_x) + '_' + str(new_y) +
                '.jpg', res)

def rotate_img(img, rotate_angle, outputdir):
    """
        rotate this image and write out to a new one
    """
    if not os.path.exists(outputdir) and not os.path.isdir(outputdir):
        os.mkdir(outputdir)

    rows, cols = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).shape
    M = cv2.getRotationMatrix2D((cols / 2, rows / 2), rotate_angle, 1)
    dst = cv2.warpAffine(img, M, (cols, rows))
    cv2.imwrite(outputdir + os.path.sep + 'snail_' + str(rotate_angle) + '.jpg', dst)

if __name__ == '__main__':
    img = read_img('/home/lucasx/snail.jpg')
    print(img.shape)
    crop_img(img, 64, 64)

    # this code block is to rotate image and output
    curr_angle = 0
    while curr_angle < 360:
        rotate_img(img, curr_angle, '/home/lucasx/rotate_img')
        curr_angle += 15


import sys
import socket
import threading

def decode_bytes(byt: bytes):
    obyt = byt
    try:
        print('[*] Trying to decode using UTF-8')
        byt = byt.decode()
    except UnicodeDecodeError:
        byt = obyt
        print('[*] Couldn\'t decode using UTF-8')
        print('[*] Trying with CP1252')
        try:
            byt = byt.decode('cp1252')
        except UnicodeDecodeError:
            byt = obyt
            print('[*] Couldn\'t decode using CP1252')
            print('[*] Trying decoding ignoring errors')
            byt = byt.decode(errors='ignore')
    print('[*] Decoding successful')
    return byt

def hexdump(src, length=16):
    result = []
    digits = 4
    for i in range(0, len(src), length):
        s = src[i:i + length]
        hexa = ' '.join(["%0*X" % (digits, ord(x)) for x in s])
        text = ''.join([x if 0x20 <= ord(x) < 0xFFF7 else '.' for x in s])
        result.append("%04X %-*s %s" % (i, length * (digits + 1), hexa,
                                        text))
    print('\n'.join(result))

def receive_from(connection: socket.SocketType, timeout=5):
    buf = b''
    connection.settimeout(timeout)
    while True:
        try:
            data = connection.recv(1024)
        except socket.timeout:
            break
        buf += data
        if len(data) < 1024:
            break
    return buf

def handle_response(buffer: bytes):
    # buffer = decode_bytes(buffer)
    # print('[*] Manipulating response')
    # buffer = buffer.replace('Microsoft', 'MrButt')
    # buffer = buffer.replace('Firefox', 'Aquawolf (Mudei o nome manipulando pacotes)')
    # buffer = bytes(buffer, 'utf-8')
    return buffer

def handle_request(buffer):
    # TODO perform packet modifications
    return buffer

def _handle_proxy(client_socket, remotehost, remoteport, receive_first):
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect((remotehost, remoteport))
    if receive_first:
        buf = receive_from(remote_socket)
        hexdump(decode_bytes(buf))
        buf = handle_response(buf)

        if len(buf):
            print('[<==] Sending {} bytes to localhost'.format(len(buf)))
            client_socket.send(buf)

    while 1:
        loc_buf = receive_from(client_socket)

        if len(loc_buf):
            print('[<==] Received {} bytes from localhost'.format(len(loc_buf)))
            hexdump(decode_bytes(loc_buf))
            loc_buf = handle_request(loc_buf)
            remote_socket.send(loc_buf)
            print('[==>] Sent buffer to remote host')

        rem_buf = receive_from(remote_socket)

        if len(rem_buf):
            print('[<==] Received {} bytes from remote host'.format(len(rem_buf)))
            hexdump(decode_bytes(rem_buf))
            rem_buf = handle_response(rem_buf)
            client_socket.send(rem_buf)
            print('[<==] Sent to localhost')

        if not len(rem_buf) or not len(loc_buf):
            client_socket.close()
            remote_socket.close()
            print('[*] Closed connections, as no more data was shown')
            break

def handle_proxy(client_socket, remotehost, remoteport, receive_first):
    try:
        _handle_proxy(client_socket, remotehost, remoteport, receive_first)
    except KeyboardInterrupt:
        print('[*] Cancelling connection')

def server_loop(localhost, localport, remotehost, remoteport, receive_first):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        server.bind((localhost, localport))
    except PermissionError:
        print('[!] Port binding failure due to missing permissions')
        print('[!] Tried with sudo?')
        sys.exit(1)
    except OSError:
        print('[!] Port binding failure because port is being used')
        print('[!] Check port settings and firewall then try again')
        sys.exit(1)
    # Binding
    print('[*] Binding successful!')
    print('[*] Listening on {}:{}'.format(localhost, localport))

    server.listen(1)

    while 1:
        csocket, addr = server.accept()
        print('[*] Connection was established with {}:{}'.format(addr[0], addr[1]))
        proxy_thread = threading.Thread(target=handle_proxy,
                                        args=(csocket, remotehost, remoteport, receive_first))
        proxy_thread.start()

def main():
    if len(sys.argv[1:]) != 5:
        print('Usage:')
        print('         [python] tcpproxy.py [localhost] [localport] [remotehost] [remoteport] [receiveFirst]')
        print('[localhost] -> the default localhost loopback IP address')
        print('[localport] -> the port that will be used to keep the connection')
        print('[remotehost] -> the IP address of the host to proxy to')
        print('[remoteport] -> the port that from which data will come')
        print('[receiveFirst] -> set to True if the remote machine will be the first to send the data')
        print('Example:')
        print('         [python] tcpproxy.py 127.0.0.1 9000 1.2.3.4 9000 True')
        sys.exit(0)

    localhost = sys.argv[1]
    localport = int(sys.argv[2])

    remotehost = sys.argv[3]
    remoteport = int(sys.argv[4])

    recvfirst = True if sys.argv[5].lower().startswith('true') else False
    try:
        server_loop(localhost, localport, remotehost, remoteport, recvfirst)
    except KeyboardInterrupt:
        print('\n[*] Quitting the proxy')
        sys.exit(0)

if __name__ == '__main__':
    main()