// Copyright (C) 2003-2026, Foxit Software Inc..
// All Rights Reserved.
//
// http://www.foxitsoftware.com
//
// The following code is copyrighted and contains proprietary information and trade secrets of Foxit Software Inc..
// You cannot distribute any part of Foxit PDF SDK to any third party or general public,
// unless there is a separate license agreement with Foxit Software Inc. which explicitly grants you such rights.
//
// This file contains an example to demonstrate how to use Foxit PDF SDK to loading PDF document asynchronously.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;


using foxit;
using foxit.common;
using foxit.common.fxcrt;
using foxit.pdf;
using foxit.pdf.graphics;
using foxit.pdf.objects;
using foxit.pdf.annots;
using foxit.common.file;
using System.IO;
using System.Collections;

namespace async_load
{

    // Data of asynchronous loader callback object.
    public class DownloadHintDataInfo
    {
        public long getOffset()
        {
            return offset;
        }

        public void setOffset(long offset)
        {
            this.offset = offset;
        }

        private long offset;
        private uint size;
        private bool is_downloaded;

        public uint getSize()
        {
            return size;
        }

        public void setSize(uint size)
        {
            this.size = size;
        }

        public bool isIs_downloaded()
        {
            return is_downloaded;
        }

        public void setIs_downloaded(bool is_downloaded)
        {
            this.is_downloaded = is_downloaded;
        }

        public DownloadHintDataInfo()
        {
            this.offset = 0;
            this.size = 0;
            this.is_downloaded = false;
        }

        public DownloadHintDataInfo(long offset, uint size, bool is_downloaded)
        {
            this.offset = offset;
            this.size = size;
            this.is_downloaded = is_downloaded;
        }
    }

    // This callback just simulates the downloading progress by DownloadHintDataInfo::is_downloaded,
    // not really to download data.
    public class AsyncFileRead : AsyncReaderCallback
    {
        private FileStream file_ = null;
        private bool is_large_file_;
        // Used to record added hint range and if the range is downloaded.
        private List<DownloadHintDataInfo> hint_data_record_ = null;

        public AsyncFileRead()
        {
            hint_data_record_ =  new List<DownloadHintDataInfo>();

        }

        public bool loadFile(String file_path, bool is_large_file)
        {
            this.is_large_file_ = is_large_file;
            this.file_ = new FileStream(file_path, FileMode.Open);
            return true;

        }

        public override long GetSize()
        {
            file_.Seek(0, SeekOrigin.End);
            long len = file_.Length;
            file_.Seek(0, SeekOrigin.Begin);
            return len;
        }

        public override bool ReadBlock(IntPtr buffer, long offset, uint size)
        {
            int read_size = 0;
            file_.Seek(offset, SeekOrigin.Begin);
            byte[] array = new byte[size + 1];
            read_size = file_.Read(array, 0, (int)size);

            Marshal.Copy(array, 0, buffer, (int)size);
            return read_size == size ? true : false;

        }

        public override void Release()
        {
            try
            {
                file_.Close();
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
            }
        }

        public override bool IsDataAvail(long offset, uint size)
        {
            return checkRecordDownloaded(offset, size, true);
        }

        public override bool AddDownloadHint(long offset, uint size)
        {
            // Record the range and downloaded data.
            return checkRecordDownloaded(offset, size, true);
        }

