package cangjie_tpc::prism4cj.prism

abstract class Cloner {
    protected func clone(grammar: Grammar): Grammar

    protected func clone(grammar: Token): Token

    protected func clone(grammar: Pattern): Pattern

    static func create(): Cloner {
        return ClonerImpl()
    }
}

class ClonerImpl <: Cloner {
    protected func clone(grammar: Grammar): Grammar {
        return clone(ContextImpl(), grammar)
    }

    protected func clone(token: Token): Token {
        return clone(ContextImpl(), token)
    }

    protected func clone(pattern: Pattern): Pattern {
        return clone(ContextImpl(), pattern)
    }

    func clone(context: Context, grammar: Grammar): Grammar {
        var contextGrammar: ?Grammar = context.grammar(grammar)
        if (let Some(v) <- contextGrammar) {
            return v
        }
        let tokens: ArrayList<Token> = grammar.tokens()
        var out: ArrayList<Token> = ArrayList<Token>(tokens.size)
        contextGrammar = GrammarImpl(grammar.name(), out)
        context.save(MyHashKey<Grammar>(grammar), contextGrammar.getOrThrow())
        for (i in 0..tokens.size) {
            out.add(clone(context, tokens[i]))
        }
        return contextGrammar.getOrThrow()
    }

    func clone(context: Context, token: Token): Token {
        var contextToken: ?Token = context.token(token)
        if (let Some(v) <- contextToken) {
            return v
        }
        var patterns: ArrayList<Pattern> = token.patterns()
        var out: ArrayList<Pattern> = ArrayList<Pattern>(patterns.size)
        contextToken = TokenImpl(token.name(), out)
        context.save(MyHashKey<Token>(token), contextToken.getOrThrow())
        for (i in 0..patterns.size) {
            out.add(clone(context, patterns[i]))
        }
        return contextToken.getOrThrow()
    }

    func clone(context: Context, pattern: Pattern): Pattern {
        var contextPattern: ?Pattern = context.pattern(pattern)
        if (let Some(v) <- contextPattern) {
            return v
        }
        var inside: ?Grammar = pattern.inside()
        if (let Some(v) <- inside) {
            contextPattern = PatternImpl(pattern.regex(), pattern.lookbehind(), pattern.greedy(), pattern.alias(),
                clone(context, v))
        } else {
            contextPattern = PatternImpl(pattern.regex(), pattern.lookbehind(), pattern.greedy(), pattern.alias(), None)
        }
        context.save(MyHashKey<Pattern>(pattern), contextPattern.getOrThrow())
        return contextPattern.getOrThrow()
    }
}

interface Context {
    func grammar(origin: Grammar): ?Grammar

    func token(origin: Token): ?Token

    func pattern(origin: Pattern): ?Pattern

    func save(origin: MyHashKey<Grammar>, clone: Grammar): Unit

    func save(origin: MyHashKey<Token>, clone: Token): Unit

    func save(origin: MyHashKey<Pattern>, clone: Pattern): Unit
}

class ContextImpl <: Context {
    private let cache: HashMap<Int64, Any> = HashMap<Int64, Any>(3)

    public func grammar(origin: Grammar): ?Grammar {
        return match (cache.get(origin.hashCode())) {
            case Some(v) => v as Grammar
            case None => None
        }
    }

    public func token(origin: Token): ?Token {
        return match (cache.get(origin.hashCode())) {
            case Some(v) => v as Token
            case None => None
        }
    }

    public func pattern(origin: Pattern): ?Pattern {
        return match (cache.get(origin.hashCode())) {
            case Some(v) => v as Pattern
            case None => None
        }
    }

    public func save(origin: MyHashKey<Grammar>, clone: Grammar): Unit {
        cache.add(origin.hashCode(), clone)
    }

    public func save(origin: MyHashKey<Token>, clone: Token): Unit {
        cache.add(origin.hashCode(), clone)
    }

    public func save(origin: MyHashKey<Pattern>, clone: Pattern): Unit {
        cache.add(origin.hashCode(), clone)
    }
}

public class MyHashKey<T> <: Hashable & Equatable<MyHashKey<T>> where T <: Hashable & Equatable<T> {
    let realKey: T
    public init(realKey: T) {
        this.realKey = realKey
    }
    public func hashCode(): Int64 {
        realKey.hashCode()
    }
    public operator func ==(a: MyHashKey<T>): Bool {
        return realKey == a.realKey
    }
    public operator func !=(a: MyHashKey<T>): Bool {
        return realKey != a.realKey
    }
}