/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
//     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/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2021 Natalia Portillo
*******************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Blazorise;
using Marechai.Helpers;
using Marechai.Shared;
using Marechai.ViewModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Tewr.Blazor.FileReader;

namespace Marechai.Pages.Admin.Details
{
    public partial class CreateMachinePhoto
    {
        const int                     _maxUploadSize = 25 * 1048576;
        bool?                         _addToDatabase;
        bool                          _allFinished;
        AuthenticationState           _authState;
        bool?                         _convertAvif1440;
        bool?                         _convertAvif1440Th;
        bool?                         _convertAvif4k;
        bool?                         _convertAvif4kTh;
        bool?                         _convertAvifHd;
        bool?                         _convertAvifHdTh;
        bool?                         _convertHeif1440;
        bool?                         _convertHeif1440Th;
        bool?                         _convertHeif4k;
        bool?                         _convertHeif4kTh;
        bool?                         _convertHeifHd;
        bool?                         _convertHeifHdTh;
        bool?                         _convertJp2k1440;
        bool?                         _convertJp2k1440Th;
        bool?                         _convertJp2k4k;
        bool?                         _convertJp2k4kTh;
        bool?                         _convertJp2kHd;
        bool?                         _convertJp2kHdTh;
        bool?                         _convertJpeg1440;
        bool?                         _convertJpeg1440Th;
        bool?                         _convertJpeg4k;
        bool?                         _convertJpeg4kTh;
        bool?                         _convertJpegHd;
        bool?                         _convertJpegHdTh;
        bool?                         _convertWebp1440;
        bool?                         _convertWebp1440Th;
        bool?                         _convertWebp4k;
        bool?                         _convertWebp4kTh;
        bool?                         _convertWebpHd;
        bool?                         _convertWebpHdTh;
        bool?                         _extractExif;
        string                        _imageFormat;
        ElementReference              _inputUpload;
        int                           _licenseId;
        List<Database.Models.License> _licenses;
        bool                          _loaded;
        MachineViewModel              _machine;
        MachinePhotoViewModel         _model;
        bool?                         _moveFile;
        double                        _progressValue;
        string                        _sourceUrl;
        bool                          _unknownSource;
        bool                          _uploaded;
        bool                          _uploadError;
        string                        _uploadErrorMessage;
        bool                          _uploading;
        [Parameter]
        public Guid Id { get; set; }
        [Parameter]
        public int MachineId { get; set; }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if(_loaded)
                return;

            _licenses  = await LicensesService.GetAsync();
            _machine   = await MachinesService.GetAsync(MachineId);
            _authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();

            if(_machine is null)
                NavigationManager.ToBaseRelativePath("admin/machines");

            _loaded = true;

            StateHasChanged();
        }