        public bool checkRecordDownloaded(long offset, uint size, bool to_download)
        {
            int record_count = hint_data_record_.Count;

            for (int i = 0; i < record_count; i++)
            {
                DownloadHintDataInfo data_info = (DownloadHintDataInfo)hint_data_record_[i];

                // If (offset+size) is out of current data_info, just continue to check other record in hint_data_record_.
                if (offset > (data_info.getOffset() + data_info.getSize()))
                {
                    continue;
                }
                if (offset + size < data_info.getOffset())
                {
                    continue;
                }

                // If data defined by <offset, size> has been in/within current data info, just download the data.
                if (offset >= data_info.getOffset() && (offset + size) <= (data_info.getOffset() + data_info.getSize()))
                {
                    if (to_download)
                        data_info.setIs_downloaded(true);
                    return data_info.isIs_downloaded();
                }

                // If only part of data defined by <offset, size> is in current data_info, download current data_info and
                // also check and download rest data.
                if (offset >= data_info.getOffset() && offset < (data_info.getOffset() + data_info.getSize()) &&
                        (offset + size) > (data_info.getOffset() + data_info.getSize()))
                {
                    if (to_download)
                        data_info.setIs_downloaded(true);
                    if (!data_info.isIs_downloaded())
                        return data_info.isIs_downloaded();
                    long new_offset = data_info.getOffset() + data_info.getSize() + 1;
                    uint new_size = (uint)(size - 1 - (data_info.getOffset() + data_info.getSize() - offset));
                    return checkRecordDownloaded(new_offset, new_size, to_download);
                }

                if (offset < data_info.getOffset() && (offset + size) >= data_info.getOffset() &&
                        (offset + size) <= (data_info.getOffset() + data_info.getSize()))
                {
                    if (to_download)
                        data_info.setIs_downloaded(true);
                    if (!data_info.isIs_downloaded())
                        return data_info.isIs_downloaded();
                    long new_offset = offset;
                    uint new_size = (uint)(data_info.getOffset() - 1 - offset);
                    return checkRecordDownloaded(new_offset, new_size, to_download);
                }
                if (offset < data_info.getOffset() && (offset + size) > (data_info.getOffset() + data_info.getSize()))
                {
                    if (to_download)
                        data_info.setIs_downloaded(true);
                    if (!data_info.isIs_downloaded())
                        return data_info.isIs_downloaded();
                    long new_offset = offset;
                    uint new_size = (uint)(data_info.getOffset() - 1 - offset);
                    if (checkRecordDownloaded(new_offset, new_size, to_download))
                    {
                        new_offset = data_info.getOffset() + data_info.getSize() + 1;
                        new_size = (uint)(size - 1 - (data_info.getOffset() - offset + data_info.getSize()));
                        return checkRecordDownloaded(new_offset, new_size, to_download);
                    }
                    else
                        return false;
                }
            }
            if (to_download)
            {
                return downloadData(offset, size);
            }
            return false;
        }

