# -*- coding: utf-8 -*-
""" PyCorrFit

    Module openfile
    This file is used to define operations on how to open some files.

    Dimensionless representation:
    unit of time        : 1 ms
    unit of inverse time: 10³ /s
    unit of distance    : 100 nm
    unit of Diff.coeff  : 10 µm²/s
    unit of inverse area: 100 /µm²
    unit of inv. volume : 1000 /µm³

    Copyright (C) 2011-2012  Paul Müller

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
"""


import csv
from distutils.version import LooseVersion # For version checking
import numpy as np
import os
import shutil
import tempfile
import wx
import yaml
import zipfile

import doc
import edclasses
from tools import info

# These imports are required for loading data
from readfiles import Filetypes
from readfiles import BGFiletypes

def ImportParametersYaml(parent, dirname):
    """ Import the parameters from a parameters.yaml file
        from an PyCorrFit session.
    """
    wc = [".pcfs", ".fcsfit-session.zip"]
    wcstring = "PyCorrFit session (*.pcfs)|*{};*{}".format(wc[0], wc[1])
    dlg = wx.FileDialog(parent, "Open session file", dirname, "", 
                                 wcstring, wx.OPEN)
    # user cannot do anything until he clicks "OK"
    if dlg.ShowModal() == wx.ID_OK:
        path = dlg.GetPath()            # Workaround since 0.7.5
        (dirname, filename) = os.path.split(path)
        #filename = dlg.GetFilename()
        #dirname = dlg.GetDirectory()
        dlg.Destroy()
        Arc = zipfile.ZipFile(os.path.join(dirname, filename), mode='r')
        # Get the yaml parms dump:
        yamlfile = Arc.open("Parameters.yaml")
        # Parms: Fitting and drawing parameters of correlation curve
        # The *yamlfile* is responsible for the order of the Pages #i.
        Parms = yaml.safe_load(yamlfile)
        yamlfile.close()
        Arc.close()
        return Parms, dirname, filename
    else:
        dirname=dlg.GetDirectory()
        return None, dirname, None


