// 회로도 그리기. 지금 보는 단계의 원시 노드, 칩 상자, 전선을 SVG 로 그리고 신호가 흐르는 곳은 계산한 신호 색으로 칠합니다.
// 입력은 그 단계의 분해 결과, 평탄 시뮬 결과, 고장 노드 아이디 목록이고 출력은 svg 조각 하나입니다.
// 같은 입력이면 늘 같은 조각이 나옵니다. 좌표 산술은 표시용이고 신호는 core.sim 이 계산한 값만 읽습니다.
// 노드, 핀, 전선, 칩마다 data-* 아이디를 달 뿐 히트 판정과 일시 상태는 다루지 않습니다. 페이지가 합니다.
// Schematic renderer: primitive nodes, chip boxes and wires as SVG colored by the computed signals.

import core.netlist
import core.sim
import core.hierarchy
import ui.routing
import ui.signalviz
import ui.components

type 상자 = { 왼: int, 위: int, 오른: int, 아래: int }

let 칩폭 = 96
let 빈노드: netlist.노드 = { 아이디: "", 종류: "INPUT", x: 0, y: 0, 값: 0 }
// 노드도 칩도 없으면 이 크기의 빈 판을 그립니다.
let 기본판: 상자 = { 왼: 0, 위: 0, 오른: 320, 아래: 200 }

// SEG7 선분 a 부터 g 까지의 양 끝 상대 좌표입니다.
let 선분들 = [
  { x1: 10, y1: 6, x2: 30, y2: 6 },
  { x1: 32, y1: 8, x2: 32, y2: 26 },
  { x1: 32, y1: 32, x2: 32, y2: 50 },
  { x1: 10, y1: 52, x2: 30, y2: 52 },
  { x1: 8, y1: 32, x2: 8, y2: 50 },
  { x1: 8, y1: 8, x2: 8, y2: 26 },
  { x1: 10, y1: 29, x2: 30, y2: 29 },
]

// 핀 이름이 여러 번 나오면 마지막 자리를 씁니다.
function 마지막자리(이름들: Array<string>, 이름: string) -> int {
  let 자리들 = [for i in 0..<이름들.length if 이름들[i] == 이름 => i]
  if 자리들.length == 0 { -1 } else { 자리들[자리들.length - 1] }
}

// 핀의 연결점은 몸체 바로 바깥이고 전선과 페이지의 히트 영역이 여기에 붙습니다. 모르는 입력 핀은 in0 자리에 둡니다.
function 핀위치(노드값: netlist.노드, 핀: string) -> routing.점 {
  let x = 노드값.x
  let y = 노드값.y
  match 노드값.종류 {
    case "INPUT" => ({ x: x + 46, y: y + 15 })
    case "OUTPUT" => ({ x: x - 6, y: y + 15 })
    case "SEG7" => ({ x: x - 6, y: y + 6 + 마지막자리(netlist.세그핀들, 핀).atLeast(0) * 8 })
    case "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR" => {
      if 핀 == "out" {
        { x: x + 76, y: y + 25 }
      } else if 핀 == "in1" {
        { x: x - 6, y: y + 35 }
      } else if netlist.입력핀들(노드값.종류).length == 2 {
        { x: x - 6, y: y + 15 }
      } else {
        { x: x - 6, y: y + 25 }
      }
    }
  }
}

// 아이디, 핀, 종류는 파서가 검증한 안전한 토큰이라 이스케이프하지 않습니다.
function 핀점(아이디: string, 핀: string, 위치: routing.점, 색: string) -> string {
  "<circle class=\"pin\" data-node-id=\"{아이디}\" data-pin=\"{핀}\" cx=\"{위치.x}\" cy=\"{위치.y}\" r=\"4\" fill=\"{색}\" />"
}

function 핀선(시작: routing.점, 끝: routing.점, 색: string) -> string {
  "<line x1=\"{시작.x}\" y1=\"{시작.y}\" x2=\"{끝.x}\" y2=\"{끝.y}\" stroke=\"{색}\" stroke-width=\"2\" />"
}

