package main

import (
	"fmt"
	"strconv"
	"strings"
	"unicode"
)

// 最大的整数
const MaxInt = int(^uint(0) >> 1)

type Page struct {
	numbers    []PageNumber
	compositor string
	encap      string
	rangetype  RangeType
}

// 按 p 生成一个与之输出类型相同的空页码
func (p *Page) Empty() *Page {
	return &Page{
		numbers:    nil,
		compositor: p.compositor,
		encap:      p.encap,
		rangetype:  PAGE_UNKNOWN,
	}
}

func (p *Page) String() string {
	var page_str []string
	for _, pn := range p.numbers {
		page_str = append(page_str, pn.String())
	}
	return strings.Join(page_str, p.compositor)
}

// 判断两个页码是否类型一致
// 两个页码类型一致指它们有相同多个类型相同的数字构成
func (page *Page) Compatible(other *Page) bool {
	if len(page.numbers) != len(other.numbers) {
		return false
	}
	for i := 0; i < len(page.numbers); i++ {
		if page.numbers[i].format != other.numbers[i].format {
			return false
		}
	}
	return true
}

// 求两个页码 page 与 other 之差(绝对值)
// 如果不一致,返回 -1;如果不是最后一段数字不同,返回 MaxInt
func (page *Page) Diff(other *Page) int {
	if !page.Compatible(other) {
		return -1
	}
	depth := len(page.numbers)
	for i := 0; i < depth-1; i++ {
		if page.numbers[i].num != other.numbers[i].num {
			return MaxInt
		}
	}
	abs := func(x int) int {
		if x >= 0 {
			return x
		} else {
			return -x
		}
	}
	return abs(page.numbers[depth-1].num - other.numbers[depth-1].num)
}

// 按字典序比较两个页码数字串的大小,返回负、零、正值
// 不同类型的序关系由参数 precedence 给出,不一致的页码仍有大小关系
// 不比较页码的 encap、rangetype 信息
func (page *Page) Cmp(other *Page, precedence map[NumFormat]int) int {
	for i := 0; i < len(page.numbers) && i < len(other.numbers); i++ {
		a, b := page.numbers[i], other.numbers[i]
		if precedence[a.format] != precedence[b.format] {
			return precedence[a.format] - precedence[b.format]
		} else if a.num != b.num {
			return a.num - b.num
		}
	}
	if len(page.numbers) != len(other.numbers) {
		return len(page.numbers) - len(other.numbers)
	}
	return 0
}

type PageNumber struct {
	format NumFormat
	num    int
}

func (p PageNumber) String() string {
	return p.format.Format(p.num)
}

type NumFormat int

const (
	NUM_UNKNOWN NumFormat = iota
	NUM_ARABIC
	NUM_ROMAN_LOWER
	NUM_ROMAN_UPPER
	NUM_ALPH_LOWER
	NUM_ALPH_UPPER
)

// 将字符串解析为一串页码数字
func scanPage(token []rune, compositor string) ([]PageNumber, error) {
	numstr_list := strings.Split(string(token), compositor)
	var nums []PageNumber
	for _, numstr := range numstr_list {
		pn, err := scanNumber([]rune(numstr))
		if err != nil {
			return nil, err
		}
		nums = append(nums, pn)
	}
	return nums, nil
}

// 读入数字,并粗略判断其格式
func scanNumber(token []rune) (PageNumber, error) {
	if len(token) == 0 {
		return PageNumber{}, ScanSyntaxError
	}
	if r := token[0]; unicode.IsDigit(r) {
		num, err := scanArabic(token)
		return PageNumber{format: NUM_ARABIC, num: num}, err
	} else if romanLowerValue[r] != 0 {
		num, err := scanRomanLower(token)
		return PageNumber{format: NUM_ROMAN_LOWER, num: num}, err
	} else if romanUpperValue[r] != 0 {
		num, err := scanRomanUpper(token)
		return PageNumber{format: NUM_ROMAN_UPPER, num: num}, err
	} else if 'a' <= r && r <= 'z' {
		num, err := scanAlphLower(token)
		return PageNumber{format: NUM_ALPH_LOWER, num: num}, err
	} else if 'A' <= r && r <= 'Z' {
		num, err := scanAlphUpper(token)
		return PageNumber{format: NUM_ALPH_UPPER, num: num}, err
	}
	return PageNumber{}, ScanSyntaxError
}

func scanArabic(token []rune) (int, error) {
	num, err := strconv.Atoi(string(token))
	if err != nil {
		err = ScanSyntaxError
	}
	return num, err
}

func scanRomanLower(token []rune) (int, error) {
	return scanRoman(token, romanLowerValue)
}

func scanRomanUpper(token []rune) (int, error) {
	return scanRoman(token, romanUpperValue)
}

func scanRoman(token []rune, romantable map[rune]int) (int, error) {
	num := 0
	for i, r := range token {
		if romantable[r] == 0 {
			return 0, ScanSyntaxError
		}
		if i == 0 || romantable[r] <= romantable[token[i-1]] {
			num += romantable[r]
		} else {
			num += romantable[r] - 2*romantable[token[i-1]]
		}
	}
	return num, nil
}

var romanLowerValue = map[rune]int{
	'i': 1, 'v': 5, 'x': 10, 'l': 50, 'c': 100, 'd': 500, 'm': 1000,
}
var romanUpperValue = map[rune]int{
	'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000,
}

func scanAlphLower(token []rune) (int, error) {
	if len(token) != 1 || token[0] < 'a' || token[0] > 'z' {
		return 0, ScanSyntaxError
	}
	return int(token[0]-'a') + 1, nil
}

func scanAlphUpper(token []rune) (int, error) {
	if len(token) != 1 || token[0] < 'A' || token[0] > 'Z' {
		return 0, ScanSyntaxError
	}
	return int(token[0]-'A') + 1, nil
}

// 按格式输出数字
func (numfmt NumFormat) Format(num int) string {
	switch numfmt {
	case NUM_UNKNOWN:
		return "?"
	case NUM_ARABIC:
		return fmt.Sprint(num)
	case NUM_ALPH_LOWER:
		return string('a' + num)
	case NUM_ALPH_UPPER:
		return string('A' + num)
	case NUM_ROMAN_LOWER:
		return romanNumString(num, false)
	case NUM_ROMAN_UPPER:
		return romanNumString(num, true)
	default:
		panic("数字格式错误")
	}
}

func romanNumString(num int, upper bool) string {
	if num < 1 {
		return ""
	}
	type pair struct {
		symbol string
		value  int
	}
	var romanTable = []pair{
		{"m", 1000}, {"cm", 900}, {"d", 500}, {"cd", 400}, {"c", 100}, {"xc", 90},
		{"l", 50}, {"xl", 40}, {"x", 10}, {"ix", 9}, {"v", 5}, {"iv", 4}, {"i", 1},
	}
	var numstr []rune
	for _, p := range romanTable {
		for num >= p.value {
			numstr = append(numstr, []rune(p.symbol)...)
			num -= p.value
		}
	}
	if upper {
		return strings.ToUpper(string(numstr))
	} else {
		return string(numstr)
	}
}