package formulae

import (
	"fmt"
	"strings"
)

/* ---------------------------------------------------------------- *
 * TYPE Formula
 * ---------------------------------------------------------------- */

type Formula struct {
	kind        string
	expr        string
	valence     int
	subformulae [](*Formula)
}

/* ---------------------------------------------------------------- *
 * METHODS
 * ---------------------------------------------------------------- */

func (fml *Formula) SetKind(kind string) {
	fml.kind = kind
}

func (fml Formula) GetKind() string {
	return fml.kind
}

func (fml *Formula) SetExpr(expr string) {
	fml.expr = expr
}

func (fml Formula) GetExpr() string {
	return fml.expr
}

func (fml *Formula) SetSubformulae(children [](*Formula)) {
	fml.subformulae = children
	fml.valence = len(children)
}

func (fml Formula) GetSubFormulae() []Formula {
	var n int = fml.valence
	var children = make([]Formula, n)
	for i, subfml := range fml.subformulae {
		children[i] = *subfml
	}
	return children
}

func (fml Formula) GetChild(indexOpt ...int) Formula {
	var index int = 0
	if len(indexOpt) > 0 {
		index = indexOpt[0]
	}
	var subfml Formula
	if 0 <= index && index < fml.valence {
		subfml = *(fml.subformulae[index])
	} else {
		panic(fmt.Sprintf("Instance has no child of index %d !", index))
	}
	return subfml
}

func (fml Formula) Pretty(preindentOpt ...string) string {
	var preindent string = ""
	if len(preindentOpt) > 0 {
		preindent = preindentOpt[0]
	}
	return fml.pretty(preindent, "  ", "", 0)
}

func (fml Formula) pretty(preindent string, tab string, prepend string, depth int) string {
	var indent string = preindent + strings.Repeat(tab, depth)
	switch fml.valence {
	case 0:
		switch kind := fml.kind; kind {
		case "atom", "generic":
			return indent + prepend + kind + " " + fml.expr
		default:
			return indent + prepend + kind
		}
	default:
		var lines string = indent + prepend + fml.kind
		prepend = "|__ "
		for _, subfml := range fml.subformulae {
			lines += "\n" + subfml.pretty(preindent, tab, prepend, depth+1)
		}
		return lines
	}
}

func (fml Formula) Deepcopy() Formula {
	var children = make([](*Formula), len(fml.subformulae))
	for i, child := range fml.subformulae {
		childCopy := child.Deepcopy()
		children[i] = &childCopy
	}
	return Formula{
		expr:        fml.expr,
		kind:        fml.kind,
		valence:     fml.valence,
		subformulae: children,
	}
}

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
 * METHODS: Basic constructions
 * ---------------------------------------------------------------- */

var Tautology = Formula{
	kind:        "taut",
	expr:        "1",
	valence:     0,
	subformulae: [](*Formula){},
}

var Contradiction = Formula{
	kind:        "contradiction",
	expr:        "0",
	valence:     0,
	subformulae: [](*Formula){},
}

func Atom(expr string) Formula {
	return Formula{
		kind:        "atom",
		expr:        expr,
		valence:     0,
		subformulae: [](*Formula){},
	}
}

func NegatedAtom(expr string) Formula {
	return Negation(Atom(expr))
}

func Generic(expr string) Formula {
	return Formula{
		kind:        "generic",
		expr:        expr,
		valence:     0,
		subformulae: [](*Formula){},
	}
}

func Negation(fml Formula) Formula {
	return Formula{
		kind:        "not",
		expr:        "!" + " " + fml.expr,
		valence:     1,
		subformulae: [](*Formula){&fml},
	}
}

func Conjunction2(fml1 Formula, fml2 Formula) Formula {
	return Formula{
		kind:        "and2",
		expr:        "(" + fml1.expr + " && " + fml2.expr + ")",
		valence:     2,
		subformulae: [](*Formula){&fml1, &fml2},
	}
}

func Conjunction(fmls []Formula) Formula {
	var expr string = ""
	var children = make([](*Formula), len(fmls))
	for i, fml := range fmls {
		if i > 0 {
			expr += " && "
		}
		expr += fml.expr
		children[i] = &fml
	}
	return Formula{
		kind:        "and",
		expr:        "(" + expr + ")",
		valence:     len(children),
		subformulae: children,
	}
}

func Disjunction2(fml1 Formula, fml2 Formula) Formula {
	return Formula{
		kind:        "or2",
		expr:        "(" + fml1.expr + " || " + fml2.expr + ")",
		valence:     2,
		subformulae: [](*Formula){&fml1, &fml2},
	}
}

func Disjunction(fmls []Formula) Formula {
	var expr string = ""
	var children = make([](*Formula), len(fmls))
	for i, fml := range fmls {
		if i > 0 {
			expr += " || "
		}
		expr += fml.expr
		children[i] = &fml
	}
	return Formula{
		kind:        "or",
		expr:        "(" + expr + ")",
		valence:     len(children),
		subformulae: children,
	}
}

func Implies(fml1 Formula, fml2 Formula) Formula {
	return Formula{
		kind:        "implies",
		expr:        "(" + fml1.expr + " -> " + fml2.expr + ")",
		valence:     2,
		subformulae: [](*Formula){&fml1, &fml2},
	}
}

/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
 * METHODS: Recognition of Formula-Types
 * ---------------------------------------------------------------- */

func (fml Formula) IsIrreducible() bool {
	return fml.valence == 0
}

func (fml Formula) IsAtom() bool {
	return fml.kind == "atom"
}

func (fml Formula) IsPositiveLiteral() bool {
	return fml.IsAtom()
}

func (fml Formula) IsNegativeLiteral() bool {
	return fml.IsNegation() && fml.GetChild().IsAtom()
}

func (fml Formula) IsLiteral() bool {
	return fml.IsPositiveLiteral() || fml.IsNegativeLiteral()
}

func (fml Formula) IsGeneric() bool {
	return fml.kind == "generic"
}

func (fml Formula) IsTautologySymbol() bool {
	return fml.kind == "taut"
}

func (fml Formula) IsContradictionSymbol() bool {
	return fml.kind == "contradiction"
}

func (fml Formula) IsConnective() bool {
	return fml.valence > 0
}

func (fml Formula) IsNegation() bool {
	return fml.kind == "not"
}

func (fml Formula) IsConjunction2() bool {
	return fml.kind == "and2"
}

func (fml Formula) IsConjunction() bool {
	return fml.kind == "and" || fml.kind == "and2"
}

func (fml Formula) IsDisjunction2() bool {
	return fml.kind == "or2"
}

func (fml Formula) IsDisjunction() bool {
	return fml.kind == "or" || fml.kind == "or2"
}

func (fml Formula) IsImplication() bool {
	return fml.kind == "implies"
}