def OpenSession(parent, dirname, sessionfile=None):
    """ Load a whole session that has previously been saved
        by PyCorrFit.
        Infodict may contain the following keys:
        "Backgrounds", list: contains the backgrounds
        "Comments", dict: "Session" comment and int keys to Page titles
        "Correlations", dict: page numbers, all correlation curves
        "External Functions", dict: modelids to external model functions
        "External Weights", dict: page numbers, external weights for fitting
        "Parameters", dict: page numbers, all parameters of the pages
        "Preferences", dict: not used yet
        "Traces", dict: page numbers, all traces of the pages
    """
    Infodict = dict()
    wc = [".pcfs", ".fcsfit-session.zip"]
    wcstring = "PyCorrFit session (*.pcfs)|*{};*{}".format(wc[0], wc[1])
    if sessionfile is None:
        dlg = wx.FileDialog(parent, "Open session file", dirname, "", 
                        wcstring, wx.OPEN)
        # user cannot do anything until he clicks "OK"
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()            # Workaround since 0.7.5
            (dirname, filename) = os.path.split(path)
            #filename = dlg.GetFilename()
            #dirname = dlg.GetDirectory()
            dlg.Destroy()
        else:
            # User did not press OK
            # stop this function
            dirname = dlg.GetDirectory()
            dlg.Destroy()
            return None, dirname, None
    else:
        (dirname, filename) = os.path.split(sessionfile)
        path = sessionfile                  # Workaround since 0.7.5
        if (filename[-len(wc[0]):] != wx[0] and
            filename[-len(wc[1]):] != wx[1]):
            # User specified wrong file
            print "Unknown file extension: "+filename
            # stop this function
            dirname = dlg.GetDirectory()
            dlg.Destroy()
            return None, dirname, None
    Arc = zipfile.ZipFile(path, mode='r')
    try:
        ## Check PyCorrFit version:
        readmefile = Arc.open("Readme.txt")
        # e.g. "This file was created using PyCorrFit version 0.7.6"
        identifier = readmefile.readline()
        arcv = LooseVersion(identifier[46:].strip())
        thisv = LooseVersion(parent.version.strip())
        if arcv > thisv:
            errstring = "Your version of Pycorrfit ("+str(thisv)+")"+\
                   " is too old to open this session ("+\
                   str(arcv).strip()+").\n"+\
                   "Please download the lates version of "+\
                   " PyCorrFit from \n"+doc.HomePage+".\n"+\
                   "Continue opening this session?"
            dlg = edclasses.MyOKAbortDialog(parent, errstring, "Warning")
            returns = dlg.ShowModal()
            if returns == wx.ID_OK:
                dlg.Destroy()
            else:
                dlg.Destroy()
                return None, dirname, None
    except:
        pass
    # Get the yaml parms dump:
    yamlfile = Arc.open("Parameters.yaml")
    # Parameters: Fitting and drawing parameters of correlation curve
    # The *yamlfile* is responsible for the order of the Pages #i.
    Infodict["Parameters"] = yaml.safe_load(yamlfile)
    yamlfile.close()
    # Supplementary data (errors of fit)
    supname = "Supplements.yaml"
    try:
        Arc.getinfo(supname)
    except:
        pass
    else:
        supfile = Arc.open(supname)
        supdata = yaml.safe_load(supfile)
        Infodict["Supplements"] = dict()
        for idp in supdata:
            Infodict["Supplements"][idp[0]] = dict() 
            Infodict["Supplements"][idp[0]]["FitErr"] = idp[1]
            if len(idp) > 2:
                # As of version 0.7.4 we save chi2 and shared pages -global fit
                Infodict["Supplements"][idp[0]]["Chi sq"] = idp[2]
                Infodict["Supplements"][idp[0]]["Global Share"] = idp[3]
    ## Preferences: Reserved for a future version of PyCorrFit :)
    prefname = "Preferences.yaml"
    try:
        Arc.getinfo(prefname)
    except KeyError:
        pass
    else:
        yamlpref = Arc.open(prefname)
        Infodict["Preferences"] = yaml.safe_load(yamlpref)
        yamlpref.close()
    # Get external functions
    Infodict["External Functions"] = dict()
    key = 7001
    while key <= 7999:
        # (There should not be more than 1000 functions)
        funcfilename = "model_"+str(key)+".txt"
        try:
            Arc.getinfo(funcfilename)
        except KeyError:
            # No more functions to import
            key = 8000
        else:
            funcfile =  Arc.open(funcfilename)
            Infodict["External Functions"][key] = funcfile.read()
            funcfile.close()
            key=key+1
    # Get the correlation arrays
    Infodict["Correlations"] = dict()
    for i in np.arange(len(Infodict["Parameters"])):
        # The *number* is used to identify the correct file
        number = str(Infodict["Parameters"][i][0]).strip().strip(":").strip("#")
        pageid = int(number)
        expfilename = "data"+number+".csv"
        expfile = Arc.open(expfilename, 'r')
        readdata = csv.reader(expfile, delimiter=',')
        dataexp = list()
        tau = list()
        if str(readdata.next()[0]) == "# tau only":
            for row in readdata:
                # Exclude commentaries
                if (str(row[0])[0:1] != '#'):
                    tau.append(float(row[0]))
            tau = np.array(tau)
            dataexp = None
        else:
            for row in readdata:
                # Exclude commentaries
                if (str(row[0])[0:1] != '#'):
                    dataexp.append((float(row[0]), float(row[1])))
            dataexp = np.array(dataexp)
            tau = dataexp[:,0]
        Infodict["Correlations"][pageid] = [tau, dataexp]
        del readdata
        expfile.close()
    # Get the Traces
    Infodict["Traces"] = dict()
    for i in np.arange(len(Infodict["Parameters"])):
        # The *number* is used to identify the correct file
        number = str(Infodict["Parameters"][i][0]).strip().strip(":").strip("#")
        pageid = int(number)
        # Find out, if we have a cross correlation data type
        IsCross = False
        try:
            IsCross = Infodict["Parameters"][i][7]
        except IndexError:
            # No Cross correlation
            pass
        if IsCross is False:
            tracefilenames = ["trace"+number+".csv"]
        else:
            # Cross correlation uses two traces
            tracefilenames = ["trace"+number+"A.csv",
                              "trace"+number+"B.csv" ]
        thistrace = list()
        for tracefilename in tracefilenames:
            try:
                Arc.getinfo(tracefilename)
            except KeyError:
                pass
            else:
                tracefile = Arc.open(tracefilename, 'r')
                traceread = csv.reader(tracefile, delimiter=',')
                singletrace = list()
                for row in traceread:
                    # Exclude commentaries
                    if (str(row[0])[0:1] != '#'):
                        singletrace.append((float(row[0]), float(row[1])))
                singletrace = np.array(singletrace)
                thistrace.append(singletrace)
                del traceread
                del singletrace
                tracefile.close()
        if len(thistrace) != 0:
            Infodict["Traces"][pageid] = thistrace
        else:
            Infodict["Traces"][pageid] = None
    # Get the comments, if they exist
    commentfilename = "comments.txt"
    try:
        # Raises KeyError, if file is not present:
        Arc.getinfo(commentfilename)
    except KeyError:
        pass   
    else:
        # Open the file
        commentfile = Arc.open(commentfilename, 'r')
        Infodict["Comments"] = dict()
        for i in np.arange(len(Infodict["Parameters"])):
            number = str(Infodict["Parameters"][i][0]).strip().strip(":").strip("#")
            pageid = int(number)
            # Strip line ending characters for all the Pages.
            Infodict["Comments"][pageid] = commentfile.readline().strip()
        # Now Add the Session Comment (the rest of the file). 
        ComList = commentfile.readlines()
        Infodict["Comments"]["Session"] = ''
        for line in ComList:
            Infodict["Comments"]["Session"] += line
        commentfile.close()
    # Get the Backgroundtraces and data if they exist
    bgfilename = "backgrounds.csv"
    try:
        # Raises KeyError, if file is not present:
        Arc.getinfo(bgfilename)
    except KeyError:
        pass
    else:
        # Open the file
        Infodict["Backgrounds"] = list()
        bgfile = Arc.open(bgfilename, 'r')
        bgread = csv.reader(bgfile, delimiter='\t')
        i = 0
        for bgrow in bgread:
            bgtracefilename = "bg_trace"+str(i)+".csv"
            bgtracefile = Arc.open(bgtracefilename, 'r')
            bgtraceread = csv.reader(bgtracefile, delimiter=',')
            bgtrace = list()
            for row in bgtraceread:
                # Exclude commentaries
                if (str(row[0])[0:1] != '#'):
                    bgtrace.append((np.float(row[0]), np.float(row[1])))
            bgtrace = np.array(bgtrace)
            Infodict["Backgrounds"].append([np.float(bgrow[0]), str(bgrow[1]), bgtrace])
            i = i + 1
        bgfile.close()
    # Get external weights if they exist
    WeightsFilename = "externalweights.txt"
    try:
        # Raises KeyError, if file is not present:
        Arc.getinfo(WeightsFilename)
    except:
        pass
    else:
        Wfile = Arc.open(WeightsFilename, 'r')
        Wread = csv.reader(Wfile, delimiter='\t')
        Weightsdict = dict()
        for wrow in Wread:
            Pkey = wrow[0]  # Page of weights
            pageid = int(Pkey)
            # Do not overwrite anything
            try:
                Weightsdict[pageid]
            except:
                Weightsdict[pageid] = dict()
            Nkey = wrow[1]  # Name of weights
            Wdatafilename = "externalweights_data"+Pkey+"_"+Nkey+".csv"
            Wdatafile = Arc.open(Wdatafilename, 'r')
            Wdatareader = csv.reader(Wdatafile)
            Wdata = list()
            for row in Wdatareader:
                # Exclude commentaries
                if (str(row[0])[0:1] != '#'):
                    Wdata.append(np.float(row[0]))
            Weightsdict[pageid][Nkey] = np.array(Wdata)
        Infodict["External Weights"] = Weightsdict
    Arc.close()
    return Infodict, dirname, filename


