package _VisualDVM.Repository.Server;
import Common.Database.Database;
import Common.Database.Objects.DBObject;
import Common.Database.Objects.riDBObject;
import Common.Database.RepositoryRefuseException;
import Common.Utils.InterruptThread;
import Common.Utils.Pair;
import Common.Utils.Utils_;
import _VisualDVM.Constants;
import _VisualDVM.Global;
import _VisualDVM.Passes.All.ArchivesBackupPass;
import _VisualDVM.Passes.All.UnzipFolderPass;
import _VisualDVM.Passes.All.ZipFolderPass;
import _VisualDVM.Passes.Server.RepositoryPass;
import _VisualDVM.Repository.EmailMessage;
import _VisualDVM.Utils;
import org.apache.commons.io.FileUtils;
import sun.misc.SignalHandler;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.util.Calendar;
import java.util.Date;
import java.util.Vector;
public abstract class RepositoryServer<D extends Database> {
    //--
    public static ZipFolderPass zip = new ZipFolderPass();
    public static UnzipFolderPass unzip = new UnzipFolderPass();
    public static ArchivesBackupPass backupSession = new ArchivesBackupPass();
    ;
    //--
    public static Vector<Pair<String, EmailMessage>> EmailMessagesQueue = null;
    protected static FileWriter Log;
    protected static boolean printOn = true;
    //-
    public D db;
    protected Socket clientSocket; //сокет для общения
    protected ServerSocket server; // серверсокет
    protected ObjectInputStream in; // поток чтения из сокета
    protected ObjectOutputStream out; // поток записи в сокет
    protected ServerExchangeUnit_2021 request;
    protected ServerExchangeUnit_2021 response;
    //-
    protected ServerCode code;
    protected long count = 0; //для отладки.
    EmailChecker emailChecker = null;
    BackUpsChecker backUpsChecker = null;
    /*
      public static Vector<Pair<Machine, User>> storages = new Vector<>(
              Arrays.asList(new Pair<>(new Machine("titan",
                      "dvmh.keldysh.ru",
                      22, MachineType.Server),
                      new User("dvmuser1", "mprit_2011"))
              ));
      //-
       */
    Class<D> d_class;
    //-----------RECURSION ->
    SignalHandler signalHandler = signal -> {
    };
    //--
    public RepositoryServer(Class<D> d_class_in) {
        d_class = d_class_in;
    }
    protected static void ResetLog() {
        if (printOn) {
            try {
                Log = new FileWriter("Log.txt", false);
                Log.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    protected static void Print(String message) {
        if (printOn) {
            try {
                Log = new FileWriter("Log.txt", true);
                String dmessage = Utils_.Brackets("SESSION -> ") + new Date() +
                        " " + message;
                Log.write(dmessage + "\n");
                Log.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    public String getServerFileName() {
        return Constants.ApplicationFileName;
    }
    public abstract String getServerHomeName();
    //-DVMTestingChecker
    public abstract int getPort();
    protected void Session() throws Exception {
    }
    ;
    protected void UnsafeSession() throws Exception {
    }
    protected void startAdditionalThreads() {
    }
    public void ActivateDB() {
        try {
            db = d_class.newInstance();
            db.Activate();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public boolean canDelete(DBObject object) throws Exception {
        return true;
    }
    public void StartAction() throws Exception {
    }
    public void Start() throws Exception {
        DiagnosticSignalHandler.install("TERM", signalHandler);
        DiagnosticSignalHandler.install("INT", signalHandler);
        DiagnosticSignalHandler.install("ABRT", signalHandler);
        //--
        EmailMessagesQueue = new Vector<>();
        emailChecker = new EmailChecker(this);
        backUpsChecker = new BackUpsChecker(this);
        //--
        new InterruptThread(10000,
                () -> {
                    System.exit(0);
                    return null;
                }).start();
        //--
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    emailChecker.Perform();
                }
            }
        }).start();
        //--
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    backUpsChecker.Perform();
                }
            }
        }).start();
        //--
        startAdditionalThreads();
        //--
        server = new ServerSocket(getPort());
        StartAction();
        while (true) {
            try {
                clientSocket = server.accept();
                ResetLog();
                Print((count++) + " клиент присоединился, IP=" + clientSocket.getInetAddress());
                code = ServerCode.Undefined;
                out = new ObjectOutputStream(clientSocket.getOutputStream());
                in = new ObjectInputStream(clientSocket.getInputStream());
                //->
                DBObject dbObject = null;
                Pair<Class, Object> p = null;
                Print("Ожидание команды от клиента...");
                Object transport = in.readObject();
                Print("Команда прочитана.");
                //-->
                if (transport instanceof ServerExchangeUnit_2021) {
                    request = (ServerExchangeUnit_2021) transport;
                    response = new ServerExchangeUnit_2021(ServerCode.OK);
                    Print("клиент: <- " + (request.codeName));
                    //--
                    try {
                        code = request.getCode();
                        //--
                        if (transport instanceof SafeServerExchangeUnit) {
                            SafeServerExchangeUnit safe_request = (SafeServerExchangeUnit) transport;
                            if (safe_request.version != Constants.version) {
                                //версия не совпала. не даем работать.
                                throw new RepositoryRefuseException("Версия клиента не совпадает с версией сервера!");
                            }
                            //БЕЗОПАСНАЯ СЕССИЯ
                            switch (code) {
                                case Email:
                                    Email();
                                    break;
                                case GetFirstEmailToSend:
                                    GetFirstEmailToSend();
                                    break;
                                case CreateBackUp:
                                    CreateBackUp();
                                    break;
                                case EditObject:
                                    EditObject();
                                    break;
                                case GetObjectCopyByPK:
                                    GetObjectCopyByPK();
                                    break;
                                case GetObjectsCopiesByPK:
                                    GetObjectsCopiesByPK();
                                    break;
                                case PublishObject:
                                    PublishObject();
                                    break;
                                case PublishObjects:
                                    PublishObjects();
                                    break;
                                case DeleteObjectByPK:
                                    DeleteObjectByPK();
                                    break;
                                case DeleteObjectsByPK:
                                    DeleteObjectsByPK();
                                    break;
                                case CloneObjectByPK:
                                    CloneRObject();
                                    break;
                                default:
                                    Session();
                                    break;
                            }
                        } else {
                            //ОПАСНАЯ СЕССИЯ
                            switch (code) {
                                case Ping:
                                    Ping();
                                    break;
                                case ReceiveFile:
                                    ReceiveFile();
                                    break;
                                case Email:
                                    Email();
                                    break;
                                default:
                                    UnsafeSession();
                                    break;
                            }
                        }
                    } catch (Exception ex) {
                        response = new ServerExchangeUnit_2021(ServerCode.FAIL, "Исключение сервера", ex);
                    } finally {
                        Print("сервер: -> " + response.codeName);
                        out.writeObject(response);
                        Print("Ответ отправлен.");
                    }
                }
                Print("Соединение с клиентом завершено.");
                //->
            } catch (Exception ex) {
                Print("Исключение.Соединение с клиентом завершено.");
            } finally {
                //->
                try {
                    if (clientSocket != null)
                        clientSocket.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                // потоки тоже хорошо бы закрыть
                try {
                    if (in != null)
                        in.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                try {
                    if (out != null)
                        out.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                Print("Сервер ждет следующего клиента.");
            }
        }
    }
    //--
    protected void afterCloneAction(riDBObject src, riDBObject dst) throws Exception {
    }
    protected void beforePublishAction(DBObject object) throws Exception {
    }
    protected void afterPublishAction(DBObject object) throws Exception {
    }
    protected void afterEditAction(DBObject object) throws Exception {
    }
    protected void afterDeleteAction(DBObject object) throws Exception {
    }
    //--
    void Ping() {
    }
    public boolean PingFromClient() {
        RepositoryPass pingPass = new RepositoryPass(this) {
            boolean success;
            @Override
            protected boolean needsAnimation() {
                return true;
            }
            @Override
            protected boolean canStart(Object... args) throws Exception {
                success = false;
                return true;
            }
            @Override
            public String getDescription() {
                return "Ping";
            }
            @Override
            protected void body() throws Exception {
                try {
                    super.body();
                    success = true;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            @Override
            protected void ServerAction() throws Exception {
                unsafe_command(new ServerExchangeUnit_2021(ServerCode.Ping));
            }
            @Override
            protected boolean validate() {
                return success;
            }
        };
        return pingPass.Do();
    }
    void ReceiveFile() throws Exception {
        File file = new File(request.arg);
        response.object = file.exists() ? Utils_.fileToBytes(file) : null;
    }
    void CreateBackUp() throws Exception {
        //определить имя папки с багом.
        System.out.println("create backup");
        boolean force = (boolean) request.object;
        //--
        Calendar rightNow = Calendar.getInstance();
        int year = rightNow.get(Calendar.YEAR);
        int month = rightNow.get(Calendar.MONTH);
        int day = rightNow.get(Calendar.DAY_OF_MONTH);
        //--
        String backUpName = year + "_" + (month + 1) + "_" + (day);
        System.out.println("name=" + backUpName);
        File todayBackUp = new File(Global.DataBackUpsDirectory, backUpName);
        File todayBackUpArchive = new File(Global.DataBackUpsDirectory, backUpName + ".zip");
        if (force || todayBackUpArchive.exists())
            Utils_.forceDeleteWithCheck(todayBackUpArchive);
        //-
        // Чистка старых бекапов на самом сервере.
        Utils.keepNewFiles(todayBackUp.getParentFile(), 2);
        if (!todayBackUpArchive.exists()) {
            FileUtils.forceMkdir(todayBackUp);
            //--
            File dbBackUp = new File(todayBackUp, db.getFile().getName());
            Files.copy(db.getFile().toPath(), dbBackUp.toPath());
            //--перегружаемая часть.
            extraBackup(todayBackUp);
            //--- общая часть.
            zip.Do(todayBackUp.getAbsolutePath(), todayBackUpArchive.getAbsolutePath());
            Utils_.forceDeleteWithCheck(todayBackUp);
            //-
            // for (Pair<Machine, User> cred : storages) {
            //     backupSession.Do(cred.getKey(), cred.getValue(), todayBackUpArchive);
            // }
            //bonus backup
            if (!force && rightNow.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) {
                System.out.println("MONDAY");
                //на почту шлем только базу.
                EmailMessage message = new EmailMessage(getClass().getSimpleName() + " BackUp", "Резервная копия базы данных");
                message.addAttachement(db.getFile());
                for (String address : Constants.admins_mails)
                    EmailMessagesQueue.add(new Pair<>(address, message));
                //--
            }
        }
    }
    protected void extraBackup(File todayBackUp) {
    }
    //--
    public Database getDb() {
        return db;
    }
    public void checkExistense(Database database, Object pk, Class d_class) throws Exception {
        if (!database.getTable(d_class).containsKey(pk))
            throw new RepositoryRefuseException(
                    database.getTable(d_class).getSingleDescription() +
                            " с ключом " +
                            Utils_.Brackets(pk.toString()) +
                            " не существует");
    }
    public void checkExistense(Object pk, Class d_class) throws Exception {
        checkExistense(db, pk, d_class);
    }
    //-------------
    void publishObject_(Database database, DBObject dbObject) throws Exception {
        beforePublishAction(dbObject);
        response.object = (Serializable) database.InsertS(dbObject).getPK();
        afterPublishAction(dbObject);
    }
    void editObject_(Database database, DBObject dbObject) throws Exception {
        database.UpdateWithCheck(dbObject);
        afterEditAction(dbObject);
    }
    void deleteObject_(Database database, Pair<Class, Object> to_delete) throws Exception {
        DBObject dbObject = database.DeleteByPK(to_delete.getKey(), to_delete.getValue());
        afterDeleteAction(dbObject);
        database.DeleteDependencies(dbObject);
    }
    //--
    void PublishObject() throws Exception {
        publishObject_(getDb(), (DBObject) request.object);
    }
    void PublishObjects() throws Exception {
        Database database = getDb();
        for (DBObject dbObject : (Vector<DBObject>) request.object)
            publishObject_(database, dbObject);
    }
    void CloneRObject() throws Exception {
        Database database = getDb();
        Pair<Class, Object> to_clone = (Pair<Class, Object>) request.object;
        Object pk = to_clone.getValue();
        //--
        if (database.getTable(to_clone.getKey()).containsKey(to_clone.getValue())) {
            riDBObject src = (riDBObject) database.getTable(to_clone.getKey()).get(to_clone.getValue());
            riDBObject dst = (riDBObject) to_clone.getKey().newInstance();
            dst.SynchronizeFields(src);
            //единственное отличие клона - текущий автор
            dst.description += " копия";
            String[] packed = request.arg.split("\n");
            dst.sender_name = packed[0];
            dst.sender_address = packed[1];
            database.Insert(dst);
            afterCloneAction(src, dst);
            response.object = (Serializable) dst.getPK();
        }
    }
    void GetObjectCopyByPK() throws Exception {
        Pair<Class, Object> p = (Pair<Class, Object>) request.object;
        DBObject dbObject = getDb().getObjectCopyByPK(p.getKey(), p.getValue());
        response.object = dbObject;
    }
    void GetObjectsCopiesByPK() throws Exception {
        Pair<Class, Object> p = (Pair<Class, Object>) request.object;
        response.object = getDb().getObjectsCopies(p.getKey(), (Vector<Object>) p.getValue());
    }
    void EditObject() throws Exception {
        editObject_(getDb(), (DBObject) request.object);
    }
    void DeleteObjectByPK() throws Exception {
        deleteObject_(getDb(), (Pair<Class, Object>) request.object);
    }
    void DeleteObjectsByPK() throws Exception {
        Database database = getDb();
        Pair<Class, Vector<Object>> to_delete = (Pair<Class, Vector<Object>>) request.object;
        for (Object object : to_delete.getValue())
            deleteObject_(database, new Pair<>(to_delete.getKey(), object));
    }
    //--
    void Email() throws Exception {
        //положить в очередь сообщений.
        EmailMessagesQueue.add(new Pair<>(request.arg, (EmailMessage) request.object));
    }
    void GetFirstEmailToSend() throws Exception {
        //
        if (EmailMessagesQueue.isEmpty()) {
            response.object = null;
        } else {
            response.object = EmailMessagesQueue.lastElement();
            EmailMessagesQueue.removeElementAt(EmailMessagesQueue.size() - 1);
        }
    }
}
