// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2017 Thomas Klose & Torsten Schache, Hiperscan GmbH Dresden, Germany
//
//    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 3 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/>.

using System;
using System.IO;
using System.Threading;
using Hiperscan.Unix;

namespace Hiperscan.ApoIdentCommon.ApoConfig.IO
{
    public abstract class SecureFileWriter
    {
        protected abstract void Write(string file_name);
        protected abstract void Read(string file_name);
        public abstract void Clear();
        public abstract bool HasWritingData { get; }

        protected SecureFileWriter(int repeats = 5)
        {
            this.Repeats = repeats;
            this.Init();
        }

        public SecureFileWriter(string file_name, int repeats = 5, int wait_in_ms = 50)
        {
            this.Repeats = repeats;
            this.WaitInMilliSeconds = wait_in_ms;
            this.Init(file_name);
        }

        private void Init()
        {
            this.FoundData = false;
            this.Clear();
        }

        private void Init(string file_name)
        {
            this.FoundData = false;
            this.Clear();

            this.SetFileNames(file_name);

            if (this.IsTest)
                this.Read(this.FileName);
            else
                this.SafetyRead();
        }

        public void SafetyWrite(string file_name)
        {
            this.SetFileNames(file_name);

            if (this.HasWritingData == false)
                return;

            for (int i = 0; i < this.Repeats; i++)
            {
                try
                {
                    this.CreateParentDirectory(this.FileName);
                    this.Write(this.FileName);
                    Thread.Sleep(this.WaitInMilliSeconds);
                    this.CreateInstance();
                    break;

                }
                catch (Exception ex)
                {
                    Thread.Sleep(this.WaitInMilliSeconds);
                    if (i == this.Repeats - 1)
                    {
                        Console.WriteLine("Error while saving {0}: {1}",
                                          this.GetType(),
                                          ex);
                        throw;
                    }
                }
            }

            try
            {
                FileIO.CopyFile(this.FileName, this.BackupFileName, true);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Cannot create {0} backup: {1}",
                                  this.GetType(),
                                  ex);
            }
        }

        private void SafetyRead()
        {
            if (this.SafetyRead(false) == true)
                return;

            this.SafetyRead(true);
        }

        private bool SafetyRead(bool backup)
        {
            string file = backup ? this.BackupFileName : this.FileName;

            if (false == FileIO.ExistsFile(file))
                return false;

            for (int i = 0; i < this.Repeats; i++)
            {
                try
                {
                    this.Read(file);
                    this.FoundData = true;
                    return true;
                }
                catch (Exception ex)
                {
                    this.Clear();
                    Thread.Sleep(this.WaitInMilliSeconds);

                    Console.WriteLine("{0}: loading{1} file failed: {2}",
                                      this.GetType(),
                                      backup ? " backup" : String.Empty,
                                      ex.ToString());
                }
            }

            return false;
        }

        private void CreateParentDirectory(string file_name)
        {
            string dir = Path.GetDirectoryName(file_name);
            FileIO.CreateDirectory(dir);
        }

        private void SetFileNames(string file_name)
        {
            this.FileName = file_name;
            this.BackupFileName = this.GetBackupFileName(file_name);
        }

        private string GetBackupFileName(string origine_filename)
        {
            string ext = Path.GetExtension(origine_filename);
            string file = Path.GetFileNameWithoutExtension(origine_filename);

            string file1 = file + "_backup";
            file1 = file1 + ext;
            file1 = Path.Combine(Path.GetDirectoryName(origine_filename), file1);
            if (FileIO.ExistsFile(file1))
                return file1;

            string file2 = file + "_back_up";
            file2 = Path.ChangeExtension(file2, ext);
            file2 = Path.Combine(Path.GetDirectoryName(origine_filename), file2);
            if (FileIO.ExistsFile(file2))
                this.MoveFile(file2, file1);

            string file3 = origine_filename + "_backup";
            if (FileIO.ExistsFile(file3))
                this.MoveFile(file3, file1);

            return file1;
        }

        private void MoveFile(string source, string destination)
        {
            try
            {
                FileIO.CopyFile(source, destination, true);
                FileIO.DeleteFile(source);
            }
            catch(Exception ex)
            {
                Console.WriteLine("{0}: Could not move file {1} to file {2}: {3}",
                                  this.GetType(),
                                  source,
                                  destination,
                                  ex);
            }
        }

        protected SecureFileWriter CreateInstance()
        {
            return (SecureFileWriter)Activator.CreateInstance(this.GetType(),
                                                               this.FileName, 1);
        }

        private bool IsTest
        {
            get
            {
                return this.Repeats == 1;
            }
        }

        private int Repeats { get; set; }

        public string FileName { get; private set; }
        private string BackupFileName { get; set; }
        private int WaitInMilliSeconds { get; set; }
        public bool FoundData { get; set; }
    }
}

