'use strict';

var dimension = require('../../design/dimension.js');
var Doc = require('./Doc.js');
var ImageUtil = require('./ImageUtil.js');
var Logger = require('./Logger.js');

const topMargin = dimension.Dimension.headerHeight + dimension.ProblemDimension.gap;
const eps = 1e-5;
async function arrangeProblemsAndPreparePages({ problemLayout, onAddPage, renderComponent, componentHeaderHeight, problemImages, }) {
    Logger.logger.info(`start arrange, rendering, count: ${problemImages.length}, passed layout: ${problemLayout}`);
    await onAddPage({ isFirstPage: true });
    let problemIdx = 0;
    let isLeft = true;
    const offsetX = () => (isLeft ? dimension.Dimension.paddingX : Doc.Doc.width / 2 + 12);
    let curY = dimension.Dimension.headerHeight + dimension.ProblemDimension.gap;
    const availableHeight = () => {
        const bottomY = Doc.Doc.height - dimension.Dimension.footerHeight;
        return bottomY - curY;
    };
    /**
     * 다음 열을 삽입하거나 필요하다면 다음 페이지로 넘어간다.
     */
    const insertNextColumn = async () => {
        // 만약 오른쪽이였고 새로운 열이 필요하다면 다음 페이지로 넘어간다.
        if (!isLeft) {
            await onAddPage({ isFirstPage: false });
        }
        curY = topMargin;
        isLeft = !isLeft;
    };
    const checkIsInTopOffsetAndHeightBiggerThanFullColumnHeight = (h) => Math.abs(curY - topMargin) < eps && h > dimension.ProblemDimension.v1WithHeader;
    /**
     * 현재 curY를 조절하는데 호출되며 페이지를 새롭게 만들거나 다음 column으로 넘기는 등 side effect가 발생한다.
     *
     * 하지만 여러번 호출해도 적절하게 curY와 값들이 변경되므로 레이아웃 로직 중간에 여러번 호출해도 무방하다.
     *
     * 단, 마지막 문제가 렌더링 된 이후에 불리면 필요없는 페이지를 만들 수 있어서 주의
     */
    const invalidateYOffsetAndPageStatus = async () => {
        const bottomY = Doc.Doc.height - dimension.Dimension.footerHeight;
        const startPositions = problemLayout === 'h2-free'
            ? [topMargin]
            : problemLayout === 'h2-v1'
                ? [topMargin]
                : problemLayout === 'h2-v2'
                    ? [topMargin, topMargin + dimension.ProblemDimension.v2WithHeader + dimension.ProblemDimension.gap]
                    : [
                        topMargin,
                        topMargin + dimension.ProblemDimension.v3WithHeader + dimension.ProblemDimension.gap,
                        topMargin + (dimension.ProblemDimension.v3WithHeader + dimension.ProblemDimension.gap) * 2,
                    ];
        const isCurrentYInStartPositions = startPositions.length === 0 || startPositions.some((p) => Math.abs(curY - p) < eps);
        // 이미 시작 위치에 존재하는 경우 아무것도 할 필요 없다.
        if (isCurrentYInStartPositions) {
            return;
        }
        if (problemLayout === 'h2-free') {
            if (curY >= bottomY - 50) {
                return insertNextColumn();
            }
        }
        else {
            let moved = false;
            for (let i = 0; i < startPositions.length; i++) {
                if (curY <= startPositions[i] - dimension.ProblemDimension.gap) {
                    curY = startPositions[i];
                    moved = true;
                    break;
                }
            }
            if (!moved) {
                return insertNextColumn();
            }
        }
    };
    for (; problemIdx < problemImages.length; problemIdx++) {
        Logger.logger.info(`problem index ${problemIdx} rendering start`);
        let segmentIndex = 0;
        const images = problemImages[problemIdx].map(ImageUtil.ImageUtil.getFromStorage);
        const heightOfSegments = [
            componentHeaderHeight + dimension.ProblemDimension.headerMarginBottom + images[0].height,
            ...images.slice(1).map((i) => i.height),
        ];
        while (segmentIndex < heightOfSegments.length) {
            /**
             * 현재 이미지를 현재 칸에 삽입이 불가능하면 다음 column을 삽입한다.
             *
             * 만약 현재 이미지의 높이가 높이 계산 오차로 인해 하나의 column의 높이보다 크고 현재 y offset이 가장 위라면 그냥 삽입한다.
             */
            if (availableHeight() < heightOfSegments[segmentIndex] &&
                !checkIsInTopOffsetAndHeightBiggerThanFullColumnHeight(heightOfSegments[segmentIndex])) {
                await insertNextColumn();
                continue;
            }
            let currentSegmentIndexEndAt = segmentIndex;
            while (currentSegmentIndexEndAt + 1 < heightOfSegments.length &&
                heightOfSegments
                    .slice(segmentIndex, currentSegmentIndexEndAt + 2)
                    .reduce((prev, acc) => prev + acc, 0) <= availableHeight()) {
                currentSegmentIndexEndAt += 1;
            }
            /**
             * 처음 문제 헤더는 첫 height에 같이 포함되므로 그대로 index로 사용해도 무방하다.
             */
            const currentSegmentImageRange = images.slice(segmentIndex, currentSegmentIndexEndAt + 1);
            const currentSegmentHeightSum = heightOfSegments
                .slice(segmentIndex, currentSegmentIndexEndAt + 1)
                .reduce((prev, acc) => prev + acc, 0);
            await renderComponent({
                index: problemIdx,
                offsetX: offsetX(),
                offsetY: curY,
                segmentStartAt: segmentIndex,
                imageSegments: currentSegmentImageRange,
            });
            curY += currentSegmentHeightSum + dimension.ProblemDimension.gap;
            segmentIndex = currentSegmentIndexEndAt + 1;
            if (segmentIndex < heightOfSegments.length) {
                await invalidateYOffsetAndPageStatus();
            }
        }
        if (problemIdx < problemImages.length - 1) {
            await invalidateYOffsetAndPageStatus();
        }
        Logger.logger.info(`problem index ${problemIdx} rendering done`);
    }
    return [];
}
const ProblemRenderer = {
    renderProblems: arrangeProblemsAndPreparePages,
};

exports.ProblemRenderer = ProblemRenderer;