function 게이트그리기(노드값: netlist.노드, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
  let x = 노드값.x
  let y = 노드값.y
  let 출력색 = signalviz.원천색(시뮬, 노드값.아이디)
  let 외곽 = if 노드값.아이디 in 고장들 { signalviz.고장색 } else { signalviz.먹 }
  let 출력 = 핀위치(노드값, "out")
  let 입력핀들 = netlist.입력핀들(노드값.종류)
  let 입력조각들 = 입력핀들.map(핀 => {
    let 위치 = 핀위치(노드값, 핀)
    핀선(위치, { x: x, y: 위치.y }, signalviz.흑연) + 핀점(노드값.아이디, 핀, 위치, signalviz.흑연)
  })
  [
    "<g class=\"node gate\" data-node-id=\"{노드값.아이디}\">",
    "<rect x=\"{x}\" y=\"{y}\" width=\"70\" height=\"50\" rx=\"4\" fill=\"{signalviz.벨럼}\" stroke=\"{외곽}\" stroke-width=\"2\" />",
    "<text x=\"{x + 35}\" y=\"{y + 29}\" text-anchor=\"middle\" font-size=\"13\" font-family=\"monospace\" fill=\"{외곽}\">{노드값.종류}</text>",
    핀선({ x: x + 70, y: 출력.y }, 출력, 출력색),
    핀점(노드값.아이디, "out", 출력, 출력색),
    입력조각들.join(""),
    "</g>",
  ].join("")
}

function 세그그리기(노드값: netlist.노드, 값들: Array<bool>) -> string {
  let x = 노드값.x
  let y = 노드값.y
  let 선분조각들 = map(0..<선분들.length, i => "<line x1=\"{x + 선분들[i].x1}\" y1=\"{y + 선분들[i].y1}\" x2=\"{x + 선분들[i].x2}\" y2=\"{y + 선분들[i].y2}\" stroke=\"{signalviz.신호색(값들[i])}\" stroke-width=\"4\" stroke-linecap=\"round\" />")
  let 핀조각들 = map(0..<netlist.세그핀들.length, i => 핀점(노드값.아이디, netlist.세그핀들[i], { x: x - 6, y: y + 6 + i * 8 }, signalviz.신호색(값들[i])))
  [
    "<g class=\"node seg7\" data-node-id=\"{노드값.아이디}\">",
    "<rect x=\"{x - 4}\" y=\"{y - 2}\" width=\"44\" height=\"62\" rx=\"4\" fill=\"{signalviz.벨럼}\" stroke=\"{signalviz.먹}\" stroke-width=\"2\" />",
    선분조각들.join(""),
    핀조각들.join(""),
    "</g>",
  ].join("")
}

// 칩 출력 핀이 구동하는 선분은 칩 안의 원시 노드를 평탄 시뮬에서 읽습니다. 핀 하나에 전선이 여럿이면 마지막 전선을 씁니다.
function 세그값들(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 아이디: string) -> Array<bool> {
  netlist.세그핀들.map(핀 => 분해값.본체.전선들.reduce(false, (값, 선) => if 선.끝노드 == 아이디 && 선.끝핀 == 핀 {
    sim.신호로(시뮬, hierarchy.평탄원천(분해값, 선.시작노드, 선.시작핀).노드).신호
  } else {
    값
  }))
}

// 입력과 출력의 몸체는 ui.components 가 그리고 여기서 핀을 더합니다. 입력 핀은 자기 신호 색, 출력 핀은 흑연색입니다.
function 노드그리기(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>, 노드값: netlist.노드) -> string {
  match 노드값.종류 {
    case "INPUT" => {
      let 켜짐 = sim.신호로(시뮬, 노드값.아이디).신호
      let 색 = signalviz.신호색(켜짐)
      let 출력 = 핀위치(노드값, "out")
      components.입력몸체(노드값, 켜짐) + 핀선({ x: 노드값.x + 40, y: 노드값.y + 15 }, 출력, 색) + 핀점(노드값.아이디, "out", 출력, 색)
    }
    case "OUTPUT" => {
      let 입력 = 핀위치(노드값, "in0")
      components.출력몸체(노드값, sim.신호로(시뮬, 노드값.아이디).신호) + 핀선(입력, { x: 노드값.x, y: 노드값.y + 15 }, signalviz.흑연) + 핀점(노드값.아이디, "in0", 입력, signalviz.흑연)
    }
    case "SEG7" => 세그그리기(노드값, 세그값들(분해값, 시뮬, 노드값.아이디))
    case "NAND" | "AND" | "OR" | "NOT" | "XOR" | "NOR" | "XNOR" => 게이트그리기(노드값, 시뮬, 고장들)
  }
}