def SaveSession(parent, dirname, Infodict):
    """ Write whole Session into a zip file.
        Infodict may contain the following keys:
        "Backgrounds", list: contains the backgrounds
        "Comments", dict: "Session" comment and int keys to Page titles
        "Correlations", dict: page numbers, all correlation curves
        "External Functions, dict": modelids to external model functions
        "External Weights", dict: page numbers, external weights for fitting
        "Parameters", dict: page numbers, all parameters of the pages
        "Preferences", dict: not used yet
        "Traces", dict: page numbers, all traces of the pages
        We will also write a Readme.txt
    """
    dlg = wx.FileDialog(parent, "Save session file", dirname, "",
                        "PyCorrFit session (*.pcfs)|*.pcfs",
                        wx.SAVE|wx.FD_OVERWRITE_PROMPT)
    if dlg.ShowModal() == wx.ID_OK:
        path = dlg.GetPath()            # Workaround since 0.7.5
        (dirname, filename) = os.path.split(path)
        #filename = dlg.GetFilename()
        #dirname = dlg.GetDirectory()
        # Sometimes you have multiple endings...
        if filename.endswith(".pcfs") is not True:
            filename = filename+".pcfs"
        dlg.Destroy()
        # Change working directory
        returnWD = os.getcwd()
        tempdir = tempfile.mkdtemp()
        os.chdir(tempdir)
        # Create zip file
        Arc = zipfile.ZipFile(filename, mode='w')
        # Only do the Yaml thing for safe operations.
        # Make the yaml dump
        parmsfilename = "Parameters.yaml"
        # Parameters have to be floats in lists
        # in order for yaml.safe_load to work.
        Parms =  Infodict["Parameters"]
        ParmsKeys = Parms.keys()
        ParmsKeys.sort()
        Parmlist = list()
        for idparm in ParmsKeys:
            # Make sure we do not accidently save arrays.
            # This would not work correctly with yaml.
            Parms[idparm][2] = np.array(Parms[idparm][2],dtype="float").tolist()
            Parms[idparm][3] = np.array(Parms[idparm][3],dtype="bool").tolist()
            # Range of fitting parameters
            Parms[idparm][9] = np.array(Parms[idparm][9],dtype="float").tolist()
            Parmlist.append(Parms[idparm])
        yaml.dump(Parmlist, open(parmsfilename, "wb"))
        Arc.write(parmsfilename)
        os.remove(os.path.join(tempdir, parmsfilename))
        # Supplementary data (errors of fit)
        errsfilename = "Supplements.yaml"
        Sups =  Infodict["Supplements"]
        SupKeys = Sups.keys()
        SupKeys.sort()
        Suplist = list()
        for idsup in SupKeys:
            error = Sups[idsup]["FitErr"]
            chi2 = Sups[idsup]["Chi sq"]
            globalshare = Sups[idsup]["Global Share"]
            Suplist.append([idsup, error, chi2, globalshare])
        yaml.dump(Suplist, open(errsfilename, "wb"))
        Arc.write(errsfilename)
        os.remove(os.path.join(tempdir, errsfilename))
        # Save external functions
        for key in Infodict["External Functions"].keys():
            funcfilename = "model_"+str(key)+".txt"
            funcfile =  open(funcfilename, 'wb')
            funcfile.write(Infodict["External Functions"][key])
            funcfile.close()
            Arc.write(funcfilename)
            os.remove(os.path.join(tempdir, funcfilename))
        # Save (dataexp and tau)s into separate csv files.
        for pageid in Infodict["Correlations"].keys():
            # Since *Array* and *Parms* are in the same order (the page order),
            # we will identify the filename by the Page title number.
            number = str(pageid)
            expfilename = "data"+number+".csv"
            expfile = open(expfilename, 'wb')
            tau = Infodict["Correlations"][pageid][0]
            exp = Infodict["Correlations"][pageid][1]
            dataWriter = csv.writer(expfile, delimiter=',')
            if exp is not None:
                # Names of Columns
                dataWriter.writerow(['# tau', 'experimental data'])
                # Actual Data
                # Do not use len(tau) instead of len(exp[:,0])) !
                # Otherwise, the experimental data will not be saved entirely,
                # if it has been cropped. Because tau might be smaller, than
                # exp[:,0] --> tau = exp[startcrop:endcrop,0]
                for j in np.arange(len(exp[:,0])):
                    dataWriter.writerow(["%.20e" % exp[j,0],
                                         "%.20e" % exp[j,1]])
            else:
                # Only write tau
                dataWriter.writerow(['# tau'+' only'])
                for j in np.arange(len(tau)):
                    dataWriter.writerow(["%.20e" % tau[j]])
            expfile.close()
            # Add to archive
            Arc.write(expfilename)
            os.remove(os.path.join(tempdir, expfilename))
        # Save traces into separate csv files.
        for pageid in Infodict["Traces"].keys():
            number = str(pageid)
            # Since *Trace* and *Parms* are in the same order, which is the
            # Page order, we will identify the filename by the Page title 
            # number.
            if Infodict["Traces"][pageid] is not None:
                if Parms[pageid][7] is True:
                    # We have cross correlation: save two traces
                    ## A
                    tracefilenamea = "trace"+number+"A.csv"
                    tracefile = open(tracefilenamea, 'wb')
                    traceWriter = csv.writer(tracefile, delimiter=',')
                    time = Infodict["Traces"][pageid][0][:,0]
                    rate = Infodict["Traces"][pageid][0][:,1]
                    # Names of Columns
                    traceWriter.writerow(['# time', 'count rate'])
                    # Actual Data
                    for j in np.arange(len(time)):
                        traceWriter.writerow(["%.20e" % time[j],
                                              "%.20e" % rate[j]])
                    tracefile.close()
                    # Add to archive
                    Arc.write(tracefilenamea)
                    os.remove(os.path.join(tempdir, tracefilenamea))
                    ## B
                    tracefilenameb = "trace"+number+"B.csv"
                    tracefile = open(tracefilenameb, 'wb')
                    traceWriter = csv.writer(tracefile, delimiter=',')
                    time = Infodict["Traces"][pageid][1][:,0]
                    rate = Infodict["Traces"][pageid][1][:,1]
                    # Names of Columns
                    traceWriter.writerow(['# time', 'count rate'])
                    # Actual Data
                    for j in np.arange(len(time)):
                        traceWriter.writerow(["%.20e" % time[j],
                                              "%.20e" % rate[j]])
                    tracefile.close()
                    # Add to archive
                    Arc.write(tracefilenameb)
                    os.remove(os.path.join(tempdir, tracefilenameb))
                else:
                    # Save one single trace
                    tracefilename = "trace"+number+".csv"
                    tracefile = open(tracefilename, 'wb')
                    traceWriter = csv.writer(tracefile, delimiter=',')
                    time = Infodict["Traces"][pageid][:,0]
                    rate = Infodict["Traces"][pageid][:,1]
                    # Names of Columns
                    traceWriter.writerow(['# time', 'count rate'])
                    # Actual Data
                    for j in np.arange(len(time)):
                        traceWriter.writerow(["%.20e" % time[j],
                                              "%.20e" % rate[j]])
                    tracefile.close()
                    # Add to archive
                    Arc.write(tracefilename)
                    os.remove(os.path.join(tempdir, tracefilename))
        # Save comments into txt file
        commentfilename = "comments.txt"
        commentfile = open(commentfilename, 'wb')
        # Comments[-1] is comment on whole Session
        Ckeys = Infodict["Comments"].keys()
        Ckeys.sort()
        for key in Ckeys:
            if key != "Session":
                commentfile.write(Infodict["Comments"][key]+"\r\n")
        commentfile.write(Infodict["Comments"]["Session"])
        commentfile.close()
        Arc.write(commentfilename)
        os.remove(os.path.join(tempdir, commentfilename))
        ## Save Background information:
        Background = Infodict["Backgrounds"]
        if len(Background) > 0:
            # We do not use a comma separated, but a tab separated file,
            # because a comma might be in the name of a bg.
            bgfilename = "backgrounds.csv"
            bgfile = open(bgfilename, 'wb')
            bgwriter = csv.writer(bgfile, delimiter='\t')
            for i in np.arange(len(Background)):
                bgwriter.writerow([str(Background[i][0]), Background[i][1]])
                # Traces
                bgtracefilename = "bg_trace"+str(i)+".csv"
                bgtracefile = open(bgtracefilename, 'wb')
                bgtraceWriter = csv.writer(bgtracefile, delimiter=',')
                bgtraceWriter.writerow(['# time', 'count rate'])
                # Actual Data
                time = Background[i][2][:,0]
                rate = Background[i][2][:,1]
                for j in np.arange(len(time)):
                    bgtraceWriter.writerow(["%.20e" % time[j],
                                            "%.20e" % rate[j]])
                bgtracefile.close()
                # Add to archive
                Arc.write(bgtracefilename)
                os.remove(os.path.join(tempdir, bgtracefilename))
            bgfile.close()
            Arc.write(bgfilename)
            os.remove(os.path.join(tempdir, bgfilename))
        ## Save External Weights information
        WeightedPageID = Infodict["External Weights"].keys()
        WeightedPageID.sort()
        WeightFilename = "externalweights.txt"
        WeightFile = open(WeightFilename, 'wb')
        WeightWriter = csv.writer(WeightFile, delimiter='\t')
        for pageid in WeightedPageID:
            number = str(pageid)
            NestWeights = Infodict["External Weights"][pageid].keys()
            # The order of the types does not matter, since they are
            # sorted in the frontend and upon import. We sort them here, anyhow.
            NestWeights.sort()
            for Nkey in NestWeights:
                WeightWriter.writerow([number, str(Nkey).strip()])
                # Add data to a File
                WeightDataFilename = "externalweights_data"+number+\
                                     "_"+str(Nkey).strip()+".csv"
                WeightDataFile = open(WeightDataFilename, 'wb')
                WeightDataWriter = csv.writer(WeightDataFile)
                wdata = Infodict["External Weights"][pageid][Nkey]
                for jw in np.arange(len(wdata)):
                    WeightDataWriter.writerow([str(wdata[jw])])
                WeightDataFile.close()
                Arc.write(WeightDataFilename)
                os.remove(os.path.join(tempdir, WeightDataFilename))
        WeightFile.close()
        Arc.write(WeightFilename)
        os.remove(os.path.join(tempdir, WeightFilename))
        ## Readme
        rmfilename = "Readme.txt"
        rmfile = open(rmfilename, 'wb')
        rmfile.write(ReadmeSession)
        rmfile.close()
        Arc.write(rmfilename)
        os.remove(os.path.join(tempdir, rmfilename))
        # Close the archive
        Arc.close()
        # Move archive to destination directory
        shutil.move(os.path.join(tempdir, filename), 
                    os.path.join(dirname, filename) )
        # Go to destination directory
        os.chdir(returnWD)
        os.rmdir(tempdir)
        return dirname, filename
    else:
        dirname = dlg.GetDirectory()
        dlg.Destroy()
        return dirname, None




