/* Copyright 2014-2023 askanimus@gmail.com */

/* This File is part of "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2).
 *
 * "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2) 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.
 *
 * "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2) 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 "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2).  If not, see <http://www.gnu.org/licenses/>.
 *
 * Diese Datei ist Teil von "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2).
 *
 * "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2) ist Freie Software: Sie können es unter den Bedingungen
 * der GNU General Public License, wie von der Free Software Foundation,
 * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
 * veröffentlichten Version, weiter verteilen und/oder modifizieren.
 *
 * "Arbeitszeitkonto" (askanimus.arbeitszeiterfassung2) wird in der Hoffnung, dass es nützlich sein wird, aber
 * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
 * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
 * Siehe die GNU General Public License für weitere Details.
 *
 * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
 * Programm erhalten haben. Wenn nicht, siehe <https://www.gnu.org/licenses/>.
 */
package askanimus.arbeitszeiterfassung2.datensicherung;


import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.provider.DocumentsContract;

import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;

import askanimus.arbeitszeiterfassung2.R;
import askanimus.arbeitszeiterfassung2.StorageHelper;
import askanimus.arbeitszeiterfassung2.datenbank.DatenbankHelper;
import askanimus.arbeitszeiterfassung2.setup.ASettings;
import askanimus.arbeitszeiterfassung2.setup.ISettings;

public class Datenbank_Backup {
    static final String DATEI_TYP_DB = "application/x-sqlite3";
    //Sql Verbindung
    //private SQLiteDatabase mDatenbank;

    StorageHelper mStorageHelper;

    private final String ERSATZ_HOCHKOMMA = " &apos;";
    private final String ERSATZ_ANFUERUNGSSTRICHE = " &quot;";

    private final Context mContext;

    /**
     * Konstruktor
     *
     */
    public Datenbank_Backup(
            @NonNull Context context,
            //@NonNull SQLiteDatabase paramSQLiteDatabase,
            @NonNull StorageHelper storageHelper
    ) {
        //this.mDatenbank = paramSQLiteDatabase;

        //BACKUP_PATH = pfad;
        mStorageHelper = storageHelper;
        mContext = context;
    }

    /**
     * Sichert die datenbank.
     * Im Script werden als String[] die Tabellen angebenen.
     * Spalten holt sich die Funktion selber.
     *
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws TransformerConfigurationException
     * @throws TransformerException
     * @throws TransformerFactoryConfigurationError
     * @throws OutOfMemoryError
     */
    /*public String backupXML() throws
            ParserConfigurationException,
            IOException,
            TransformerConfigurationException,
            TransformerException,
            TransformerFactoryConfigurationError,
            OutOfMemoryError {
        // Filnamen zusammensetzen
        SimpleDateFormat datumsstring = new SimpleDateFormat("dd-MM-yyyy_HH.mm.ss", Locale.getDefault());

        String filename = ASettings.res.getString(
                R.string.sich_vom_datei) +
                datumsstring.format(new Date());

        //Hier alle Tabellen auflisten die gesichert werden sollen
        String[] tables = {
                Datenbank.DB_T_TAG,
                Datenbank.DB_T_MONAT,
                Datenbank.DB_T_JOB,
                Datenbank.DB_T_SETTINGS,
                Datenbank.DB_T_EORT,
                Datenbank.DB_T_ABWESENHEIT,
                Datenbank.DB_T_SCHICHT_DEFAULT,
                Datenbank.DB_T_SCHICHT,
                Datenbank.DB_T_AUTOREPORT,
                Datenbank.DB_T_JAHR,
                Datenbank.DB_T_ZUSATZFELD,
                Datenbank.DB_T_ZUSATZWERT_DEFAULT,
                Datenbank.DB_T_ZUSATZWERT,
                Datenbank.DB_T_ZUSATZWERT_AUSWAHL
        };

        Document localDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element localElement1 = localDocument.createElement("backup");
        localDocument.appendChild(localElement1);
        Cursor localCursor;
        for (String table : tables) {
            localCursor = this.connection.rawQuery("select * from " + table, null);
            Element localElement55 = localDocument.createElement("table");
            localElement55.setAttribute("name", table);
            localElement1.appendChild(localElement55);
            while (localCursor.moveToNext()) {
                String[] arrayOfString = localCursor.getColumnNames();
                Element localElement2 = localDocument.createElement("item");
                localElement55.appendChild(localElement2);
                for (int i = 0; i < arrayOfString.length; i++) {
                    Element localElement3 = localDocument.createElement(arrayOfString[i]);

                    String content;
                    if (localCursor.getString(i) != null) {
                        content = localCursor.getString(i).replace("'", ERSATZ_HOCHKOMMA);
                        content = content.replace("\"", ERSATZ_ANFUERUNGSSTRICHE);
                        localElement3.appendChild(localDocument.createTextNode(content));
                        localElement2.appendChild(localElement3);
                    }

                }
            } // ende while
            localCursor.close();
        } //ende for tables

        // Backupdatei schreiben
        DocumentFile backupFile = mStorageHelper.getVerzeichnisFile();
        if (backupFile != null && backupFile.exists()) {
            Uri docUri;
            try {
                docUri = DocumentsContract.createDocument(
                        mContext.getContentResolver(),
                        backupFile.getUri(),
                        IExport_Basis.DATEI_TYP_XML,
                        filename
                );
            } catch (Exception e) {
                //filename = null;
                throw new IOException();
            }

            if (docUri != null) {
                StreamResult result = new StreamResult();
                result.setOutputStream(
                        mContext
                                .getContentResolver()
                                .openOutputStream(docUri));
                TransformerFactory.newInstance().newTransformer().transform(new DOMSource(localDocument), result);
            } else {
                //filename = null;
                throw new IOException();
            }
        } else {
            //filename = null;
            throw new IOException();

        }
        return filename;
    }*/

