import { SetElementList } from "../internal/container/associative/SetElementList";
import { MultiIteratorTree } from "../internal/tree/MultiIteratorTree";

import { IForwardIterator } from "../iterator/IForwardIterator";
import { Pair } from "../utility/Pair";

import { Comparator } from "../internal/functional/Comparator";
import { less } from "../functional/comparators";

export class TreeMultiSet<Key>
{
    private data_: SetElementList<Key, false, TreeMultiSet<Key>> = new SetElementList(<TreeMultiSet<Key>>this);
    private tree_: MultiIteratorTree<Key, TreeMultiSet.Iterator<Key>>;

    public constructor(comp: Comparator<Key> = (x, y) => less(x, y))
    {
        this.tree_ = new MultiIteratorTree<Key, TreeMultiSet.Iterator<Key>>
        (
            it => it.value, 
            comp, 
            (x, y) => SetElementList.Iterator._Compare_uid<Key, false, TreeMultiSet<Key>>(x, y)
        );
    }

    @inline
    public assign<InputIterator extends IForwardIterator<Key, InputIterator>>
        (first: InputIterator, last: InputIterator): void
    {
        if (this.empty() === false)
            this.clear();
        this.insert_range<InputIterator>(first, last);
    }

    @inline
    public clear(): void
    {
        this.data_.clear();
        this.tree_.clear();
    }

    public swap(obj: TreeMultiSet<Key>): void
    {
        // SWAP ELEMENTS
        this.data_.swap(obj.data_);
        
        const data: SetElementList<Key, true, TreeMultiSet<Key>> = this.data_;
        this.data_ = obj.data_;
        obj.data_ = data;

        // SWAP TREE
        const tree: MultiIteratorTree<Key, TreeMultiSet.Iterator<Key>> = this.tree_;
        this.tree_ = obj.tree_;
        obj.tree_ = tree;
    }

    /* ---------------------------------------------------------
        ACCESSORS
    --------------------------------------------------------- */
    @inline
    public size(): usize
    {
        return this.data_.size();
    }

    @inline
    public empty(): boolean
    {
        return this.data_.empty();
    }

    @inline
    public begin(): TreeMultiSet.Iterator<Key>
    {
        return this.data_.begin();
    }

    @inline
    public end(): TreeMultiSet.Iterator<Key>
    {
        return this.data_.end();
    }

    @inline
    public rbegin(): TreeMultiSet.ReverseIterator<Key>
    {
        return this.data_.rbegin();
    }

    @inline
    public rend(): TreeMultiSet.ReverseIterator<Key>
    {
        return this.data_.rend();
    }

    @inline
    public find(key: Key): TreeMultiSet.Iterator<Key>
    {
        const it: TreeMultiSet.Iterator<Key> = this.lower_bound(key);
        return (it != this.end() && this.key_comp()(key, it.value) === false)
            ? it
            : this.end();
    }

    @inline
    public has(key: Key): boolean
    {
        return this.find(key) != this.end();
    }

    @inline
    public count(key: Key): usize
    {
        let ret: usize = 0;
        for (let it = this.find(key); it != this.end() && this.key_comp()(key, it.value) === false; it = it.next())
            ++ret;
        return ret;
    }

    @inline
    public key_comp(): Comparator<Key>
    {
        return this.tree_.key_comp();
    }

    @inline
    public lower_bound(key: Key): TreeMultiSet.Iterator<Key>
    {
        return this.tree_.lower_bound(this.end(), key);
    }

    @inline
    public upper_bound(key: Key): TreeMultiSet.Iterator<Key>
    {
        return this.tree_.upper_bound(this.end(), key);
    }

    @inline
    public equal_range(key: Key): Pair<TreeMultiSet.Iterator<Key>, TreeMultiSet.Iterator<Key>>
    {
        return this.tree_.equal_range(this.end(), key);
    }

    /* ---------------------------------------------------------
        ELEMENTS I/O
    --------------------------------------------------------- */
    public insert(key: Key): TreeMultiSet.Iterator<Key>
    {
        const upper: TreeMultiSet.Iterator<Key> = this.upper_bound(key);
        const it: TreeMultiSet.Iterator<Key> = this.data_.insert(upper, key);

        this.tree_.insert(it);
        return it;
    }

    @inline
    public insert_hint(hint: TreeMultiSet.Iterator<Key>, key: Key): TreeMultiSet.Iterator<Key>
    {
        return this.insert(key);
    }

    public insert_range<InputIterator extends IForwardIterator<Key, InputIterator>>
        (first: InputIterator, last: InputIterator): void
    {
        for (; first != last; first = first.next())
            this.insert(first.value);
    }
    
    public erase(first: TreeMultiSet.Iterator<Key>, last: TreeMultiSet.Iterator<Key> = first.next()): TreeMultiSet.Iterator<Key>
    {
        const ret = this.data_.erase(first, last);
        for (; first != last; first = first.next())
            this.tree_.erase(first);

        return ret;
    }

    public erase_by_key(key: Key): usize
    {
        let it: TreeMultiSet.Iterator<Key> = this.find(key);
        if (it == this.end())
            return 0;

        let count: usize = 0;
        while (it != this.end() && this.key_comp()(key, it.value) === false)
        {
            it = this.erase(it);
            ++count;
        }
        return count;
    }
}

export namespace TreeMultiSet
{
    export type Iterator<Key> = SetElementList.Iterator<Key, false, TreeMultiSet<Key>>;
    export type ReverseIterator<Key> = SetElementList.ReverseIterator<Key, false, TreeMultiSet<Key>>;
}