def saveCSV(parent, dirname, Page):
    """ Write relevant data into a comma separated list.
        
        Parameters:
        *parent*   the parent window
        *dirname* directory to set on saving
        *Page*     Page containing all necessary variables
    """
    filename = Page.tabtitle.GetValue().strip()+Page.counter[:2]
    dlg = wx.FileDialog(parent, "Save curve", dirname, filename, 
          "Correlation with trace (*.csv)|*.csv;*.CSV"+\
          "|Correlation only (*.csv)|*.csv;*.CSV",
           wx.SAVE|wx.FD_OVERWRITE_PROMPT)
    # user cannot do anything until he clicks "OK"
    if dlg.ShowModal() == wx.ID_OK:
        path = dlg.GetPath()            # Workaround since 0.7.5
        (dirname, filename) = os.path.split(path)
        #filename = dlg.GetFilename()
        #dirname = dlg.GetDirectory()
        if filename.lower().endswith(".csv") is not True:
            filename = filename+".csv"
        openedfile = open(os.path.join(dirname, filename), 'wb')
        ## First, some doc text
        openedfile.write(ReadmeCSV.replace('\n', '\r\n'))
        # The infos
        InfoMan = info.InfoClass(CurPage=Page)
        PageInfo = InfoMan.GetCurFancyInfo()
        for line in PageInfo.splitlines():
            openedfile.write("# "+line+"\r\n")
        openedfile.write("#\r\n#\r\n")
        # Get all the data we need from the Page
        # Modeled data
        # Since 0.7.8 the user may normalize the curves. The normalization
        # factor is set in *Page.normfactor*.
        corr = Page.datacorr[:,1]*Page.normfactor
        if Page.dataexp is not None:
            # Experimental data
            tau = Page.dataexp[:,0]
            exp = Page.dataexp[:,1]*Page.normfactor
            res = Page.resid[:,1]*Page.normfactor
            # Plotting! Because we only export plotted area.
            weight = Page.weights_used_for_plotting
            if weight is None:
                pass
            elif len(weight) != len(exp):
                text = "Weights have not been calculated for the "+\
                       "area you want to export. Pressing 'Fit' "+\
                       "again should solve this issue. Data will "+\
                       "not be saved."
                wx.MessageDialog(parent, text, "Error", 
                    style=wx.ICON_ERROR|wx.OK|wx.STAY_ON_TOP)
                return dirname, None
        else:
            tau = Page.datacorr[:,0]
            exp = None
            res = None
        # Include weights in data saving:
        # PyCorrFit thinks in [ms], but we will save as [s]
        timefactor = 0.001
        tau = timefactor * tau
        ## Now we want to write all that data into the file
        # This is for csv writing:
        ## Correlation curve
        dataWriter = csv.writer(openedfile, delimiter='\t')
        if exp is not None:
            header = '# Channel (tau [s])'+"\t"+ \
                     'Experimental correlation'+"\t"+ \
                     'Fitted correlation'+ "\t"+ \
                     'Residuals'+"\r\n"
            data = [tau, exp, corr, res]
            if Page.weighted_fit_was_performed is True \
            and weight is not None:
                header = header.strip() + "\t"+'Weights (fit)'+"\r\n"
                data.append(weight)
        else:
            header = '# Channel (tau [s])'+"\t"+ \
                     'Correlation function'+"\r\n"
            data = [tau, corr]
        # Write header
        openedfile.write(header)
        # Write data
        for i in np.arange(len(data[0])):
            # row-wise, data may have more than two elements per row
            datarow = list()
            for j in np.arange(len(data)):
                rowcoli = str("%.10e") % data[j][i]
                datarow.append(rowcoli)
            dataWriter.writerow(datarow)
        ## Trace
        # Only save the trace if user wants us to:
        if dlg.GetFilterIndex() == 0:
            # We will also save the trace in [s]
            # Intensity trace in kHz may stay the same
            if Page.trace is not None:
                # Mark beginning of Trace
                openedfile.write('#\r\n#\r\n# BEGIN TRACE\r\n#\r\n')
                # Columns
                time = Page.trace[:,0]*timefactor
                intensity = Page.trace[:,1]
                # Write
                openedfile.write('# Time [s]'+"\t" 
                                     'Intensity trace [kHz]'+" \r\n")
                for i in np.arange(len(time)):
                    dataWriter.writerow([str("%.10e") % time[i],
                                         str("%.10e") % intensity[i]])
            elif Page.tracecc is not None:
                # We have some cross-correlation here:
                # Mark beginning of Trace A
                openedfile.write('#\r\n#\r\n# BEGIN TRACE\r\n#\r\n')
                # Columns
                time = Page.tracecc[0][:,0]*timefactor
                intensity = Page.tracecc[0][:,1]
                # Write
                openedfile.write('# Time [s]'+"\t" 
                                     'Intensity trace [kHz]'+" \r\n")
                for i in np.arange(len(time)):
                    dataWriter.writerow([str("%.10e") % time[i],
                                         str("%.10e") % intensity[i]])
                # Mark beginning of Trace B
                openedfile.write('#\r\n#\r\n# BEGIN SECOND TRACE\r\n#\r\n')
                # Columns
                time = Page.tracecc[1][:,0]*timefactor
                intensity = Page.tracecc[1][:,1]
                # Write
                openedfile.write('# Time [s]'+"\t" 
                                     'Intensity trace [kHz]'+" \r\n")
                for i in np.arange(len(time)):
                    dataWriter.writerow([str("%.10e") % time[i],
                                         str("%.10e") % intensity[i]])
        dlg.Destroy()
        openedfile.close()
        return dirname, filename
    else:
        dirname = dlg.GetDirectory()
        dlg.Destroy()
        return dirname, None