function 칩높이(정의: hierarchy.칩정의) -> int {
  정의.외부입력.length.atLeast(정의.외부출력.length) * 30 + 16
}

// extin 은 왼쪽, extout 은 오른쪽에 30 간격으로 놓습니다.
function 칩핀위치(칩: hierarchy.놓인칩, 핀: string) -> routing.점 {
  let 배치 = 칩.배치
  let 입력자리 = 마지막자리(칩.정의.외부입력.map(핀값 => 핀값.핀), 핀)
  if 입력자리 >= 0 {
    { x: 배치.x - 6, y: 배치.y + 30 + 입력자리 * 30 }
  } else {
    { x: 배치.x + 칩폭 + 6, y: 배치.y + 30 + 마지막자리(칩.정의.외부출력.map(핀값 => 핀값.핀), 핀) * 30 }
  }
}

// 전선마다 노드를 훑지 않도록 그리기 전에 한 번 만듭니다. 앞에서부터 넣으므로 같은 아이디가 여럿이면 마지막 노드가 남습니다.
function 노드표만들기(노드들: Array<netlist.노드>) -> Map<string, netlist.노드> {
  let mut 표: Map<string, netlist.노드> = Map.new()
  for 노드값 in 노드들 { 표.insert(노드값.아이디, 노드값) }
  표
}

// 전선 양 끝은 파서가 있음을 보장합니다.
function 끝점위치(분해값: hierarchy.분해결과, 노드표: Map<string, netlist.노드>, 노드: string, 핀: string) -> routing.점 {
  match hierarchy.놓인칩찾기(분해값, 노드) {
    case Some(칩) => 칩핀위치(칩, 핀)
    case None => 핀위치(노드표.getOr(노드, 빈노드), 핀)
  }
}

// 칩 안 게이트 하나라도 고장이면 상자를 고장색으로 칠합니다. 평탄 아이디가 인스턴스 접두로 시작합니다.
function 칩고장인가(고장들: Array<string>, 칩아이디: string) -> bool {
  고장들.filter(고장노드 => 고장노드.startsWith(칩아이디 + "__")).length > 0
}

function 칩상자그리기(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>, 칩: hierarchy.놓인칩) -> string {
  let x = 칩.배치.x
  let y = 칩.배치.y
  let 아이디 = 칩.배치.아이디
  let 높이 = 칩높이(칩.정의)
  let 외곽 = if 칩고장인가(고장들, 아이디) { signalviz.고장색 } else { signalviz.먹 }
  let 입력조각들 = 칩.정의.외부입력.map(핀값 => {
    let 위치 = 칩핀위치(칩, 핀값.핀)
    let 색 = signalviz.원천색(시뮬, hierarchy.칩입력원천(분해값, 아이디, 핀값.핀).노드)
    핀선(위치, { x: x, y: 위치.y }, 색) + "<text x=\"{x + 6}\" y=\"{위치.y + 4}\" font-size=\"9\" font-family=\"monospace\" fill=\"{signalviz.흑연}\">{핀값.핀}</text>" + 핀점(아이디, 핀값.핀, 위치, 색)
  })
  let 출력조각들 = 칩.정의.외부출력.map(핀값 => {
    let 위치 = 칩핀위치(칩, 핀값.핀)
    let 색 = signalviz.원천색(시뮬, hierarchy.평탄원천(분해값, 아이디, 핀값.핀).노드)
    핀선({ x: x + 칩폭, y: 위치.y }, 위치, 색) + "<text x=\"{x + 칩폭 - 6}\" y=\"{위치.y + 4}\" text-anchor=\"end\" font-size=\"9\" font-family=\"monospace\" fill=\"{signalviz.흑연}\">{핀값.핀}</text>" + 핀점(아이디, 핀값.핀, 위치, 색)
  })
  [
    "<g class=\"node chip\" data-chip-id=\"{아이디}\">",
    "<rect x=\"{x}\" y=\"{y}\" width=\"{칩폭}\" height=\"{높이}\" rx=\"5\" fill=\"{signalviz.벨럼}\" stroke=\"{외곽}\" stroke-width=\"3\" />",
    "<rect x=\"{x + 4}\" y=\"{y + 4}\" width=\"{칩폭 - 8}\" height=\"{높이 - 8}\" rx=\"3\" fill=\"none\" stroke=\"{signalviz.흑연}\" stroke-width=\"1\" />",
    "<text x=\"{x + 칩폭 / 2}\" y=\"{y + 높이 / 2 + 4}\" text-anchor=\"middle\" font-size=\"12\" font-family=\"sans-serif\" fill=\"{외곽}\">{칩.배치.칩이름}</text>",
    입력조각들.join(""),
    출력조각들.join(""),
    "</g>",
  ].join("")
}

