using System;
using System.Collections.Generic;
using System.IO;
using NLog;
using NcProgramManager.Cnc;
using NcProgramManager.Cnc.Models;
using NcProgramManager.Licensing;

namespace NcProgramManager
{
    internal class Program
    {
        private static readonly Logger _log = LogManager.GetCurrentClassLogger();

        public static int Main(string[] args)
        {
            InputArgs inputArgs;
            try
            {
                inputArgs = ArgParser.Parse(args);
            }
            catch (Exception e)
            {
                _log.Error(e, "Arg parsing failed");
                _log.Info("Gestore Programmi FANUC: {0}", e.Message);
                _log.Info("Errore parametri input. Consulta `-help' per maggiori informazioni.");
                VediErrore(-101, false);
                return -101;
            }

            if (inputArgs.mostraHelp)
            {
                MostraAiuto();
                VediErrore(0, inputArgs.debugMode);
                return 0;
            }

#if !DEBUG
            string licFingerprint = new MachineFingerprint().GetFingerprint();
            string licBase64;
            string licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");
            if (!LicenseFile.TryRead(licPath, out licBase64)
                || !new LicenseValidator().Validate(licFingerprint, licBase64))
            {
                _log.Info("Licenza ERRATA");
                VediErrore(-100, inputArgs.debugMode);
                return -1;
            }
#else
            _log.Info("DEBUG build - licenza non richiesta");
#endif

            bool needsIp = inputArgs.manufacturer != CncManufacturer.Fanuc || inputArgs.nodoHssb == -1;
            if (inputArgs.azione == "" || (needsIp && string.IsNullOrEmpty(inputArgs.ip)) || inputArgs.pathLocaleProgramma == "")
            {
                VediErrore(-102, inputArgs.debugMode);
                return -102;
            }

            _log.Info("manufacturer: {0}", inputArgs.manufacturer);
            _log.Info("IndirizzoIP: {0}", inputArgs.ip);
            _log.Info("Porta: {0}", inputArgs.porta);
            _log.Info("Compatibility: {0}", inputArgs.compatibilityMode);
            _log.Info("commentoProgramma: {0}", inputArgs.commentoProgramma);
            _log.Info("pathCNC: {0}", inputArgs.pathCNC);
            _log.Info("pathLocaleProgramma: {0}", inputArgs.pathLocaleProgramma);

            ICncMachine machine = null;
            try
            {
                machine = CncMachineFactory.Create(inputArgs);

                switch (inputArgs.azione.ToUpperInvariant())
                {
                    case "SCARICA": return Scarica(machine, inputArgs);
                    case "INVIA":   return Invia(machine, inputArgs);
                    default:
                        VediErrore(-103, inputArgs.debugMode);
                        return -103;
                }
            }
            finally
            {
                if (machine != null)
                    machine.Dispose();
            }
        }

        static int Scarica(ICncMachine macchina, InputArgs inputArgs)
        {
            _log.Info("RICEVI DA CNC-->");
            bool connected = false;
            try
            {
                var connResult = macchina.ConnectAsync().GetAwaiter().GetResult();
                if (!connResult.Success)
                {
                    _log.Info("Macchina non connessa");
                    PrintErrors(connResult.Errors);
                    VediErrore(-201, inputArgs.debugMode);
                    return -201;
                }
                connected = true;

                _log.Info("Inizio RICEZIONE NCProgram");
                var progResult = macchina.ReadProgramAsync(inputArgs.pathCNC.ToString()).GetAwaiter().GetResult();
                _log.Info("Fine RICEZIONE NCProgram");
                if (!progResult.Success || string.IsNullOrEmpty(progResult.Value))
                {
                    PrintErrors(progResult.Errors);
                    VediErrore(-204, inputArgs.debugMode);
                    return -204;
                }

                FanucProgram programma = new FanucProgram(progResult.Value);
                IFanucMachine fanuc = macchina as IFanucMachine;

                if (inputArgs.hasToolOffsetData)
                {
                    if (fanuc == null)
                    {
                        _log.Info("ToolOffset non supportato per questo produttore");
                        VediErrore(-205, inputArgs.debugMode);
                        return -205;
                    }
                    _log.Info("Inizio RICEZIONE ToolOffsetData");
                    var toolResult = fanuc.ReadToolDataAsync(inputArgs.pathCNC.ToString()).GetAwaiter().GetResult();
                    _log.Info("Fine RICEZIONE ToolOffsetData");
                    if (!toolResult.Success || string.IsNullOrEmpty(toolResult.Value))
                    {
                        PrintErrors(toolResult.Errors);
                        VediErrore(-205, inputArgs.debugMode);
                        return -205;
                    }
                    programma.setValue(toolResult.Value, FanucProgram.FILE_TYPE.TOOL_OFFEST_DATA);
                }

                if (inputArgs.hasWorkZeroOffsetData)
                {
                    if (fanuc == null)
                    {
                        _log.Info("WorkZeroOffset non supportato per questo produttore");
                        VediErrore(-206, inputArgs.debugMode);
                        return -206;
                    }
                    _log.Info("Inizio RICEZIONE WorkZeroOffsetData");
                    var workResult = fanuc.ReadWorkZeroDataAsync(inputArgs.pathCNC.ToString()).GetAwaiter().GetResult();
                    _log.Info("Fine RICEZIONE WorkZeroOffsetData");
                    if (!workResult.Success || string.IsNullOrEmpty(workResult.Value))
                    {
                        PrintErrors(workResult.Errors);
                        VediErrore(-206, inputArgs.debugMode);
                        return -206;
                    }
                    programma.setValue(workResult.Value, FanucProgram.FILE_TYPE.WORK_ZERO_OFFSET);
                }

                File.WriteAllText(inputArgs.pathLocaleProgramma, programma.ToString());
                VediErrore(0, inputArgs.debugMode);
                return 0;
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Scarica failed");
                _log.Info("{0}", ex.Message);
                VediErrore(-202, inputArgs.debugMode);
                return -202;
            }
            finally
            {
                if (connected)
                    macchina.DisconnectAsync().GetAwaiter().GetResult();
            }
        }