ReadmeCSV = """# This file was created using PyCorrFit version {}.
#
# Lines starting with a '#' are treated as comments.
# The data is stored as CSV below this comment section.
# Data usually consists of lag times (channels) and
# the corresponding correlation function - experimental
# and fitted values plus resulting residuals.
# If this file is opened by PyCorrFit, only the first two
# columns will be imported as experimental data.
#
""".format(doc.__version__)
    
    
ReadmeSession = """This file was created using PyCorrFit version {}.
The .zip archive you are looking at is a stored session of PyCorrFit.
If you are interested in how the data is stored, you will find
out here. Most important are the dimensions of units:
Dimensionless representation:
 unit of time        : 1 ms
 unit of inverse time: 10³ /s
 unit of distance    : 100 nm
 unit of Diff.coeff  : 10 µm²/s
 unit of inverse area: 100 /µm²
 unit of inv. volume : 1000 /µm³
From there, the dimension of any parameter may be
calculated.

There are a number of files within this archive, 
depending on what was done during the session.

backgrounds.csv
 - Contains the list of backgrounds used and
 - Averaged intensities in [kHz]

bg_trace*.csv (where * is an integer)
 - The trace of the background corresponding
   to the line number in backgrounds.csv
 - Time in [ms], Trace in [kHz]

comments.txt
 - Contains page titles and session comment
 - First n lines are titles, rest is session
   comment (where n is total number of pages)

data*.csv (where * is (Number of page))
 - Contains lag times [ms]
 - Contains experimental data, if available

externalweights.txt
 - Contains names (types) of external weights other than from
   Model function or spline fit
 - Linewise: 1st element is page number, 2nd is name
 - According to this data, the following files are present in the archive

externalweights_data_*PageID*_*Type*.csv
 - Contains weighting information of Page *PageID* of type *Type*

model_*ModelID*.txt
 - An external (user-defined) model file with internal ID *ModelID*

Parameters.yaml
 - Contains all Parameters for each page
   Block format:
    - - '#(Number of page): '       
      - (Internal model ID)
      - (List of parameters)
      - (List of checked parameters (for fitting))
      - [(Min channel selected), (Max channel selected)]
      - [(Weighted fit method (0=None, 1=Spline, 2=Model function)), 
         (No. of bins from left and right),
         (No. of knots (of e.g. spline)),
         (Type of fitting algorithm (e.g. "Lev-Mar", "Nelder-Mead")]
      - [B1,B2] Background to use (line in backgrounds.csv)
           B2 is always *null* for autocorrelation curves
      - Data type is Cross-correlation?
      - Parameter id (int) used for normalization in plotting.
        This number first enumerates the model parameters and then
        the supplemental parameters (e.g. "n1").
      - - [min, max] fitting parameter range of 1st parameter
        - [min, max] fitting parameter range of 2nd parameter
        - etc.
 - Order in Parameters.yaml defines order of pages in a session
 - Order in Parameters.yaml defines order in comments.txt

Readme.txt (this file)

Supplements.yaml
 - Contains errors of fitting
   Format:
   -- Page number
    -- [parameter id, error value]
     - [parameter id, error value]
    - Chi squared
    - [pages that share parameters] (from global fitting)
     
trace*.csv (where * is (Number of page) | appendix "A" or "B" point to
            the respective channels (only in cross-correlation mode))
 - Contains times [ms]
 - Contains countrates [kHz]
""".format(doc.__version__)
