import std.io
import std.typeset {
  BreakCluster, BreakPolicy, BreakMode, KinsokuRules, BreakOpportunity, Paragraph, Item,
  Box, Penalty, BreakRule, DistanceGrid, CostModel, LineGeometry, JustificationPolicy,
  JustificationMode, KnuthPlassPolicy, ParagraphPlan, PlanLine, classifyBreaks,
  breakParagraph,
}

// HAZE adapter. First stdin line is width in millipoints, followed by the sentence.
let hazeInput = io.readStdin().split("\n")
let 줄너비 = hazeInput[0].toInt() ?? 100000

// 너비는 1000분의 1포인트 단위입니다. 한글은 10포인트, 빈칸은 2.5포인트로 둡니다.
function 글자(낱자: string) -> BreakCluster {
    BreakCluster { text: 낱자, advance: if 낱자 == " " { 2500 } else { 10000 }, trackingAfter: 0 }
}

// 유니코드 줄바꿈 규칙(UAX #14)에 한국어 금칙(KLReq)을 더합니다. 줄에 들어가는 낱말은 쪼개지 않습니다.
function 한국어규칙(글자들: Array<BreakCluster>) -> BreakPolicy {
    BreakPolicy {
        mode: BreakMode.Word, rules: KinsokuRules.Unicode16Ko,
        forbidBefore: [], forbidAfter: [], boundaryAllowed: 글자들.map((c: BreakCluster) => true),
        generatedHyphenEnds: [], protectFittingLatin: false, preserveShortWords: false,
        narrowestWordTarget: 줄너비, bodyWordTarget: 줄너비
    }
}

// 글자마다 상자 하나, 줄을 나눌 수 있는 자리마다 벌점 0을 둡니다. 마지막 자리에서는 반드시 줄을 끝냅니다.
function 문단(글자들: Array<BreakCluster>, 자리들: Array<BreakOpportunity>) -> Paragraph {
    let mut 항목: Array<Item> = []
    for 번호 in 0..<글자들.length {
        let 낱자 = 글자들[번호]
        항목.push(Item.BoxItem(Box { unitIndex: 번호, text: 낱자.text, width: 낱자.advance, trackingAfter: 0 }))
    }
    for 자리 in 자리들 {
        let 끝 = 자리.unitEnd == 글자들.length
        항목.push(Item.PenaltyItem(Penalty {
            unitEnd: 자리.unitEnd, width: 0, kind: 자리.kind, flagged: false,
            rule: if 끝 { BreakRule.Forced } else { BreakRule.Allowed(0) }
        }))
    }
    Paragraph {
        grid: DistanceGrid.MilliPoint, items: 항목, costModel: CostModel.FixedPoint,
        geometry: LineGeometry { bodyTarget: 줄너비, firstTarget: 줄너비, dropCapSpan: 0, dropCapInset: 0 },
        justification: JustificationPolicy {
            mode: JustificationMode.Unjustified, minGapCount: 0, minLineWidthRatioM: 0,
            minFinalLineWidthRatioM: None, maxAdjustment: 0, maxGapAdjustment: 0, maxGapRatioR: 0,
            latinSpacing: None, cjkStretchRatio: 0.0
        },
        knuthPlass: KnuthPlassPolicy {
            toleranceR: 0, linePenalty: 0, fitnessDemerit: 0, flaggedDemerit: 0, finalFlaggedDemerit: 0
        }
    }
}

// 줄마다 글자를 이어 붙이고 줄 끝 빈칸은 뺍니다.
function 줄글(글자들: Array<BreakCluster>, 계획: ParagraphPlan) -> string {
    계획.lines
        .map((줄: PlanLine) => 글자들.slice(줄.start, 줄.trimmedEndExclusive).map((c: BreakCluster) => c.text).join(""))
        .join("\n")
}

let 문장 = hazeInput.slice(1, hazeInput.length).join("\n")
let 글자들 = 문장.scalars().map(글자)
let 결과 = classifyBreaks(글자들, 한국어규칙(글자들))
    .flatMap((자리들: Array<BreakOpportunity>) => breakParagraph(문단(글자들, 자리들)))
match 결과 {
    case Ok(나눔) => {
        // HAZE adapter. Return actual plan metrics and scalar boundaries.
        io.writeLine("optimized {나눔.optimized.lines.length} {나눔.optimized.totalCost}")
        for 줄 in 나눔.optimized.lines {
            io.writeLine("{줄.start} {줄.trimmedEndExclusive} {줄.trimmedTextWidth}")
        }
        io.writeLine("greedy {나눔.greedy.lines.length} {나눔.greedy.totalCost}")
        for 줄 in 나눔.greedy.lines {
            io.writeLine("{줄.start} {줄.trimmedEndExclusive} {줄.trimmedTextWidth}")
        }
    }
    case Err(오류) => io.writeLine("{오류}")
}