        [SuppressMessage("ReSharper", "InconsistentNaming")]
        async Task UploadFile()
        {
            if(!_unknownSource &&
               string.IsNullOrWhiteSpace(_sourceUrl))
                return;

            if(_licenseId == 0)
            {
                _uploadError        = true;
                _uploadErrorMessage = L["Please choose a valid license."];

                return;
            }

            var processExiftool = new Process
            {
                StartInfo =
                {
                    FileName               = "exiftool",
                    CreateNoWindow         = true,
                    RedirectStandardError  = true,
                    RedirectStandardOutput = true
                }
            };

            var processIdentify = new Process
            {
                StartInfo =
                {
                    FileName               = "identify",
                    CreateNoWindow         = true,
                    RedirectStandardError  = true,
                    RedirectStandardOutput = true
                }
            };

            var processConvert = new Process
            {
                StartInfo =
                {
                    FileName               = "convert",
                    CreateNoWindow         = true,
                    RedirectStandardError  = true,
                    RedirectStandardOutput = true
                }
            };

            string identifyOutput;
            string convertOutput;
            string exiftoolOutput;

            try
            {
                processIdentify.Start();
                identifyOutput = await processIdentify.StandardOutput.ReadToEndAsync();
                processIdentify.WaitForExit();
                processConvert.Start();
                convertOutput = await processConvert.StandardOutput.ReadToEndAsync();
                processConvert.WaitForExit();
                processExiftool.Start();
                exiftoolOutput = await processExiftool.StandardOutput.ReadToEndAsync();
                processExiftool.WaitForExit();
            }
            catch(Exception)
            {
                _uploadError        = true;
                _uploadErrorMessage = L["Cannot run ImageMagick please contact the administrator."];

                return;
            }

            IFileReference file = (await FileReaderService.CreateReference(_inputUpload).EnumerateFilesAsync()).
                FirstOrDefault();

            if(file is null)
                return;

            IFileInfo fileInfo = await file.ReadFileInfoAsync();

            if(fileInfo.Size > _maxUploadSize)
            {
                _uploadError        = true;
                _uploadErrorMessage = L["The selected file is too big."];

                return;
            }

            string tmpPath = Path.GetTempFileName();

            FileStream outFs;

            try
            {
                outFs = new FileStream(tmpPath, FileMode.Open, FileAccess.ReadWrite);
            }
            catch(Exception)
            {
                _uploadError        = true;
                _uploadErrorMessage = L["There was an error uploading the file."];

                return;
            }

            _uploading = true;

            await using AsyncDisposableStream fs     = await file.OpenReadAsync();
            byte[]                            buffer = new byte[20480];

            try
            {
                double lastProgress = 0;
                int    count;

                while((count = await fs.ReadAsync(buffer, 0, buffer.Length)) != 0)
                {
                    await outFs.WriteAsync(buffer, 0, count);

                    double progress = (double)fs.Position * 100 / fs.Length;

                    if(!(progress > lastProgress + 0.01))
                        continue;

                    _progressValue = progress;
                    await InvokeAsync(StateHasChanged);
                    await Task.Yield();
                    lastProgress = progress;
                }
            }
            catch(Exception)
            {
                _uploading          = false;
                _uploadError        = true;
                _uploadErrorMessage = L["There was an error uploading the file."];

                return;
            }

            outFs.Close();
            _uploading = false;
            await InvokeAsync(StateHasChanged);
            await Task.Yield();

            processIdentify = new Process
            {
                StartInfo =
                {
                    FileName               = "identify",
                    CreateNoWindow         = true,
                    RedirectStandardError  = true,
                    RedirectStandardOutput = true,
                    ArgumentList =
                    {
                        tmpPath
                    }
                }
            };

            processIdentify.Start();
            identifyOutput = await processIdentify.StandardOutput.ReadToEndAsync();
            processIdentify.WaitForExit();

            if(processIdentify.ExitCode != 0 ||
               string.IsNullOrWhiteSpace(identifyOutput))
            {
                _uploading          = false;
                _uploadError        = true;
                _uploadErrorMessage = L["The uploaded file was not recognized as an image."];
                File.Delete(tmpPath);

                return;
            }

            string[] pieces = identifyOutput.Substring(tmpPath.Length).
                                             Split(" ", StringSplitOptions.RemoveEmptyEntries);

            if(pieces.Length < 2)
            {
                _uploading          = false;
                _uploadError        = true;
                _uploadErrorMessage = L["The uploaded file was not recognized as an image."];
                File.Delete(tmpPath);

                return;
            }

            // TODO: Move this to Helpers, keep progress

            string extension = ImageMagick.GetExtension(pieces[0]);

            if(string.IsNullOrWhiteSpace(extension))
            {
                _uploading          = false;
                _uploadError        = true;
                _uploadErrorMessage = L["The uploaded file was not recognized as an image."];
                File.Delete(tmpPath);

                return;
            }

            _imageFormat = pieces[0];
            _uploaded    = true;

            _model = new MachinePhotoViewModel
            {
                UserId            = (await UserManager.GetUserAsync(_authState.User)).Id,
                MachineId         = MachineId,
                Id                = Guid.NewGuid(),
                OriginalExtension = extension,
                UploadDate        = DateTime.UtcNow,
                Source            = _unknownSource ? null : _sourceUrl,
                LicenseId         = _licenseId
            };

            try
            {
                processExiftool = new Process
                {
                    StartInfo =
                    {
                        FileName               = "exiftool",
                        CreateNoWindow         = true,
                        RedirectStandardError  = true,
                        RedirectStandardOutput = true,
                        ArgumentList =
                        {
                            "-n",
                            "-json",
                            tmpPath
                        }
                    }
                };

                processExiftool.Start();
                exiftoolOutput = await processExiftool.StandardOutput.ReadToEndAsync();
                processExiftool.WaitForExit();

                Exif[] exif = JsonSerializer.Deserialize<Exif[]>(exiftoolOutput);

                if(exif?.Length >= 1)
                    exif[0].ToViewModel(_model);

                _extractExif = true;
            }
            catch(Exception)
            {
                _extractExif = false;
            }

            string originalFilePath = Path.Combine(Host.WebRootPath, "assets", "photos", "machines", "originals",
                                                   $"{_model.Id}{_model.OriginalExtension}");

            try
            {
                File.Move(tmpPath, originalFilePath);
                _moveFile = true;
            }
            catch(Exception)
            {
                _moveFile = false;
                File.Delete(tmpPath);

                return;
            }

            await Task.Yield();
            await InvokeAsync(StateHasChanged);

            var photos = new Photos();
            photos.FinishedAll                        += OnFinishedAll;
            photos.FinishedRenderingJpeg4k            += OnFinishedRenderingJpeg4k;
            photos.FinishedRenderingJpeg1440          += OnFinishedRenderingJpeg1440;
            photos.FinishedRenderingJpegHd            += OnFinishedRenderingJpegHd;
            photos.FinishedRenderingJpeg4kThumbnail   += OnFinishedRenderingJpeg4kThumbnail;
            photos.FinishedRenderingJpeg1440Thumbnail += OnFinishedRenderingJpeg1440Thumbnail;
            photos.FinishedRenderingJpegHdThumbnail   += OnFinishedRenderingJpegHdThumbnail;
            photos.FinishedRenderingJp2k4k            += OnFinishedRenderingJp2k4k;
            photos.FinishedRenderingJp2k1440          += OnFinishedRenderingJp2k1440;
            photos.FinishedRenderingJp2kHd            += OnFinishedRenderingJp2kHd;
            photos.FinishedRenderingJp2k4kThumbnail   += OnFinishedRenderingJp2k4kThumbnail;
            photos.FinishedRenderingJp2k1440Thumbnail += OnFinishedRenderingJp2k1440Thumbnail;
            photos.FinishedRenderingJp2kHdThumbnail   += OnFinishedRenderingJp2kHdThumbnail;
            photos.FinishedRenderingWebp4k            += OnFinishedRenderingWebp4k;
            photos.FinishedRenderingWebp1440          += OnFinishedRenderingWebp1440;
            photos.FinishedRenderingWebpHd            += OnFinishedRenderingWebpHd;
            photos.FinishedRenderingWebp4kThumbnail   += OnFinishedRenderingWebp4kThumbnail;
            photos.FinishedRenderingWebp1440Thumbnail += OnFinishedRenderingWebp1440Thumbnail;
            photos.FinishedRenderingWebpHdThumbnail   += OnFinishedRenderingWebpHdThumbnail;
            photos.FinishedRenderingHeif4k            += OnFinishedRenderingHeif4k;
            photos.FinishedRenderingHeif1440          += OnFinishedRenderingHeif1440;
            photos.FinishedRenderingHeifHd            += OnFinishedRenderingHeifHd;
            photos.FinishedRenderingHeif4kThumbnail   += OnFinishedRenderingHeif4kThumbnail;
            photos.FinishedRenderingHeif1440Thumbnail += OnFinishedRenderingHeif1440Thumbnail;
            photos.FinishedRenderingHeifHdThumbnail   += OnFinishedRenderingHeifHdThumbnail;
            photos.FinishedRenderingAvif4k            += OnFinishedRenderingAvif4k;
            photos.FinishedRenderingAvif1440          += OnFinishedRenderingAvif1440;
            photos.FinishedRenderingAvifHd            += OnFinishedRenderingAvifHd;
            photos.FinishedRenderingAvif4kThumbnail   += OnFinishedRenderingAvif4kThumbnail;
            photos.FinishedRenderingAvif1440Thumbnail += OnFinishedRenderingAvif1440Thumbnail;
            photos.FinishedRenderingAvifHdThumbnail   += OnFinishedRenderingAvifHdThumbnail;

            #pragma warning disable 4014
            Task.Run(() => photos.ConversionWorker(Host.WebRootPath, _model.Id, originalFilePath, _imageFormat, false,
                                                   "machines"));
            #pragma warning restore 4014
        }

