"""
Functions to simulate Wood Anderson traces, pick maximum peak-to-peak \
amplitudes write these amplitudes and periods to SEISAN s-files and to \
calculate magnitudes from this and the informaiton within SEISAN s-files.

Written as part of the EQcorrscan package by Calum Chamberlain - first \
written to impliment magnitudes for the 2015 Wanaka aftershock sequence, \
written up by Warren-Smith [2014/15].

Copyright 2015 Calum Chamberlain

This file is part of EQcorrscan.

    EQcorrscan is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    EQcorrscan is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with EQcorrscan.  If not, see <http://www.gnu.org/licenses/>.

"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import numpy as np
import warnings


def dist_calc(loc1, loc2):
    """
    Function to calcualte the distance in km between two points, uses the \
    flat Earth approximation.

    :type loc1: tuple
    :param loc1: Tuple of lat, lon, depth (in decimal degrees and km)
    :type loc2: tuple
    :param loc2: Tuple of lat, lon, depth (in decimal degrees and km)

    :returns: float, Distance between points.
    """
    R = 6371.009  # Radius of the Earth in km
    dlat = np.radians(abs(loc1[0] - loc2[0]))
    dlong = np.radians(abs(loc1[1] - loc2[1]))
    ddepth = abs(loc1[2] - loc2[2])
    mean_lat = np.radians((loc1[0] + loc2[0]) / 2)
    dist = R * np.sqrt(dlat ** 2 + (np.cos(mean_lat) * dlong) ** 2)
    dist = np.sqrt(dist ** 2 + ddepth ** 2)
    return dist


def _sim_WA(trace, PAZ, seedresp, water_level):
    """
    Function to remove the insturment response from a trace and return a \
    de-meaned, de-trended, Wood Anderson simulated trace in it's place.

    Works in-place on data and will destroy your original data, copy the \
    trace before giving it to this function!

    :type trace: obspy.Trace
    :param trace: A standard obspy trace, generally should be given without
                    pre-filtering, if given with pre-filtering for use with
                    amplitude determiniation for magnitudes you will need to
                    worry about how you cope with the response of this filter
                    yourself.
    :type PAZ: dict
    :param PAZ: Dictionary containing lists of poles and zeros, the gain and
                the sensitivity.
    :type water_level: int
    :param water_level: Water level for the simulation.

    :returns: obspy.Trace
    """
    # Note Wood anderson sensitivity is 2080 as per Uhrhammer & Collins 1990
    PAZ_WA = {'poles': [-6.283 + 4.7124j, -6.283 - 4.7124j],
              'zeros': [0 + 0j], 'gain': 1.0, 'sensitivity': 2080}
    from obspy.signal import seisSim
    # De-trend data
    trace.detrend('simple')
    # Simulate Wood Anderson
    if PAZ:
        trace.data = seisSim(trace.data, trace.stats.sampling_rate,
                             paz_remove=PAZ, paz_simulate=PAZ_WA,
                             water_level=water_level, remove_sensitivity=True)
    elif seedresp:
        trace.data = seisSim(trace.data, trace.stats.sampling_rate,
                             paz_remove=None, paz_simulate=PAZ_WA,
                             water_level=water_level, seedresp=seedresp)
    else:
        UserWarning('No response given to remove, will just simulate WA')
        trace.data = seisSim(trace.data, trace.stats.samplng_rate,
                             paz_remove=None, water_level=water_level)
    return trace


def _max_p2t(data, delta):
    """
    Function to find the maximum peak-to-trough amplitude and period of this \
    amplitude.  Originally designed to be used to calculate magnitudes (by \
    taking half of the peak-to-trough amplitude as the peak amplitude).

    :type data: ndarray
    :param data: waveform trace to find the peak-to-trough in.
    :type delta: float
    :param delta: Sampling interval in seconds

    :returns: tuple of (amplitude, period, time) with amplitude in the same \
        scale as given in the input data, and period in seconds, and time in \
        seconds from the start of the data window.
    """
    import matplotlib.pyplot as plt
    debug_plot = False
    turning_points = []  # A list of tuples of (amplitude, sample)
    for i in range(1, len(data) - 1):
        if (data[i] < data[i-1] and data[i] < data[i+1]) or\
           (data[i] > data[i-1] and data[i] > data[i+1]):
            turning_points.append((data[i], i))
    if len(turning_points) >= 1:
        amplitudes = np.empty([len(turning_points)-1],)
        half_periods = np.empty([len(turning_points)-1],)
    else:
        plt.plot(data)
        plt.show()
        print('Turning points has length: '+str(len(turning_points)) +
              ' data have length: '+str(len(data)))
        return (0.0, 0.0, 0.0)
    for i in range(1, len(turning_points)):
        half_periods[i-1] = (delta * (turning_points[i][1] -
                                      turning_points[i-1][1]))
        amplitudes[i-1] = np.abs(turning_points[i][0]-turning_points[i-1][0])
    amplitude = np.max(amplitudes)
    period = 2 * half_periods[np.argmax(amplitudes)]
    if debug_plot:
        plt.plot(data, 'k')
        plt.plot([turning_points[np.argmax(amplitudes)][1],
                  turning_points[np.argmax(amplitudes)-1][1]],
                 [turning_points[np.argmax(amplitudes)][0],
                  turning_points[np.argmax(amplitudes)-1][0]], 'r')
        plt.show()
    return (amplitude, period, delta*turning_points[np.argmax(amplitudes)][1])


def _GSE2_PAZ_read(GSEfile):
    """
    Function to read the instrument response information from a GSE Poles and \
    Zeros file as generated by the SEISAN program RESP.

    Format must be CAL2, not coded for any other format at the moment, \
    contact the authors to add others in.

    :type GSEfile: string
    :param GSEfile: Path to GSE file

    :returns: Dict of poles, zeros, gain and sensitivity
    """
    import datetime as dt
    f = open(GSEfile)
    # First line should start with CAL2
    header = f.readline()
    if not header[0:4] == 'CAL2':
        raise IOError('Unknown format for GSE file, only coded for CAL2')
    station = header.split()[1]
    channel = header.split()[2]
    sensor = header.split()[3]
    date = dt.datetime.strptime(header.split()[7], '%Y/%m/%d')
    header = f.readline()
    if not header[0:4] == 'PAZ2':
        raise IOError('Unknown format for GSE file, only coded for PAZ2')
    gain = float(header.split()[3])  # Measured in nm/counts
    kpoles = int(header.split()[4])
    kzeros = int(header.split()[5])
    poles = []
    for i in range(kpoles):
        pole = f.readline()
        poles.append(complex(float(pole.split()[0]), float(pole.split()[1])))
    zeros = []
    for i in range(kzeros):
        zero = f.readline()
        zeros.append(complex(float(zero.split()[0]), float(zero.split()[1])))
    # Have Poles and Zeros, but need Gain and Sensitivity
    # Gain should be in the DIG2 line:
    for line in f:
        if line[0:4] == 'DIG2':
            sensitivity = float(line.split()[2])  # measured in counts/muVolt
    f.close()
    PAZ = {'poles': poles, 'zeros': zeros, 'gain': gain,
           'sensitivity': sensitivity}
    return PAZ, date, station, channel, sensor


def _find_resp(station, channel, network, time, delta, directory):
    """
    Helper function to find the response information for a given station and \
    channel at a given time and return a dictionary of poles and zeros, gain \
    and sensitivity.

    :type station: str
    :param station: Station name (as in the response files)
    :type channel: str
    :param channel: Channel name (as in the response files)
    :type network: str
    :param network: Network to scan for, can be a wildcard
    :type time: datetime.datetime
    :param time: Date-time to look for repsonse information
    :type delta: float
    :param delta: Sample interval in seconds
    :type directory: str
    :param directory: Directory to scan for response information

    :returns: dict, response information
    """
    import glob
    from obspy.signal.invsim import evalresp
    from obspy import UTCDateTime
    possible_respfiles = glob.glob(directory+'/RESP.'+network+'.'+station +
                                   '.*.' + channel)  # GeoNet RESP naming
    possible_respfiles += glob.glob(directory+'/RESP.'+network+'.'+channel +
                                    '.' + station)  # RDseed RESP naming
    possible_respfiles += glob.glob(directory+'/RESP.'+station+'.'+network)
    # WIZARD resp naming
    # GSE format, station needs to be 5 charectars padded with _, channel is 4
    # characters padded with _
    possible_respfiles += glob.glob(directory+'/'+station.ljust(5, '_') +
                                    channel[0:len(channel)-1].ljust(3, '_') +
                                    channel[-1]+'.*_GSE')
    PAZ = []
    seedresp = []
    for respfile in possible_respfiles:
        print('Reading response from: '+respfile)
        if respfile.split('/')[-1][0:4] == 'RESP':
            # Read from a resp file
            seedresp = {'filename': respfile, 'date': UTCDateTime(time),
                        'units': 'DIS', 'network': network, 'station': station,
                        'channel': channel, 'location': '*'}
            try:
                # Attempt to evaluate the response for this information, if not
                # then this is not the correct response info!
                freq_resp, freqs = evalresp(delta, 100, seedresp['filename'],
                                            seedresp['date'],
                                            units=seedresp['units'], freq=True,
                                            network=seedresp['network'],
                                            station=seedresp['station'],
                                            channel=seedresp['channel'])
            except:
                print('Issues with RESP file')
                seedresp = []
                continue
        elif respfile[-3:] == 'GSE':
            PAZ, pazdate, pazstation, pazchannel, pazsensor =\
                _GSE2_PAZ_read(respfile)
            # check that the date is good!
            if pazdate >= time and pazchannel != channel and\
               pazstation != station:
                print('Issue with GSE file')
                print('date: '+str(pazdate)+' channel: '+pazchannel +
                      ' station: '+pazstation)
                PAZ = []
        else:
            continue
        # Check that PAZ are for the correct station, channel and date
        if PAZ or seedresp:
            break
    if PAZ:
        return PAZ
    elif seedresp:
        return seedresp


def _pairwise(iterable):
    """
    Wrapper on itertools for SVD_magnitude.
    """
    import itertools
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)


def Amp_pick_sfile(sfile, datapath, respdir, chans=['Z'], var_wintype=True,
                   winlen=0.9, pre_pick=0.2, pre_filt=True, lowcut=1.0,
                   highcut=20.0, corners=4):
    """
    Function to read information from a SEISAN s-file, load the data and the \
    picks, cut the data for the channels given around the S-window, simulate \
    a Wood Anderson seismometer, then pick the maximum peak-to-trough \
    amplitude.

    Output will be put into a mag_calc.out file which will be in full S-file \
    format and can be copied to a REA database.

    :type sfile: string
    :type datapath: string
    :param datapath: Path to the waveform files - usually the path to the WAV \
        directory
    :type respdir: string
    :param respdir: Path to the response information directory
    :type chans: List of strings
    :param chans: List of the channels to pick on, defaults to ['Z'] - should \
        just be the orientations, e.g. Z,1,2,N,E
    :type var_wintype: bool
    :param var_wintype: If True, the winlen will be \
        multiplied by the P-S time if both P and S picks are \
        available, otherwise it will be multiplied by the \
        hypocentral distance*0.34 - dervided using a p-s ratio of \
        1.68 and S-velocity of 1.5km/s to give a large window, \
        defaults to True
    :type winlen: float
    :param winlen: Length of window, see above parameter, if var_wintype is \
        False then this will be in seconds, otherwise it is the \
        multiplier to the p-s time, defaults to 0.5.
    :type pre_pick: float
    :param pre_pick: Time before the s-pick to start the cut window, defaults \
        to 0.2
    :type pre_filt: bool
    :param pre_filt: To apply a pre-filter or not, defaults to True
    :type lowcut: float
    :param lowcut: Lowcut in Hz for the pre-filter, defaults to 1.0
    :type highcut: float
    :param highcut: Highcut in Hz for the pre-filter, defaults to 20.0
    :type corners: int
    :param corners: Number of corners to use in the pre-filter
    """
    # Hardwire a p-s multiplier of hypocentral distance based on p-s ratio of
    # 1.68 and an S-velocity 0f 1.5km/s, deliberately chosen to be quite slow
    ps_multiplier = 0.34
    from eqcorrscan.utils import Sfile_util
    from obspy import read
    from scipy.signal import iirfilter
    from obspy.signal.invsim import paz2AmpValueOfFreqResp
    import warnings
    # First we need to work out what stations have what picks
    event = Sfile_util.readpicks(sfile)[0]
    # Convert these picks into a lists
    stations = []  # List of stations
    channels = []  # List of channels
    picktimes = []  # List of pick times
    picktypes = []  # List of pick types
    distances = []  # List of hypocentral distances
    picks_out = []
    for pick in event.picks:
        if pick.phase_hint in ['P', 'S']:
            picks_out.append(pick)  # Need to be able to remove this if there
            # isn't data for a station!
            stations.append(pick.waveform_id.station_code)
            channels.append(pick.waveform_id.channel_code)
            picktimes.append(pick.time)
            picktypes.append(pick.phase_hint)
            arrival = [arrival for arrival in event.origins[0].arrivals
                       if arrival.pick_id == pick.resource_id]
            distances.append(arrival.distance)
    # Read in waveforms
    stream = read(datapath+'/'+Sfile_util.readwavename(sfile)[0])
    if len(Sfile_util.readwavename(sfile)) > 1:
        for wavfile in Sfile_util.readwavename(sfile):
            stream += read(datapath+'/'+wavfile)
    stream.merge()  # merge the data, just in case!
    # For each station cut the window
    uniq_stas = list(set(stations))
    del arrival
    for sta in uniq_stas:
        for chan in chans:
            print('Working on '+sta+' '+chan)
            tr = stream.select(station=sta, channel='*'+chan)
            if not tr:
                # Remove picks from file
                # picks_out=[picks_out[i] for i in xrange(len(picks))\
                # if picks_out[i].station+picks_out[i].channel != \
                # sta+chan]
                warnings.warn('There is no station and channel match in the ' +
                              'wavefile!')
                break
            else:
                tr = tr[0]
            # Apply the pre-filter
            if pre_filt:
                try:
                    tr.detrend('simple')
                except:
                    dummy = tr.split()
                    dummy.detrend('simple')
                    tr = dummy.merge()[0]
                tr.filter('bandpass', freqmin=lowcut, freqmax=highcut,
                          corners=corners)
            sta_picks = [i for i in xrange(len(stations))
                         if stations[i] == sta]
            pick_id = event.picks[sta_picks[0]].resource_id
            arrival = [arrival for arrival in event.origins[0].arrivals
                       if arrival.pick_id == pick_id]
            hypo_dist = arrival.distance
            CAZ = arrival.azimuth
            if var_wintype:
                if 'S' in [picktypes[i] for i in sta_picks] and\
                   'P' in [picktypes[i] for i in sta_picks]:
                    # If there is an S-pick we can use this :D
                    S_pick = [picktimes[i] for i in sta_picks
                              if picktypes[i] == 'S']
                    S_pick = min(S_pick)
                    P_pick = [picktimes[i] for i in sta_picks
                              if picktypes[i] == 'P']
                    P_pick = min(P_pick)
                    try:
                        tr.trim(starttime=S_pick-pre_pick,
                                endtime=S_pick+(S_pick-P_pick)*winlen)
                    except:
                        break
                elif 'S' in [picktypes[i] for i in sta_picks]:
                    S_pick = [picktimes[i] for i in sta_picks
                              if picktypes[i] == 'S']
                    S_pick = min(S_pick)
                    P_modelled = S_pick - hypo_dist * ps_multiplier
                    try:
                        tr.trim(starttime=S_pick-pre_pick,
                                endtime=S_pick + (S_pick - P_modelled) *
                                winlen)
                    except:
                        break
                else:
                    # In this case we only have a P pick
                    P_pick = [picktimes[i] for i in sta_picks
                              if picktypes[i] == 'P']
                    P_pick = min(P_pick)
                    S_modelled = P_pick + hypo_dist * ps_multiplier
                    try:
                        tr.trim(starttime=S_modelled - pre_pick,
                                endtime=S_modelled + (S_modelled - P_pick) *
                                winlen)
                    except:
                        break
                # Work out the window length based on p-s time or distance
            elif 'S' in [picktypes[i] for i in sta_picks]:
                # If the window is fixed we still need to find the start time,
                # which can be based either on the S-pick (this elif), or
                # on the hypocentral distance and the P-pick

                # Take the minimum S-pick time if more than one S-pick is
                # available
                S_pick = [picktimes[i] for i in sta_picks
                          if picktypes[i] == 'S']
                S_pick = min(S_pick)
                try:
                    tr.trim(starttime=S_pick - pre_pick,
                            endtime=S_pick + winlen)
                except:
                    break
            else:
                # In this case, there is no S-pick and the window length is
                # fixed we need to calculate an expected S_pick based on the
                # hypocentral distance, this will be quite hand-wavey as we
                # are not using any kind of velocity model.
                P_pick = [picktimes[i] for i in sta_picks
                          if picktypes[i] == 'P']
                P_pick = min(P_pick)
                hypo_dist = [distances[i] for i in sta_picks
                             if picktypes[i] == 'P'][0]
                S_modelled = P_pick + hypo_dist * ps_multiplier
                try:
                    tr.trim(starttime=S_modelled - pre_pick,
                            endtime=S_modelled + winlen)
                except:
                    break
            # Find the response information
            resp_info = _find_resp(tr.stats.station, tr.stats.channel,
                                   tr.stats.network, tr.stats.starttime,
                                   tr.stats.delta, respdir)
            PAZ = []
            seedresp = []
            if resp_info and 'gain' in resp_info:
                PAZ = resp_info
            elif resp_info:
                seedresp = resp_info
            # Simulate a Wood Anderson Seismograph
            if PAZ and len(tr.data) > 10:
                # Set ten data points to be the minimum to pass
                tr = _sim_WA(tr, PAZ, None, 10)
            elif seedresp and len(tr.data) > 10:
                tr = _sim_WA(tr, None, seedresp, 10)
            elif len(tr.data) > 10:
                warnings.warn('No PAZ for '+tr.stats.station+' ' +
                              tr.stats.channel+' at time: ' +
                              str(tr.stats.starttime))
                continue
            if len(tr.data) <= 10:
                # Should remove the P and S picks if len(tr.data)==0
                warnings.warn('No data found for: '+tr.stats.station)
                # print 'No data in miniseed file for '+tr.stats.station+\
                # ' removing picks'
                # picks_out=[picks_out[i] for i in xrange(len(picks_out))\
                # if i not in sta_picks]
                break
            # Get the amplitude
            amplitude, period, delay = _max_p2t(tr.data, tr.stats.delta)
            if amplitude == 0.0:
                break
            print('Amplitude picked: ' + str(amplitude))
            # Note, amplitude should be in meters at the moment!
            # Remove the pre-filter response
            if pre_filt:
                # Generate poles and zeros for the filter we used earlier: this
                # is how the filter is designed in the convenience methods of
                # filtering in obspy.
                z, p, k = iirfilter(corners, [lowcut / (0.5 *
                                                        tr.stats.
                                                        sampling_rate),
                                              highcut / (0.5 *
                                                         tr.stats.
                                                         sampling_rate)],
                                    btype='band', ftype='butter', output='zpk')
                filt_paz = {'poles': list(p),
                            'zeros': list(z),
                            'gain': k,
                            'sensitivity':  1.0}
                amplitude /= (paz2AmpValueOfFreqResp(filt_paz, 1 / period) *
                              filt_paz['sensitivity'])
            # Convert amplitude to mm
            if PAZ:  # Divide by Gain to get to nm (returns pm? 10^-12)
                # amplitude *=PAZ['gain']
                amplitude /= 1000
            if seedresp:  # Seedresp method returns mm
                amplitude *= 1000000
            # Write out the half amplitude, approximately the peak amplitude as
            # used directly in magnitude calculations
            # Page 343 of Seisan manual:
            #   Amplitude (Zero-Peak) in units of nm, nm/s, nm/s^2 or counts
            amplitude *= 0.5
            # Generate a PICK type object for this pick
            picks_out.append(Sfile_util.PICK(station=tr.stats.station,
                                             channel=tr.stats.channel,
                                             impulsivity=' ',
                                             phase='IAML',
                                             weight='', polarity=' ',
                                             time=tr.stats.starttime+delay,
                                             coda=999, amplitude=amplitude,
                                             peri=period, azimuth=float('NaN'),
                                             velocity=float('NaN'), AIN=999,
                                             SNR='',
                                             azimuthres=999,
                                             timeres=float('NaN'),
                                             finalweight=999,
                                             distance=hypo_dist,
                                             CAZ=CAZ))
    # Copy the header from the sfile to a new local S-file
    fin = open(sfile, 'r')
    fout = open('mag_calc.out', 'w')
    for line in fin:
        if not line[79] == '7':
            fout.write(line)
        else:
            fout.write(line)
            break
    fin.close()
    for pick in picks_out:
        fout.write(pick)
        # Note this uses the legacy pick class
    fout.close()
    # Write picks out to new s-file
    for pick in picks_out:
        print(pick)
    # Sfile_util.populateSfile('mag_calc.out', picks_out)
    return picks_out


def SVD_moments(U, s, V, stachans, event_list, n_SVs=4):
    """
    Function to convert basis vectors calculated by singular value \
    decomposition (see the SVD functions in clustering) into relative \
    magnitudes.

    :type U: List of np.ndarray
    :param U: List of the input basis vectors from the SVD, one array for \
        each channel used.
    :type s: List of nd.array
    :param s: List of the singular values, one array for each channel
    :type V: List of np.ndarry
    :param V: List of output basis vectors from SVD, one array per channel.
    :type stachans: List of string
    :param stachans: List of station.channel input
    :type event_list: List of list
    :param event_list: List of events for which you have data, such that \
        event_list[i] corresponds to stachans[i], U[i] etc. and \
        event_list[i][j] corresponds to event j in U[i]
    type n_SVs: int
    :param n_SVs: Number of singular values to use, defaults to 4.

    :returns: M, np.array of relative moments
    """
    import copy
    import random
    import pickle

    # Copying script from one obtained from John Townend.
    # Define maximum number of events, will be the width of K
    K_width = max([max(ev_list) for ev_list in event_list])+1
    # Sometimes the randomisation generates a singular matrix - rather than
    # attempting to regulerize this matrix I propose undertaking the
    # randomisation step a further time
    for i, stachan in enumerate(stachans):
        k = []  # Small kernel matrix for one station - channel
        # Copy the relevant vectors so as not to detroy them
        U_working = copy.deepcopy(U[i])
        V_working = copy.deepcopy(V[i])
        s_working = copy.deepcopy(s[i])
        ev_list = event_list[i]
        if len(ev_list) > len(V_working):
            print('V is : '+str(len(V_working)))
            f_dump = open('mag_calc_V_working.pkl', 'wb')
            pickle.dump(V_working, f_dump)
            f_dump.close()
            raise IOError('More events than represented in V')
        # Set all non-important singular values to zero
        s_working[n_SVs:len(s_working)] = 0
        s_working = np.diag(s_working)
        # Convert to numpy matrices
        U_working = np.matrix(U_working)
        V_working = np.matrix(V_working)
        s_working = np.matrix(s_working)

        SVD_weights = U_working[:, 0]
        # If all the weights are negative take the abs
        if np.all(SVD_weights < 0):
            warnings.warn('All weights are negative - flipping them')
            SVD_weights = np.abs(SVD_weights)
        SVD_weights = np.array(SVD_weights).reshape(-1).tolist()
        # Shuffle the SVD_weights prior to pairing - will give one of multiple
        # pairwise options - see p1956 of Rubinstein & Ellsworth 2010
        # We need to keep the real indexes though, otherwise, if there are
        # multiple events with the same weight we will end up with multiple
        # -1 values
        random_SVD_weights = np.copy(SVD_weights)
        # Tack on the indexes
        random_SVD_weights = random_SVD_weights.tolist()
        random_SVD_weights = [(random_SVD_weights[_i], _i)
                              for _i in range(len(random_SVD_weights))]
        random.shuffle(random_SVD_weights)
        # Add the first element to the end so all elements will be paired twice
        random_SVD_weights.append(random_SVD_weights[0])
        # Take pairs of all the SVD_weights (each weight appears in 2 pairs)
        pairs = []
        for pair in _pairwise(random_SVD_weights):
            pairs.append(pair)
        # Deciding values for each place in kernel matrix using the pairs
        for pairsIndex in range(len(pairs)):
            # We will normalize by the minimum weight
            _weights = zip(*list(pairs[pairsIndex]))[0]
            _indeces = zip(*list(pairs[pairsIndex]))[1]
            min_weight = min(_weights)
            max_weight = max(_weights)
            min_index = _indeces[np.argmin(_weights)]
            max_index = _indeces[np.argmax(_weights)]
            row = []
            # Working out values for each row of kernel matrix
            for j in range(len(SVD_weights)):
                if j == max_index:
                    result = -1
                elif j == min_index:
                    normalised = max_weight / min_weight
                    result = float(normalised)
                else:
                    result = 0
                row.append(result)
            print(row)
            # Add each row to the K matrix
            k.append(row)
        # k is now a square matrix, we need to flesh it out to be K_width
        k_filled = np.zeros([len(k), K_width])
        for j in range(len(k)):
            for l, ev in enumerate(ev_list):
                k_filled[j, ev] = k[j][l]
        if 'K' not in locals():
            K = k_filled
        else:
            K = np.concatenate([K, k_filled])
    # Remove any empty rows
    K_nonempty = []
    events_out = []
    for i in range(0, K_width):
        if not np.all(K[:, i] == 0):
            K_nonempty.append(K[:, i])
            events_out.append(i)
    K = np.array(K_nonempty).T
    K = K.tolist()
    K_width = len(K[0])
    # Add an extra row to K, so average moment = 1
    K.append(np.ones(K_width) * (1. / K_width))
    print("\nCreated Kernel matrix: ")
    del row
    print('\n'.join([''.join([str(round(float(item), 3)).ljust(6)
          for item in row]) for row in K]))
    Krounded = np.around(K, decimals=4)
    # Create a weighting matrix to put emphasis on the final row.
    W = np.matrix(np.identity(len(K)))
    # the final element of W = the number of stations*number of events
    W[-1, -1] = len(K) - 1
    # Make K into a matrix
    K = np.matrix(K)

    ############

    # Solve using the weighted least squares equation, K.T is K transpose
    Kinv = np.array(np.linalg.inv(K.T*W*K) * K.T * W)

    # M are the relative moments of the events
    M = Kinv[:, -1]

    return M, events_out


def pick_db(indir, outdir, calpath, startdate, enddate, wavepath=None):
    """
    Wrapper to loop through a SEISAN database and make a lot of magnitude \
    picks.

    :type indir: str
    :param indir: Path to the seisan REA directory (not including yyyy/mm)
    :type outdir: str
    :param outdir: Path to output seisan REA directory (not including yyyy/mm)
    :type calpath: str
    :param calpath: Path to the directory containing the response files
    :type startdate: datetime.datetime
    :param startdate: Date to start looking for S-files
    :type enddate: datetime.datetime
    :param enddate: Date to stop looking for S-files
    :type wavepath: str
    :param wavepath: Path to the seisan WAV directory (not including yyyy/mm)
    """
    import datetime as dt
    import glob
    import shutil

    kdays = ((enddate + dt.timedelta(1)) - startdate).days
    for i in xrange(kdays):
        day = startdate + dt.timedelta(i)
        print('Working on ' + str(day))
        sfiles = glob.glob(indir + '/' + str(day.year) + '/' +
                           str(day.month).zfill(2) + '/' +
                           str(day.day).zfill(2) + '-*L.S' + str(day.year) +
                           str(day.month).zfill(2))
        datetimes = [dt.datetime.strptime(sfiles[i].split('/')[-1],
                                          '%d-%H%M-%SL.S%Y%m')
                     for i in xrange(len(sfiles))]
        sfiles = [sfiles[i] for i in xrange(len(sfiles))
                  if datetimes[i] > startdate and
                  datetimes[i] < enddate]
        if not wavepath:
            wavedir = "/".join(indir.split('/')[:-2])+'/WAV/' +\
                indir.split('/')[-1]+'/'+str(day.year)+'/' +\
                str(day.month).zfill(2)
        else:
        	wavedir = wavepath+'/'+str(day.year)+'/' +\
                    str(day.month).zfill(2)
        sfiles.sort()
        for sfile in sfiles:
            # Make the picks!
            print('				Working on Sfile: '+sfile)
            picks = Amp_pick_sfile(sfile, wavedir, calpath)
            del picks
            # Copy the mag_calc.out file to the correct place
            shutil.copyfile('mag_calc.out', outdir+'/'+str(day.year)+'/' +
                            str(day.month).zfill(2)+'/'+sfile.split('/')[-1])

if __name__ == "__main__":
    import doctest
    doctest.testmod()
