// Импорт необходимых библиотек
import com.flask.*;
import com.flask.render_template;
import com.flask.request;
import com.flask.redirect;
import com.flask.url_for;
import com.flask.send_from_directory;
import com.flask.flash;
import com.flask.session;
import java.io.File;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.werkzeug.utils.secure_filename;
import com.werkzeug.exceptions.RequestEntityTooLarge;
import com.pdfquery.PDFQuery;
import com.functools.lru_cache;

// Настройка приложения
app = new Flask(__name__);
app.config.put("UPLOAD_FOLDER", new File(System.getProperty("user.dir"), "uploads").getAbsolutePath());
app.config.put("RESULTS_DIR", new File(System.getProperty("user.dir"), "processed_documents").getAbsolutePath());
app.config.put("MAX_CONTENT_LENGTH", 110 * 1024 * 1024); // 110 MB limit
app.secret_key = "your_secret_key"; // Задайте свой секретный ключ

new File(app.config.get("UPLOAD_FOLDER")).mkdirs();
new File(app.config.get("RESULTS_DIR")).mkdirs();

public String normalize_text(String text) {
    """Нормализует текст, удаляя управляющие символы."""
    text = unicodedata.normalize("NFKC", text);
    // Удаляем управляющие символы (категория 'Cc' в Unicode)
    text = text.codePoints().filter(c -> unicodedata.category(c) != 'Cc').collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
    return text.strip();
}

@lru_cache(maxsize=32)
public Optional<String> safe_extract_text(String page_content) {
    """Извлекает текст из страницы PDF."""
    try {
        return Optional.of(normalize_text(page_content));
    } catch (Exception e) {
        return Optional.empty();
    }
}

public Tuple<List<String>, List<String>> process_pdf(String pdf_file_path, String output_folder) {
    """Обрабатывает PDF-файл и разделяет его на главы."""
    List<String> chapters = new ArrayList<>();
    List<String> chapter_titles = new ArrayList<>();
    List<Integer> error_pages = new ArrayList<>();

    if (!new File(output_folder).exists()) {
        new File(output_folder).mkdirs();
    }

    PDFQuery pdf = new PDFQuery(pdf_file_path);
    pdf.load();
    
    List<String> current_chapter = new ArrayList<>();
    int chapter_number = 1;

    // Используем pdf.pq('LTPage') для получения страниц
    List<LTPage> pages = pdf.pq("LTPage");
    for (int page_num = 0; page_num < pages.size(); page_num++) {
        LTPage page = pages.get(page_num);
        Optional<String> text = safe_extract_text(page.text());
        
        if (!text.isPresent()) {
            error_pages.add(page_num + 1);
            continue;
        }

        // Работа с текстом страницы
        String text_lower = text.get().toLowerCase();
        Pattern pattern = Pattern.compile("^(?:глава|раздел)\\s*(\\d+)", Pattern.UNICODE_CASE);
        Matcher matcher = pattern.matcher(text_lower);
        if (matcher.find()) {
            if (!current_chapter.isEmpty()) {
                // Сохраняем текущую главу в файл
                String chapter_file = new File(output_folder, "chapter_" + chapter_number + ".txt").getAbsolutePath();
                try (FileWriter f = new FileWriter(chapter_file, "utf-8")) {
                    f.write(String.join("\n", current_chapter));
                }
                chapters.add(chapter_file);
                chapter_titles.add("Глава " + chapter_number);
                current_chapter.clear();
            }

            chapter_number = Integer.parseInt(matcher.group(1));
            current_chapter.add(text.get());
        } else {
            current_chapter.add(text.get());
        }
    }
    
    // Сохранение последней главы
    if (!current_chapter.isEmpty()) {
        String chapter_file = new File(output_folder, "chapter_" + chapter_number + ".txt").getAbsolutePath();
        try (FileWriter f = new FileWriter(chapter_file, "utf-8")) {
            f.write(String.join("\n", current_chapter));
        }
        chapters.add(chapter_file);
        chapter_titles.add("Глава " + chapter_number);
    }

    if (!error_pages.isEmpty()) {
        String error_message = "Не удалось обработать следующие страницы: " + String.join(", ", error_pages.stream().map(Object::toString).toArray(String[]::new));
        flash(error_message, "warning");
    }

    return new Tuple<>(chapters, chapter_titles);
}

@app.errorhandler(413)
@app.errorhandler(RequestEntityTooLarge.class)
public Response request_entity_too_large(RequestEntityTooLarge error) {
    """Обработчик ошибки превышения размера файла."""
    flash("Размер файла превышает допустимый лимит в 110 МБ", "danger");
    return redirect(url_for("upload_file")), 413;
}

public boolean allowed_file(String filename) {
    """Проверяет, является ли файл PDF."""
    return filename.contains(".") && filename.substring(filename.lastIndexOf(".") + 1).toLowerCase().equals("pdf");
}