        bool downloadData(long offset, uint size)
        {
            DownloadHintDataInfo new_info = new DownloadHintDataInfo(offset, size, true);
            hint_data_record_.Add(new_info);
            return true;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string input_path = "../input_files/";
            string output_path = "../output_files/async_load/";
            System.IO.Directory.CreateDirectory(output_path);
            string sn = "Rew1vB0TqoMIxX444UcpScARWdhgJpsJfzmyMLR32a0+EgrCjp2NaQ==";
            string key = "8f3g1sFttW8NAgfSNv+BvaJWy4Pp8flcm7ncNDkdvCCO4JwUFbGLyZdujReMaiplbYJwwxam3lr7uHtQaEf0xTSu8j2ZBdrZ8FS4OQkEUjOI9UEGb3Ic0GqmlP9T9OnSItOYqoZZA/OnYg5EaVP+c/ha32nUquOk+5riEdhYgINAZDdgJkPc+YSWsrfixOIROf6t34j3eRcyFnOY0MS7tSXi1AEPE0VZdyu+5bfmBSGvLI+WR5lQ+oOQR5hRGwqf990WEdBelNO7dxhZELVhEZ+/L60PpDf5y9A8qmrY6PiEU8nhqIyLpks0BxeIsYyZ3Cr82jjfbItPGyEARsIVzluBSgVMD4oGjojz8T6wGDEXUbmZH/pra94XAs8wZ28mzbmIEppWnhBqX0c6Ov8iXCfFfUZr4KNuPxMEgk4VOYTMOd70GEjGnylivw18hbn7ZpkWh2Zql+uU2HejvOgn+WHvT0HF9NhK0Xb5vH8aFkgBP92PJx8obIbxBVeeExczxvsqWufuPdXS2sPNIgMYZ2TtvqVVsiUToeGB2FQo09fzZOSH5Vi18u/siyvC3FmjeMf4+OYWPuDPNCcrb21NgcHam7XI8AAoGLTGtLLWR+fxynbBBcg2d2ZPcZ5CMFIfOBHRWCkcA0sDT3cl9nsBppHNJze/jVxHy+lREeQEA/etSIgohTGAWW3ZlpibRXqMxlmVWEPifCubbWTzQ6U54mRMpW2UghH3gvCNFNgdday3Av7EtlIJOdUuiMq3zmmd5J4Jt4C82CKnVewA0DtZ4EEtwRG9IYuLrfpuZlnjBAtoeXRV1lqUZ6BcCe0E3m9jO28sGkALuIeJQ74XG7X8lMC7lMWWQYiYD7D6G9QT6X3qV4vXJwNKRKpYc/rI+amEawTSnDYAMEaYPwtZ0rb/7BL+atsZZaI+ncebdfVfjDJZbCZAc+JnkuNlUn2xrNRuObw8h/G0lm2pNOEJ10S8R4+yplo5B1QmC2dCDhaTaW67QJPqnJPKbVmjw7i6KGBi+CDP3JnAKlOX/YUu8uk56JfeQUuGcQg/KaBMGctnchH2n1i3+oGbibhurQj22+OO66Q6mijKoCRP/QJm5WTGcO4c0iVX0B8dIlai/uv/pGU8Wps9MxysH5ViVIj56LWKuNfy1FNO9+wx4iHYnUxfW0Iccq3YJYAlOBkMv8VZIJl9aTEEFJ626LJEw/UbEBdR9CdlwnylPf4RuNW/lcGYMgq3xNVpp/bzYKwZivvQOFgMdX1tv8D33SHvAm3zrRCU+v94BJXnBi9k25fnKsc705IMwv+I6bueXA+mDnagj1oTqsWXZ0WnPAYI3WUO7sBf7XUH230wfYXnTSVhujJxiEP/2oWqi2813gtQkKwz9N2p6yT8Vsh3+BaAwwDvtOZMJKUFcabTBSL6NoouzmzwItJ4mIboT6KXaef66rs8jCU=";
            // Initialize library
            ErrorCode error_code = Library.Initialize(sn, key);
            if (error_code != ErrorCode.e_ErrSuccess)
            {
                Console.WriteLine("Library Initialize Error: {0}\n", error_code);
                return;
            }

            String input_file = input_path + "AboutFoxit.pdf";
            String output_file = output_path + "page0.bmp";

            try
            {
                AsyncFileRead file_read = new AsyncFileRead();
                file_read.loadFile(input_file, false);
                using (PDFDoc doc = new PDFDoc(file_read, true))
                {
                    // Actually, here, application should download needed data which specified by AsyncFileRead::AddDownloadHint().
                    // But here, for simple example, we just "download" these data inside AsyncFileRead::AddDownloadHint().
                    // So, just continue to check the ready state here, which will trigger AsyncFileRead::AddDownloadHint() to
                    // "download" data.
                    ErrorCode code = ErrorCode.e_ErrDataNotReady;
                    while (code == ErrorCode.e_ErrDataNotReady)
                    {
                        code = doc.Load(null);
                    }

                    if (code != ErrorCode.e_ErrSuccess)
                    {
                        Console.WriteLine(String.Format("The PDFDoc [%s] Error: %d", input_file, code));
                        return;
                    }

                    // Actually, here, application should download needed data which specified by AsyncFileRead::AddDownloadHint().
                    // But here, for simple example, we just "download" these data inside AsyncFileRead::AddDownloadHint().
                    // So, just continue to check the ready state here, which will trigger AsyncFileRead::AddDownloadHint() to
                    // "download" data.
                    PDFPage page = null;
                    code = ErrorCode.e_ErrDataNotReady;
                    while (code == ErrorCode.e_ErrDataNotReady)
                    {
                        using (page = doc.GetPage(0))
                        {
                            if (!page.IsEmpty())
                            {
                                code = ErrorCode.e_ErrSuccess;
                            }

                            // Parse page.
                            page.StartParse((int)PDFPage.ParseFlags.e_ParsePageNormal, null, false);

                            int width = (int)page.GetWidth();
                            int height = (int)page.GetHeight();
                            Matrix2D matrix = page.GetDisplayMatrix(0, 0, width, height, page.GetRotation());
                            using (foxit.common.Bitmap bitmap = new foxit.common.Bitmap(width, height, foxit.common.Bitmap.DIBFormat.e_DIBArgb, System.IntPtr.Zero, 0))
                            {
                                bitmap.FillRect(0xFFFFFFFF, null);
                                // Render page
                                using (foxit.common.Renderer render = new Renderer(bitmap, false))
                                {
                                    render.StartRender(page, matrix, null);
                                    foxit.common.Image image = new foxit.common.Image();
                                    image.AddFrame(bitmap);
                                    image.SaveAs(output_file);
                                 }
                            }
                         }
                    }
                }
                Console.WriteLine("async-load demo finished.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return;
            }
            Library.Release();
        }
    }
}
