/**
 * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import Iterator from 'mol-data/iterator'
import { HashSet } from 'mol-data/util'
import { Structure, Atom, AtomSet } from '../structure'

type Selection =
    | Structure // each atom is interpreted as a singleton structure
    | Structure[]

// TODO: Do not allow to change unit set in the middle of a query, create a differnt language to control assemblies etc.
/*
type Selection =
  | { kind: 'sequence', structure: Structure, sets: AtomSet[] }
  | { kind: 'atom-set', structure: Structure, set: AtomSet }

  structure allows for good unions.
*/

namespace Selection {
    export const Empty: Selection = [];

    function isStructure(x: Selection): x is Structure { return !!(x as Structure).units && !!(x as Structure).atoms; }

    export const isOfSingletons = isStructure

    export function structureCount(sel: Selection) {
        if (isStructure(sel)) return AtomSet.atomCount(sel.atoms);
        return sel.length;
    }

    export function union(sel: Selection): Structure {
        if (isStructure(sel)) return sel;
        if (!sel.length) return Structure.Empty;
        const sets = [];
        for (let i = 0, _i = sel.length; i < _i; i++) sets[sets.length] = sel[i].atoms;
        return Structure.create(unionUnits(sel), AtomSet.union(sets, AtomSet.Empty));
    }

    export function structures(sel: Selection): Iterator<Structure> {
        if (isStructure(sel)) {
            const units = sel.units;
            return Iterator.map<Atom, Structure>(AtomSet.atoms(sel.atoms), atoms => Structure.create(units, atoms));
        }
        return Iterator.Array(sel);
    }

    export function getAt(sel: Selection, i: number): Structure {
        if (isStructure(sel)) {
            return Structure.create(sel.units, AtomSet.atomGetAt(sel.atoms, i));
        }
        return sel[i];
    }

    export interface Builder {
        add(s: Structure): void,
        getSelection(): Selection
    }

    class LinearBuilderImpl implements Builder {
        private structures: Structure[] = [];
        private allSingletons = true;

        add(s: Structure) {
            const atomCount = AtomSet.atomCount(s.atoms);
            if (atomCount === 0) return;
            this.structures[this.structures.length] = s;
            if (atomCount !== 1) this.allSingletons = false;
        }

        getSelection() {
            const len = this.structures.length;
            if (len === 0) return Empty;
            if (len === 1) return this.structures[0];
            if (this.allSingletons) return union(this.structures);
            return this.structures;
        }

        constructor() { }
    }

    class HashBuilderImpl implements Builder {
        private structures: Structure[] = [];
        private allSingletons = true;
        private sets = HashSet(AtomSet.hashCode, AtomSet.areEqual);

        add(s: Structure) {
            const atomCount = AtomSet.atomCount(s.atoms);
            if (atomCount === 0 || !this.sets.add(s.atoms)) return;
            this.structures[this.structures.length] = s;
            if (atomCount !== 1) this.allSingletons = false;
        }

        getSelection() {
            const len = this.structures.length;
            if (len === 0) return Empty;
            if (len === 1) return this.structures[0];
            if (this.allSingletons) return union(this.structures);
            return this.structures;
        }

        constructor() { }
    }

    export function LinearBuilder(): Builder { return new LinearBuilderImpl(); }
    export function UniqueBuilder(): Builder { return new HashBuilderImpl(); }

    // TODO: spatial lookup
}

export default Selection

function unionUnits(xs: Structure[]): Structure['units'] {
    return xs[0].units;
//    let prev = xs[0].units;
    // let sameUnits = true;
    // for (let i = 1, _i = xs.length; i < _i; i++) {
    //     if (xs[i].units !== prev) sameUnits = false;
    // }
    // if (sameUnits) return prev;

    // const ret = [...prev];
    // for (let i = 1, _i = xs.length; i < _i; i++) {
    //     const units = xs[i].units;
    //     if (units !== prev) IntMap.addFrom(ret, units);
    //     prev = units;
    // }

    //return ret;
}