import ceylon.language.impl {
    BaseIterable,
    BaseIterator
}

"Need a map-like thing, but can't use java.util.HashMap directly, 
 not ceylon.collection::HashMap"
native class NativeMap<Key,Element>() {
    shared native Element? get(Key id);
    shared native void put(Key id, Element instanceOrPartial);
    shared native Boolean contains(Key id);
    shared native {Key*} keys;
    shared native {Element*} items;
    shared native Integer size;
    shared native actual String string;
}


native("jvm") class NativeMap<Key,Element>() {
    
    import java.util {
        HashMap,
        JavaIterator=Iterator
    }
    native("jvm") HashMap<Key, Element> m = HashMap<Key, Element>();
    
    shared native("jvm") void put(Key id, Element instanceOrPartial) {
        m.put(id, instanceOrPartial);
    }
    
    shared native("jvm") Element? get(Key id) {
        return m.get(id of Object?);
    }
    
    shared native("jvm") Boolean contains(Key id) {
        return m.containsKey(id of Object?);
    }
    
    shared native("jvm") Integer size {
        return m.size();
    }
    
    shared native("jvm") Iterable<Element, Null> items {
        return object extends BaseIterable<Element,Null>() satisfies Identifiable {
            
            shared actual Iterator<Element> iterator() {
                JavaIterator<Element> it = m.values().iterator();
                return object extends BaseIterator<Element>() satisfies Identifiable {
                    
                    shared actual Element|Finished next() {
                        if (it.hasNext()) {
                            return it.next();
                        } else { 
                            return finished;
                        }
                    }
                };
            }
        };
    }
    
    
    shared native("jvm") Iterable<Key, Null> keys {
        return object extends BaseIterable<Key,Null>() satisfies Identifiable {
            
            shared actual Iterator<Key> iterator() {
                JavaIterator<Key> it = m.keySet().iterator();
                return object extends BaseIterator<Key>() satisfies Identifiable {
                    
                    shared actual Key|Finished next() {
                        if (it.hasNext()) {
                            return it.next();
                        } else { 
                            return finished;
                        }
                    }
                };
            }
        };
    }
    
    shared native("jvm") actual String string {
        return m.string;
    }
}

native("js") class NativeMap<Key,Element>() {

    dynamic ks;
    dynamic vs;
    dynamic {
        ks = dynamic[null];
        vs = dynamic[null];
    }
    "Find the index of the specified key, or -1 if it isn't in the map"
    Integer find(Key id) {
        if (id exists) {
            dynamic {
                Integer len=ks.length;
                if (len < 2) {
                    return -1;
                }
                for (i in 1:len-1) {
                    if (id == ks[i]) {
                        return i;
                    }
                }
            }
            return -1;
        }
        return 0;
    }

    shared native("js") void put(Key id, Element instanceOrPartial) {
        value i = find(id);
        if (i >= 0) {
            //replace
            dynamic {
                setObjectProperty(vs, i, instanceOrPartial);
            }
        } else if (i < 0) {
            //new entry
            dynamic {
                ks.push(id);
                vs.push(instanceOrPartial);
            }
        }
    }
    
    shared native("js") Element? get(Key id) {
        value i = find(id);
        dynamic {
            return i >= 0 then vs[i] else null;
        }
    }
    
    shared native("js") Boolean contains(Key id) =>
        find(id) >= 0;
    
    shared native("js") Integer size {
        dynamic {
            return ks.length-1;
        }
    }
    
    shared native("js") Iterable<Element, Null> items {
        return object extends BaseIterable<Element,Null>() satisfies Identifiable {
            
            shared actual Iterator<Element> iterator() {
                variable value i=1;
                return object extends BaseIterator<Element>() satisfies Identifiable {
                    
                    shared actual Element|Finished next() {
                        dynamic {
                            if (i >= vs.length) {
                                return finished;
                            }
                            Element e = vs[i];
                            i++;
                            return e;
                        }
                    }
                };
            }
        };
    }
    
    
    shared native("js") Iterable<Key, Null> keys {
        return object extends BaseIterable<Key,Null>() satisfies Identifiable {
            
            shared actual Iterator<Key> iterator() {
                variable value i=1;
                return object extends BaseIterator<Key>() satisfies Identifiable {
                    
                    shared actual Key|Finished next() {
                        dynamic {
                            if (i >= ks.length) {
                                return finished;
                            }
                            Key e = ks[i];
                            i++;
                            return e;
                        }
                    }
                };
            }
        };
    }
    
    shared native("js") actual String string {
        value sb=StringBuilder();
        sb.append("{");
        dynamic {
            if (vs[0] exists) {
                sb.append("<null> ->").append(vs[0].string);
            }
            if (ks.length>1) {
                for (i in 1:ks.length-1) {
                    if (sb.size > 1) {
                        sb.append(", ");
                    }
                    sb.append(ks[i]).append("->");
                    if (vs[i] exists) {
                        sb.append(vs[i].string);
                    } else {
                        sb.append("[null]");
                    }
                }
            }
        }
        sb.append("}");
        return sb.string;
    }
}