// 전선의 아이디는 도착 끝점입니다. 입력 핀 하나는 전선 하나만 받으므로 그리는 순서와 상관없이 유일합니다.
function 전선그리기(분해값: hierarchy.분해결과, 노드표: Map<string, netlist.노드>, 시뮬: sim.시뮬결과, 선: netlist.전선) -> string {
  let 경로 = routing.직각경로(끝점위치(분해값, 노드표, 선.시작노드, 선.시작핀), 끝점위치(분해값, 노드표, 선.끝노드, 선.끝핀))
  let 원천 = hierarchy.평탄원천(분해값, 선.시작노드, 선.시작핀).노드
  "<polyline class=\"wire\" data-wire-id=\"{선.끝노드}:{선.끝핀}\"{signalviz.원천프레임(시뮬, 원천)} points=\"{경로}\" fill=\"none\" stroke=\"{signalviz.원천색(시뮬, 원천)}\" stroke-width=\"2\" />"
}

function 격자(판: 상자) -> string {
  "<defs><pattern id=\"grid\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\"><path d=\"M20 0H0V20\" fill=\"none\" stroke=\"{signalviz.먹}\" stroke-opacity=\"0.05\" stroke-width=\"1\" /></pattern></defs><rect x=\"{판.왼}\" y=\"{판.위}\" width=\"{판.오른 - 판.왼}\" height=\"{판.아래 - 판.위}\" fill=\"url(#grid)\" />"
}

function 상자합치기(가: 상자, 나: 상자) -> 상자 {
  { 왼: 가.왼.atMost(나.왼), 위: 가.위.atMost(나.위), 오른: 가.오른.atLeast(나.오른), 아래: 가.아래.atLeast(나.아래) }
}

// 판 크기는 노드와 칩 상자를 모두 덮는 경계 상자라 음수 좌표와 팬도 덮습니다. 전선을 먼저 깔고 노드와 칩을 위에 그립니다.
export function 그리기뷰(분해값: hierarchy.분해결과, 시뮬: sim.시뮬결과, 고장들: Array<string>) -> string {
  let 칩들 = hierarchy.놓인칩들(분해값)
  let 노드표 = 노드표만들기(분해값.본체.노드들)
  let 노드상자들 = 분해값.본체.노드들.map(노드값 => ({ 왼: 노드값.x - 12, 위: 노드값.y - 22, 오른: 노드값.x + 86, 아래: 노드값.y + 62 }))
  let 칩상자들 = 칩들.map(칩 => ({ 왼: 칩.배치.x - 16, 위: 칩.배치.y - 12, 오른: 칩.배치.x + 칩폭 + 16, 아래: 칩.배치.y + 칩높이(칩.정의) + 12 }))
  let 상자들 = [for 묶음 in [노드상자들, 칩상자들] for 상자값 in 묶음 => 상자값]
  let 판 = if 상자들.length == 0 { 기본판 } else { 상자들.reduce(상자들[0], 상자합치기) }
  let 폭 = 판.오른 - 판.왼
  let 높이 = 판.아래 - 판.위
  [
    "<svg class=\"schematic\" viewBox=\"{판.왼} {판.위} {폭} {높이}\" width=\"{폭}\" height=\"{높이}\" xmlns=\"http://www.w3.org/2000/svg\">",
    격자(판),
    분해값.본체.전선들.map(선 => 전선그리기(분해값, 노드표, 시뮬, 선)).join(""),
    분해값.본체.노드들.map(노드값 => 노드그리기(분해값, 시뮬, 고장들, 노드값)).join(""),
    칩들.map(칩 => 칩상자그리기(분해값, 시뮬, 고장들, 칩)).join(""),
    "</svg>",
  ].join("")
}