        static int Invia(ICncMachine macchina, InputArgs inputArgs)
        {
            _log.Info("INVIA A CNC-->");
            bool connected = false;
            try
            {
                var connResult = macchina.ConnectAsync().GetAwaiter().GetResult();
                if (!connResult.Success)
                {
                    _log.Info("Macchina non connessa");
                    PrintErrors(connResult.Errors);
                    VediErrore(-201, inputArgs.debugMode);
                    return -201;
                }
                connected = true;

                FanucProgram programma = new FanucProgram(File.ReadAllText(inputArgs.pathLocaleProgramma));
                if (!string.IsNullOrEmpty(inputArgs.commentoProgramma))
                    programma.ChangeComment(inputArgs.commentoProgramma);

                if (programma.Program != null)
                {
                    _log.Info("Inizio INVIO NCProgram");
                    var cncProg = new CncProgram
                    {
                        Name    = programma.GetProgramTitle(),
                        Comment = programma.GetProgramComment(),
                        Content = programma.Program,
                        Path    = inputArgs.pathCNC.ToString()
                    };
                    var writeResult = macchina.WriteProgramAsync(inputArgs.pathCNC.ToString(), cncProg).GetAwaiter().GetResult();
                    _log.Info("Fine INVIO NCProgram");
                    if (!writeResult.Success)
                    {
                        PrintErrors(writeResult.Errors);
                        VediErrore(-204, inputArgs.debugMode);
                        return -204;
                    }
                }

                IFanucMachine fanuc = macchina as IFanucMachine;

                if (programma.ToolOffsetData != null && inputArgs.hasToolOffsetData)
                {
                    if (fanuc == null)
                    {
                        _log.Info("ToolOffset non supportato per questo produttore");
                        VediErrore(-205, inputArgs.debugMode);
                        return -205;
                    }
                    _log.Info("Inizio INVIO ToolOffsetData");
                    var toolResult = fanuc.WriteToolDataAsync(inputArgs.pathCNC.ToString(), programma.ToolOffsetData).GetAwaiter().GetResult();
                    _log.Info("Fine INVIO ToolOffsetData");
                    if (!toolResult.Success)
                    {
                        PrintErrors(toolResult.Errors);
                        VediErrore(-205, inputArgs.debugMode);
                        return -205;
                    }
                }

                if (programma.WorkZeroOffsetData != null && inputArgs.hasWorkZeroOffsetData)
                {
                    if (fanuc == null)
                    {
                        _log.Info("WorkZeroOffset non supportato per questo produttore");
                        VediErrore(-206, inputArgs.debugMode);
                        return -206;
                    }
                    _log.Info("Inizio INVIO WorkZeroOffsetData");
                    var workResult = fanuc.WriteWorkZeroDataAsync(inputArgs.pathCNC.ToString(), programma.WorkZeroOffsetData).GetAwaiter().GetResult();
                    _log.Info("Fine INVIO WorkZeroOffsetData");
                    if (!workResult.Success)
                    {
                        PrintErrors(workResult.Errors);
                        VediErrore(-206, inputArgs.debugMode);
                        return -206;
                    }
                }

                VediErrore(0, inputArgs.debugMode);
                return 0;
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Invia failed");
                _log.Info("{0}", ex.Message);
                VediErrore(-202, inputArgs.debugMode);
                return -202;
            }
            finally
            {
                if (connected)
                    macchina.DisconnectAsync().GetAwaiter().GetResult();
            }
        }

        static void PrintErrors(IReadOnlyList<CncError> errors)
        {
            if (errors == null) return;
            foreach (var e in errors)
                _log.Info("  {0}", e.ToString());
        }