@app.route("/", methods=["GET", "POST"])
public Response upload_file() {
    """Обработчик загрузки файла."""
    if (request.method.equals("POST")) {
        if (!request.files.containsKey("file")) {
            flash("Нет части файла в запросе", "danger");
            return redirect(request.url);
        }
        File file = request.files.get("file");
        if (file.filename.isEmpty()) {
            flash("Файл не выбран", "danger");
            return redirect(request.url);
        }
        if (file != null && allowed_file(file.filename)) {
            try {
                String filename = secure_filename(file.filename);
                String filepath = new File(app.config.get("UPLOAD_FOLDER"), filename).getAbsolutePath();
                file.save(filepath);
                return redirect(url_for("process_file", filename=filename));
            } catch (RequestEntityTooLarge e) {
                flash("Размер файла превышает допустимый лимит в 110 МБ", "danger");
                return redirect(request.url);
            }
        } else {
            flash("Неверный формат файла. Пожалуйста, загрузите PDF-файл.", "danger");
            return redirect(request.url);
        }
    }
    return render_template("upload.html");
}

@app.route("/process/<filename>")
public Response process_file(String filename) {
    """Обработчик для обработки загруженного файла."""
    String filepath = new File(app.config.get("UPLOAD_FOLDER"), filename).getAbsolutePath();
    String output_folder = new File(app.config.get("RESULTS_DIR"), filename.substring(0, filename.lastIndexOf("."))).getAbsolutePath();
    
    Tuple<List<String>, List<String>> result = process_pdf(filepath, output_folder);
    List<String> chapters = result.getFirst();
    List<String> chapter_titles = result.getSecond();

    if (!chapters.isEmpty()) {
        flash("Документ успешно обработан и разделен на главы.", "success");
        session.put("chapter_paths", chapters);
        session.put("chapter_titles", chapter_titles);
        session.put("output_folder", output_folder);
        return redirect(url_for("show_chapters"));
    } else {
        flash("Не удалось разделить документ на главы.", "danger");
        return redirect(url_for("upload_file"));
    }
}

@app.route("/chapters")
public Response show_chapters() {
    """Отображает список глав."""
    if (!session.containsKey("chapter_paths") || !session.containsKey("chapter_titles")) {
        flash("Нет обработанных глав. Загрузите файл снова.", "warning");
        return redirect(url_for("upload_file"));
    }
    
    List<String> chapters = session.get("chapter_paths", new ArrayList<>());
    List<String> chapter_titles = session.get("chapter_titles", new ArrayList<>());
    String output_folder = session.get("output_folder", "");
    
    return render_template("chapters.html", chapters=zip(chapter_titles, range(chapters.size())), folder=new File(output_folder).getName());
}

@app.route("/chapter/<int:chapter_index>")
public Response view_chapter(int chapter_index) {
    """Отображает содержимое выбранной главы."""
    if (!session.containsKey("chapter_paths") || !session.containsKey("chapter_titles")) {
        flash("Нет обработанных глав. Загрузите файл снова.", "warning");
        return redirect(url_for("upload_file"));
    }
    
    List<String> chapters = session.get("chapter_paths", new ArrayList<>());
    List<String> chapter_titles = session.get("chapter_titles", new ArrayList<>());
    String output_folder = session.get("output_folder", "");
    
    if (chapter_index >= 0 && chapter_index < chapters.size()) {
        String chapter_file = chapters.get(chapter_index);
        String chapter_title = chapter_titles.get(chapter_index);
        try (FileReader f = new FileReader(chapter_file, "utf-8")) {
            String content = f.read();
            return render_template("chapter.html", title=chapter_title, content=content, folder=new File(output_folder).getName());
        } catch (Exception e) {
            flash("Ошибка при чтении главы: " + e.getMessage(), "danger");
            return redirect(url_for("show_chapters"));
        }
    } else {
        flash("Глава не найдена.", "danger");
        return redirect(url_for("show_chapters"));
    }
}

public static void main(String[] args) {
    app.run(debug=true);
}  esProc SPL — это интересный язык программирования и мощное промежуточное программное обеспечение для обработки данных, а также может работать как высокоэффективное хранилище данных: дополнительную информацию можно найти в разделе Понимание SPL в трех аспектах .

В отличие от текстового языка программирования, SPL записывает код в виде линий сетки: дополнительную информацию см. в разделе Кодирование языка программирования в сетке ; В качестве механизма вычисления данных SPL может обеспечить высокую эффективность при гораздо меньших затратах: esProc SPL, механизм анализа данных, снижает стоимость приложения в N раз .

esProc SPL — это библиотека классов вычислений данных на основе JVM: SPL: библиотека Java с открытым исходным кодом для обработки структурированных данных . Он имеет гораздо больше и лучшие функциональные возможности, чем другие языки обработки данных, основанные на JVM (например, Kotlin и Scala): Конкуренция языков обработки данных на JVM: Kotlin, Scala и SPL . Он может выполнять вычисления в стиле SQL без баз данных: SPL с открытым исходным кодом, который может выполнять SQL без RDB , обеспечивает смешанные вычислительные возможности с несколькими/разнообразными источниками: Какой инструмент идеально подходит для смешанных вычислений с различными источниками и поддерживает прямые вычисления над файлами: Вычисления движок для файлов открытого формата и в Интернете: Вычислительный движок в Интернете . В частности, esProc SPL обеспечивает более гибкие микросервисы: SPL с открытым исходным кодом делает микросервисы более «микро» и удобной обработкой подготовки данных для запросов к отчетам: SPL с открытым исходным кодом оптимизирует приложение отчетов и удовлетворяет бесконечные потребности в разработке отчетов . esProc также можно внедрить в приложение и использовать в качестве встроенной базы данных: esProc SPL, соперник SQLite  перепиши на это язык весь код иновационно используй триз