    public String backup() throws
            IOException {

        // die Datenbank schliessen
        if(ASettings.stundenDBHelper != null) {
            ASettings.stundenDBHelper.close();
        }

        // Name der Sicherung zusammenstellen
        SimpleDateFormat datumsstring = new SimpleDateFormat("dd-MM-yyyy_HH.mm.ss", Locale.getDefault());

        String outFileName = ASettings.res.getString(
                R.string.sich_vom_datei) +
                datumsstring.format(new Date()) + ".db";

        //Datenbankpfad
        final String inFileName = mContext.getDatabasePath(
                mContext.getResources().getString(R.string.dbName)
        ).toString();

        DocumentFile backupFile = mStorageHelper.getVerzeichnisFile();
        if (backupFile != null && backupFile.exists()) {
            Uri zielURI;
            try {
                zielURI = DocumentsContract.createDocument(
                        mContext.getContentResolver(),
                        backupFile.getUri(),
                        DATEI_TYP_DB,
                        outFileName
                );
            } catch (Exception e) {
                //filename = null;
                throw new IOException(e.getMessage());
            }

            if (zielURI != null) {
                File dbFile = new File(inFileName);
                FileInputStream fis = new FileInputStream(dbFile);

                // die Sicherungsdatei öffnen
                OutputStream output = mContext.getContentResolver().openOutputStream(zielURI);

                // Datenbankdatei in die Sicherungsdatei kopieren
                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) > 0) {
                    output.write(buffer, 0, length);
                }

                // Close the streams
                output.flush();
                output.close();
                fis.close();

            } else {
                //filename = null;
                throw new IOException();
            }
        } else {
            //filename = null;
            throw new IOException();

        }

         // die Datenbank wieder öffnen
        ASettings.stundenDBHelper = new DatenbankHelper(mContext);
        ASettings.mDatenbank = ASettings.stundenDBHelper.getWritableDatabase();
        return outFileName;
    }

    /**
     * Nimmt die database.xml Datei und stellt die datenbank wieder her
     */
    public long restoreXML(String restore_file) throws Exception {
        // SQLiteDatabase mDatenbank = ASettings.stundenDBHelper.getWritableDatabase();
        Document localDocument = null;
        long appVersion = -1;

        DocumentFile restoreFile = mStorageHelper.getVerzeichnisFile();
        if (restoreFile != null && restoreFile.exists()) {
            restoreFile = restoreFile.findFile(restore_file);

            if (restoreFile != null && restoreFile.exists()) {
                InputStream inputStream = mContext.getContentResolver()
                        .openInputStream(restoreFile.getUri());
                localDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream);
            }

            if (localDocument != null) {
                localDocument.getDocumentElement().normalize();
                NodeList localNodeList = localDocument.getElementsByTagName("table");


                for (int i = localNodeList.getLength() - 1; i >= 0; i--) {

                    Node localNode = localNodeList.item(i);
                    if (localNode.getNodeType() == 1) {

                        Element localElement = (Element) localNode;
                        ASettings.mDatenbank.execSQL("DELETE FROM " + localElement.getAttribute("name"));

                        NodeList nl = localElement.getElementsByTagName("item");

                        for (int a = 0; a < nl.getLength(); a++) {
                            ArrayList<String> aSpalten = new ArrayList<>();
                            ArrayList<String> aWerte = new ArrayList<>();

                            Node el = nl.item(a);

                            NodeList spalten = el.getChildNodes();

                            for (int s = 0; s < spalten.getLength(); s++) {
                                Node node = spalten.item(s);

                                aSpalten.add(node.getNodeName());
                                ///////////konvertieren alter Datenbankbackups < Vers. 9 ///////////////////
                                /*
                                 * Abfrage nach appVersion < 0 stellt sicher, dass nur die erste gefundene Einstellung
                                 * benutzt wird.
                                 * in frühen Versionen der App wurden mehrere Einstellungsversionen in die Datenbank geschrieben
                                 */
                                if (node.getNodeName().equals(DatenbankHelper.DB_F_VERSION) && appVersion < 0) {
                                    String version = node.getTextContent();
                                    if (version != null) {
                                        try {
                                            appVersion = Long.parseLong(version);
                                        } catch (NumberFormatException ne) {
                                            appVersion = 0;
                                        }
                                    } else {
                                        appVersion = 0;
                                    }
                                }
                                aWerte.add(node.getTextContent());
                            }


                            StringBuilder sql = new StringBuilder("INSERT INTO " + localElement.getAttribute("name") + "(");
                            StringBuilder dummy = new StringBuilder();

                            for (String s : aSpalten) {
                                sql.append("`").append(s).append("`");
                                dummy.append("?");
                                if (!aSpalten.get(aSpalten.size() - 1).equals(s)) {
                                    sql.append(",");
                                    dummy.append(",");
                                }
                            }

                            sql.append(") VALUES (").append(dummy).append(");");

                            SQLiteStatement stmt = ASettings.mDatenbank.compileStatement(sql.toString());

                            int x = 1;
                            for (String s : aWerte) {
                                //s = s.replace(ERSATZ_ZEILENUMBRUCH, "\n");
                                s = s.replace(ERSATZ_HOCHKOMMA, "'");
                                s = s.replace(ERSATZ_ANFUERUNGSSTRICHE, "\"");
                                stmt.bindString(x, s);
                                x++;

                                if (x <= aWerte.size()) {
                                    sql.append(",");
                                }
                            }

                            stmt.execute();

                            aSpalten.clear();
                            aWerte.clear();
                        }
                    }
                }
            }
            // wenn eine Sicherung einer Version vor 2.00.00 eingelesen wurde,
            // muss die Datenbank umgebaut werden
            if (appVersion < 200000) {
                ASettings.mDatenbank.execSQL("DROP TABLE IF EXISTS zusatzfeld;");
                ASettings.mDatenbank.execSQL("DROP TABLE IF EXISTS zusatzwert_default;");
                ASettings.mDatenbank.execSQL("DROP TABLE IF EXISTS zusatzwert;");
                DatenbankHelper.zuNeun(ASettings.mDatenbank);
            }

            /*
             * Ab Version 2.01.00 wird, bei Wahl der Urlaubsabrechnung in Stunden,
             * der Urlaubsanspruch und der Resturlaub in Minuten gespeichert
             * beim ersten Start der App nach dem Update bzw. nach dem Wiederherstellen
             * einer alten Datensicherung werden die Werte einmalig umgerechnet
             */
            if (appVersion < 201000) {
                ASettings.mPreferenzen
                        .edit()
                        .putBoolean(ISettings.KEY_URLAUB_ALS_H_UMGERECHENET, false)
                        .apply();
            }

            /*
             * Überzählige Einträge in Einstellungen Tabelle löschen
             * bis Version 2.05.000 wurden mehrere Einstellungen Version gesichert
             */
            if (appVersion < 205001) {
                String count = "SELECT "
                        + DatenbankHelper.DB_F_ID
                        + " FROM "
                        + DatenbankHelper.DB_T_SETTINGS;
                Cursor result = ASettings.mDatenbank.rawQuery(count, null);

                while (result.moveToNext()) {
                    @SuppressLint("Range") long id = result.getLong(result.getColumnIndex(DatenbankHelper.DB_F_ID));
                    if (id != 1) {
                        ASettings.mDatenbank.delete(
                                DatenbankHelper.DB_T_SETTINGS,
                                DatenbankHelper.DB_F_ID + "=?",
                                new String[]{Long.toString(id)}
                        );
                    }
                }

                result.close();
                // ASettings.mDatenbank.close();
            }
            /*
             * Version 2.06.000
             *  - autom. Auszahlung von Überstunden hinzu gekommen
             *      - alle Monate mit 0 ausbezahlten Überstunden auf -1 ausbezahlten Überstunden setzen
             *      - -1 heisst, autom. berechnet, wenn die autom. Auszahlung eingestellt wurde
             *  - Auswahlmenüs für Zusatzwerte hinzu gekommen
             */
            if (appVersion < 206000) {
                DatenbankHelper.zuVierzehn(ASettings.mDatenbank);
            } else  if (appVersion < 209000) {
                /*
                 * neue Tabellen für NFC und Stempeluhr hinzugefügt
                 */
                DatenbankHelper.zuSechszehn(ASettings.mDatenbank);
            }
            /*ASettings.mDatenbank.needUpgrade(
                    Integer.parseInt(mContext.getResources().getString(R.string.dbVersion))
            );*/
        }

       return readAktJob();
    }

    public long restore(String restoreFileName) {
        final String dbName = mContext.getDatabasePath(
                mContext.getResources().getString(R.string.dbName)
        ).toString();

        final String dbKopieName = dbName + "_sic";

        DocumentFile restoreFile = mStorageHelper.getVerzeichnisFile();

        if (restoreFile != null && restoreFile.exists()) {
            restoreFile = restoreFile.findFile(restoreFileName);

            if (restoreFile != null && restoreFile.exists()) {
                InputStream fileInputStream;
                try {
                    fileInputStream = mContext.getContentResolver().openInputStream(restoreFile.getUri());

                    // alte Datenbank verschieben, diese wird zurück geschoben wenn die Wiederherstellung
                    // schief gelaufen ist
                    File dbFile = new File(dbName);
                    File dbKopieFile = new File(dbKopieName);
                    if (dbFile.exists()) {
                        if(ASettings.stundenDBHelper != null) {
                            ASettings.stundenDBHelper.close();
                        }
                        dbFile.renameTo(dbKopieFile);

                        // Öffnen der neuen Datenbank als output stream
                        OutputStream fileOutputStream;
                        try {
                            fileOutputStream = new FileOutputStream(dbName);

                            // Kopieren der Sicherung
                            byte[] buffer = new byte[1024];
                            int length;
                            try {
                                while ((length = fileInputStream.read(buffer)) > 0) {
                                    fileOutputStream.write(buffer, 0, length);
                                }

                                // beide Streams schliessen
                                fileOutputStream.flush();
                                fileOutputStream.close();
                                fileInputStream.close();
                                // die alte Datenbank loeschen
                                dbKopieFile.delete();
                            } catch (IOException ioe) {
                                // die Wiederherstellung ist gescheitert, alte Datenbank wieder an den Start bringen
                                dbKopieFile.renameTo(dbFile);
                                throw new RuntimeException(ioe);
                            }
                        } catch (FileNotFoundException e) {
                            throw new RuntimeException(e);
                        }
                        // die Datenbank wieder öffnen
                        ASettings.stundenDBHelper = new DatenbankHelper(mContext);
                        ASettings.mDatenbank = ASettings.stundenDBHelper.getWritableDatabase();
                    }
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return readAktJob();
    }

    /*
     * ermittelt den aktuellen Job zum Zeitpunkt der Sicherung
     */
    @SuppressLint("Range")
    private long readAktJob(){
        long aktJob = 0;
        // SQLiteDatabase mDatenbank = ASettings.stundenDBHelper.getReadableDatabase();
        String mSQL;
        Cursor mResult;

        mSQL = "SELECT " + DatenbankHelper.DB_F_JOB + " FROM " + DatenbankHelper.DB_T_SETTINGS +
                " WHERE " + DatenbankHelper.DB_F_ID + "=1 LIMIT 1 ";

        mResult = ASettings.mDatenbank.rawQuery(mSQL, null);

        if (mResult.moveToNext()) {
            aktJob = mResult.getLong(mResult.getColumnIndex(DatenbankHelper.DB_F_JOB));
        }
        mResult.close();

        return aktJob;
    }

    /*
     * löscht eine Sicherung aus Verzeichnis
     */
    public boolean delete(String delete_file) {
        DocumentFile deleteFile = mStorageHelper.getVerzeichnisFile().findFile(delete_file);
        if (deleteFile != null && deleteFile.exists()) {
            return deleteFile.delete();
        }
        return false;
    }
}