        static void VediErrore(int errore, bool debugMode)
        {
            _log.Info("Errore:{0}", errore);
            _log.Info("");
            _log.Info("Codici Errori:");
            _log.Info("-100 : Licenza Errata/non valida.");
            _log.Info("-101 : Impossibile parsare i parametri.");
            _log.Info("-102 : Parametri mancanti. ");
            _log.Info("-103 : Codice connessione errato: 3->Ethernet 2->HSSB. ");
            _log.Info("-201 : Macchina non connessa. ");
            _log.Info("-202 : Eccezione nell'esecuzione dell'AZIONE. ");
            _log.Info("-203 : L'azione è fallita. ");
            _log.Info("-204 : Errore in fase invio/ricezione Programma CN. ");
            _log.Info("-205 : Errore in fase invio/ricezione tool offset data ");
            _log.Info("-206 : Errore in fase invio/ricezione work zero offest data");
            _log.Info("-207 : Posizionare il CN in MDI o EDIT per eseguire l'invio di un programma");
            _log.Info("-208 : Programma CN Caricato ma non settato a main.");
            if (debugMode)
                Console.ReadLine();
        }

        static void MostraAiuto()
        {
            _log.Info("Utilizzo del Software gestore Programmi FANUC");
            _log.Info("Le funzioni pricipali sono due.");
            _log.Info("INVIA: caricamento di programma su CNC.");
            _log.Info("SCARICA: scaricamento di programma su CNC.");
            _log.Info("");
            _log.Info("");
            _log.Info("Lista dei parametri da fornire all'eseguibile\nDevono essere separati da uno spazio tra l'uno e l'altro,\nnon andare a capo e non lasciare spazi dopo l'= ");
            _log.Info("");
            _log.Info("Il file 'license.lic' deve essere posizionato nella stessa cartella dell'eseguibile.");
            _log.Info("-azione=");
            _log.Info("    l'{AZIONE} da eseguire.");
            _log.Info("-manufacturer=");
            _log.Info("    Il produttore del CNC: fanuc (default), heidenhain, siemens, mitsubishi, fagor");
            _log.Info("-tipo=");
            _log.Info("    Il tipo di connessione utilizzato dalla macchina: 3 per ethernet\n 2 per HSSB (solo Fanuc, legacy)");
            _log.Info("-ip=");
            _log.Info("    Dato per Ethernet indirizzo IP della macchina");
            _log.Info("-porta=");
            _log.Info("    Dato per Ethernet PORTA di connessione");
            _log.Info("-nodo=");
            _log.Info("    Dato per HSSB Nodo di connesione (solo Fanuc)");
            _log.Info("-username=");
            _log.Info("    Username per autenticazione (Heidenhain/Siemens/Mitsubishi/Fagor)");
            _log.Info("-password=");
            _log.Info("    Password per autenticazione (Heidenhain/Siemens/Mitsubishi/Fagor)");
            _log.Info("-compatibility");
            _log.Info("    Attivazione modalità compatibilità");
            _log.Info("-commento=");
            _log.Info("    Commento da applicare al programma scelto");
            _log.Info("-path=");
            _log.Info("    Path dei programmi del CNC dove vengono salvati i programmi, se non definito è impostato a 1 cioè //CNC_MEM/USER/PATH1/");
            _log.Info("-pathprogramma=");
            _log.Info("    Necessario per INVIO: Path del programma locale da caricare sul CNC");
            _log.Info("-workzero=");
            _log.Info("    Invia/Scarica anche i dati relativi al workzero offset");
            _log.Info("-tooloffset=");
            _log.Info("    Invia/Scarica anche i dati relativi al tool offset");
            _log.Info("-debug=");
            _log.Info("    Mantiene il terminale aperto alla fine delle operazioni");
            _log.Info("-help");
            _log.Info("    Mostra questo messaggio e termina");
            _log.Info("");
            _log.Info("Codici Errori:");
            _log.Info("-100 : Licenza Errata/non valida.");
            _log.Info("-101 : Impossibile parsare i parametri.");
            _log.Info("-102 : Parametri mancanti. ");
            _log.Info("-103 : Codice connessione errato: 3->Ethernet 2->HSSB. ");
            _log.Info("-201 : Macchina non connessa. ");
            _log.Info("-202 : Eccezione nell'esecuzione dell'AZIONE. ");
            _log.Info("-203 : L'azione è fallita. ");
            _log.Info("-204 : Errore in fase invio/ricezione Programma CN. ");
            _log.Info("-205 : Errore in fase invio/ricezione tool offset data ");
            _log.Info("-206 : Errore in fase invio/ricezione work zero offest data");
            _log.Info("-207 : Posizionare il CN in MDI o EDIT per eseguire l'invio di un programma");
            _log.Info("-208 : Programma CN Caricato ma non settato a main.");
        }
    }
}