        async Task OnFinishedRenderingJpeg4k(bool result)
        {
            _convertJpeg4k = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJpeg1440(bool result)
        {
            _convertJpeg1440 = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJpegHd(bool result)
        {
            _convertJpegHd = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJpeg4kThumbnail(bool result)
        {
            _convertJpeg4kTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJpeg1440Thumbnail(bool result)
        {
            _convertJpeg1440Th = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJpegHdThumbnail(bool result)
        {
            _convertJpegHdTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2k4k(bool result)
        {
            _convertJp2k4k = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2k1440(bool result)
        {
            _convertJp2k1440 = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2kHd(bool result)
        {
            _convertJp2kHd = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2k4kThumbnail(bool result)
        {
            _convertJp2k4kTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2k1440Thumbnail(bool result)
        {
            _convertJp2k1440Th = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingJp2kHdThumbnail(bool result)
        {
            _convertJp2kHdTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebp4k(bool result)
        {
            _convertWebp4k = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebp1440(bool result)
        {
            _convertWebp1440 = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebpHd(bool result)
        {
            _convertWebpHd = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebp4kThumbnail(bool result)
        {
            _convertWebp4kTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebp1440Thumbnail(bool result)
        {
            _convertWebp1440Th = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingWebpHdThumbnail(bool result)
        {
            _convertWebpHdTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeif4k(bool result)
        {
            _convertHeif4k = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeif1440(bool result)
        {
            _convertHeif1440 = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeifHd(bool result)
        {
            _convertHeifHd = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeif4kThumbnail(bool result)
        {
            _convertHeif4kTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeif1440Thumbnail(bool result)
        {
            _convertHeif1440Th = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingHeifHdThumbnail(bool result)
        {
            _convertHeifHdTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvif4k(bool result)
        {
            _convertAvif4k = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvif1440(bool result)
        {
            _convertAvif1440 = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvifHd(bool result)
        {
            _convertAvifHd = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvif4kThumbnail(bool result)
        {
            _convertAvif4kTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvif1440Thumbnail(bool result)
        {
            _convertAvif1440Th = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedRenderingAvifHdThumbnail(bool result)
        {
            _convertAvifHdTh = result;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        async Task OnFinishedAll(bool result)
        {
            try
            {
                await Service.CreateAsync(_model, (await UserManager.GetUserAsync(_authState.User)).Id);
                _addToDatabase = true;
            }
            catch(Exception e)
            {
                _addToDatabase = false;
                Console.WriteLine(e);

                throw;
            }

            _allFinished = true;
            await Task.Yield();
            await InvokeAsync(StateHasChanged);
        }

        void ValidateSource(ValidatorEventArgs e) =>
            Validators.ValidateUrl(e, L["Source URL must be smaller than 255 characters."], 255);
    }
}