1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
#[cfg(test)]
mod tests;

use crate::{
    builtins::{
        function::NativeFunctionData,
        object::{Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE},
        property::Property,
        value::{from_value, to_value, undefined, ResultValue, Value, ValueData},
    },
    exec::Interpreter,
};
use gc::Gc;
use std::borrow::Borrow;
use std::cmp::{max, min};

pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue {
    let array = ValueData::new_obj(Some(
        &interpreter
            .get_realm()
            .environment
            .get_global_object()
            .expect("Could not get global object"),
    ));
    array.set_kind(ObjectKind::Array);
    array.borrow().set_internal_slot(
        INSTANCE_PROTOTYPE,
        interpreter
            .get_realm()
            .environment
            .get_binding_value("Array")
            .borrow()
            .get_field_slice(PROTOTYPE),
    );
    array.borrow().set_field_slice("length", to_value(0));
    Ok(array)
}

/// Utility function for creating array objects: `array_obj` can be any array with
/// prototype already set (it will be wiped and recreated from `array_contents`)
pub fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue {
    let array_obj_ptr = array_obj.clone();

    // Wipe existing contents of the array object
    let orig_length: i32 =
        from_value(array_obj.get_field_slice("length")).expect("failed to convert length to i32");
    for n in 0..orig_length {
        array_obj_ptr.remove_prop(&n.to_string());
    }

    // Create length
    let length = Property::new()
        .value(to_value(array_contents.len() as i32))
        .writable(true)
        .configurable(false)
        .enumerable(false);

    array_obj_ptr.set_prop("length".to_string(), length);

    for (n, value) in array_contents.iter().enumerate() {
        array_obj_ptr.set_field_slice(&n.to_string(), value.clone());
    }
    Ok(array_obj_ptr)
}

/// Utility function which takes an existing array object and puts additional
/// values on the end, correctly rewriting the length
pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue {
    let orig_length: i32 =
        from_value(array_ptr.get_field_slice("length")).expect("failed to conveert lenth to i32");

    for (n, value) in add_values.iter().enumerate() {
        let new_index = orig_length.wrapping_add(n as i32);
        array_ptr.set_field_slice(&new_index.to_string(), value.clone());
    }

    array_ptr.set_field_slice(
        "length",
        to_value(orig_length.wrapping_add(add_values.len() as i32)),
    );

    Ok(array_ptr.clone())
}

/// Create a new array
pub fn make_array(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
    // Make a new Object which will internally represent the Array (mapping
    // between indices and values): this creates an Object with no prototype

    // Create length
    let length = Property::new()
        .value(to_value(args.len() as i32))
        .writable(true)
        .configurable(false)
        .enumerable(false);

    this.set_prop("length".to_string(), length);

    // Set Prototype
    let array_prototype = ctx
        .realm
        .global_obj
        .get_field_slice("Array")
        .get_field_slice(PROTOTYPE);

    this.set_internal_slot(INSTANCE_PROTOTYPE, array_prototype);
    // This value is used by console.log and other routines to match Object type
    // to its Javascript Identifier (global constructor method name)
    this.set_kind(ObjectKind::Array);

    // And finally add our arguments in
    for (n, value) in args.iter().enumerate() {
        this.set_field_slice(&n.to_string(), value.clone());
    }

    Ok(this.clone())
}

/// Array.isArray ( arg )
///
/// The isArray function takes one argument arg, and returns the Boolean value true
/// if the argument is an object whose class internal property is "Array"; otherwise it returns false.
/// <https://tc39.es/ecma262/#sec-array.isarray>
/// ECMA-262 v5, 15.4.3.2
pub fn is_array(_this: &Value, args: &[Value], _interpreter: &mut Interpreter) -> ResultValue {
    let value_true = Gc::new(ValueData::Boolean(true));
    let value_false = Gc::new(ValueData::Boolean(false));

    match args.get(0) {
        Some(arg) => {
            match *(*arg).clone() {
                // 1.
                ValueData::Object(ref obj) => {
                    // 2.
                    if obj.borrow().kind == ObjectKind::Array {
                        return Ok(value_true);
                    }
                    Ok(value_false)
                }
                // 3.
                _ => Ok(value_false),
            }
        }
        None => Ok(value_false),
    }
}

/// Array.prototype.concat(...arguments)
///
/// When the concat method is called with zero or more arguments, it returns an
/// array containing the array elements of the object followed by the array
/// elements of each argument in order.
/// <https://tc39.es/ecma262/#sec-array.prototype.concat>
pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        // If concat is called with no arguments, it returns the original array
        return Ok(this.clone());
    }

    // Make a new array (using this object as the prototype basis for the new
    // one)
    let mut new_values: Vec<Value> = Vec::new();

    let this_length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
    for n in 0..this_length {
        new_values.push(this.get_field_slice(&n.to_string()));
    }

    for concat_array in args {
        let concat_length: i32 = from_value(concat_array.get_field_slice("length"))
            .expect("Could not convert argument to i32");
        for n in 0..concat_length {
            new_values.push(concat_array.get_field_slice(&n.to_string()));
        }
    }

    construct_array(this, &new_values)
}

/// Array.prototype.push ( ...items )
///
/// The arguments are appended to the end of the array, in the order in which
/// they appear. The new length of the array is returned as the result of the
/// call.
/// <https://tc39.es/ecma262/#sec-array.prototype.push>
pub fn push(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    let new_array = add_to_array_object(this, args)?;
    Ok(new_array.get_field_slice("length"))
}

/// Array.prototype.pop ( )
///
/// The last element of the array is removed from the array and returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.pop>
pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
    let curr_length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
    if curr_length < 1 {
        return Ok(Gc::new(ValueData::Undefined));
    }
    let pop_index = curr_length.wrapping_sub(1);
    let pop_value: Value = this.get_field_slice(&pop_index.to_string());
    this.remove_prop(&pop_index.to_string());
    this.set_field_slice("length", to_value(pop_index));
    Ok(pop_value)
}

/// Array.prototype.forEach ( callbackFn [ , thisArg ] )
///
/// This method executes the provided callback function for each element in the array.
/// <https://tc39.es/ecma262/#sec-array.prototype.foreach>
pub fn for_each(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "Missing argument for Array.prototype.forEach".to_string(),
        ));
    }

    let callback_arg = args.get(0).expect("Could not get `callbackFn` argument.");
    let this_arg = args.get(1).cloned().unwrap_or_else(undefined);

    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not get `length` property.");

    for i in 0..length {
        let element = this.get_field_slice(&i.to_string());
        let arguments = vec![element.clone(), to_value(i), this.clone()];

        interpreter.call(callback_arg, &this_arg, arguments)?;
    }

    Ok(Gc::new(ValueData::Undefined))
}

/// Array.prototype.join ( separator )
///
/// The elements of the array are converted to Strings, and these Strings are
/// then concatenated, separated by occurrences of the separator. If no
/// separator is provided, a single comma is used as the separator.
/// <https://tc39.es/ecma262/#sec-array.prototype.join>
pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    let separator = if args.is_empty() {
        String::from(",")
    } else {
        args.get(0).expect("Could not get argument").to_string()
    };

    let mut elem_strs: Vec<String> = Vec::new();
    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
    for n in 0..length {
        let elem_str: String = this.get_field_slice(&n.to_string()).to_string();
        elem_strs.push(elem_str);
    }

    Ok(to_value(elem_strs.join(&separator)))
}

/// Array.prototype.toString ( separator )
///
/// The toString function is intentionally generic; it does not require that
/// its this value be an Array object. Therefore it can be transferred to
/// other kinds of objects for use as a method.
/// <https://tc39.es/ecma262/#sec-array.prototype.tostring>
pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
    let method_name = "join";
    let mut arguments = vec![to_value(",")];
    // 2.
    let mut method: Value =
        from_value(this.get_field_slice(method_name)).expect("failed to get Array.prototype.join");
    // 3.
    if !method.is_function() {
        method = _ctx
            .realm
            .global_obj
            .get_field_slice("Object")
            .get_field_slice(PROTOTYPE)
            .get_field_slice("toString");

        method = from_value(method).expect("failed to get Object.prototype.toString");
        arguments = vec![];
    }
    // 4.
    let join_result = _ctx.call(&method, this, arguments);
    let match_string = match join_result {
        Ok(v) => match *v {
            ValueData::String(ref s) => (*s).clone(),
            _ => "".to_string(),
        },
        Err(v) => format!("error: {}", v),
    };
    Ok(to_value(match_string))
}

/// Array.prototype.reverse ( )
///
/// The elements of the array are rearranged so as to reverse their order.
/// The object is returned as the result of the call.
/// <https://tc39.es/ecma262/#sec-array.prototype.reverse/>
#[allow(clippy::else_if_without_else)]
pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
    let len: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
    let middle: i32 = len.wrapping_div(2);

    for lower in 0..middle {
        let upper = len.wrapping_sub(lower).wrapping_sub(1);

        let upper_exists = this.has_field(&upper.to_string());
        let lower_exists = this.has_field(&lower.to_string());

        let upper_value = this.get_field_slice(&upper.to_string());
        let lower_value = this.get_field_slice(&lower.to_string());

        if upper_exists && lower_exists {
            this.set_field_slice(&upper.to_string(), lower_value);
            this.set_field_slice(&lower.to_string(), upper_value);
        } else if upper_exists {
            this.set_field_slice(&lower.to_string(), upper_value);
            this.remove_prop(&upper.to_string());
        } else if lower_exists {
            this.set_field_slice(&upper.to_string(), lower_value);
            this.remove_prop(&lower.to_string());
        }
    }

    Ok(this.clone())
}

/// Array.prototype.shift ( )
///
/// The first element of the array is removed from the array and returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.shift/>
pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
    let len: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");

    if len == 0 {
        this.set_field_slice("length", to_value(0_i32));
        // Since length is 0, this will be an Undefined value
        return Ok(this.get_field_slice(&0.to_string()));
    }

    let first: Value = this.get_field_slice(&0.to_string());

    for k in 1..len {
        let from = k.to_string();
        let to = (k.wrapping_sub(1)).to_string();

        let from_value = this.get_field_slice(&from);
        if from_value == Gc::new(ValueData::Undefined) {
            this.remove_prop(&to);
        } else {
            this.set_field_slice(&to, from_value);
        }
    }

    let final_index = len.wrapping_sub(1);
    this.remove_prop(&(final_index).to_string());
    this.set_field_slice("length", to_value(final_index));

    Ok(first)
}

/// Array.prototype.unshift ( ...items )
///
/// The arguments are prepended to the start of the array, such that their order
/// within the array is the same as the order in which they appear in the
/// argument list.
/// <https://tc39.es/ecma262/#sec-array.prototype.unshift/>
pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    let len: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");
    let arg_c: i32 = args.len() as i32;

    if arg_c > 0 {
        for k in (1..=len).rev() {
            let from = (k.wrapping_sub(1)).to_string();
            let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string();

            let from_value = this.get_field_slice(&from);
            if from_value == Gc::new(ValueData::Undefined) {
                this.remove_prop(&to);
            } else {
                this.set_field_slice(&to, from_value);
            }
        }
        for j in 0..arg_c {
            this.set_field_slice(
                &j.to_string(),
                args.get(j as usize)
                    .expect("Could not get argument")
                    .clone(),
            );
        }
    }

    let temp = len.wrapping_add(arg_c);
    this.set_field_slice("length", to_value(temp));
    Ok(to_value(temp))
}

/// Array.prototype.every ( callback, [ thisArg ] )
///
/// The every method executes the provided callback function once for each
/// element present in the array until it finds the one where callback returns
/// a falsy value. It returns `false` if it finds such element, otherwise it
/// returns `true`.
/// <https://tc39.es/ecma262/#sec-array.prototype.every/>
pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "missing callback when calling function Array.prototype.every".to_string(),
        ));
    }
    let callback = &args[0];
    let this_arg = if args.len() > 1 {
        args[1].clone()
    } else {
        Gc::new(ValueData::Undefined)
    };
    let mut i = 0;
    let max_len: i32 = from_value(this.get_field_slice("length")).unwrap();
    let mut len = max_len;
    while i < len {
        let element = this.get_field_slice(&i.to_string());
        let arguments = vec![element.clone(), to_value(i), this.clone()];
        let result = interpreter.call(callback, &this_arg, arguments)?.is_true();
        if !result {
            return Ok(to_value(false));
        }
        len = min(max_len, from_value(this.get_field_slice("length")).unwrap());
        i += 1;
    }
    Ok(to_value(true))
}

/// Array.prototype.map ( callback, [ thisArg ] )
///
/// For each element in the array the callback function is called, and a new
/// array is constructed from the return values of these calls.
/// <https://tc39.es/ecma262/#sec-array.prototype.map>
pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "missing argument 0 when calling function Array.prototype.map",
        ));
    }

    let callback = args.get(0).cloned().unwrap_or_else(undefined);
    let this_val = args.get(1).cloned().unwrap_or_else(undefined);

    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not get `length` property.");

    let new = new_array(&interpreter)?;

    let values = (0..length)
        .map(|idx| {
            let element = this.get_field_slice(&idx.to_string());

            let args = vec![element, to_value(idx), new.clone()];

            interpreter
                .call(&callback, &this_val, args)
                .unwrap_or_else(|_| undefined())
        })
        .collect::<Vec<Value>>();

    construct_array(&new, &values)
}

/// Array.prototype.indexOf ( searchElement[, fromIndex ] )
///
///
/// indexOf compares searchElement to the elements of the array, in ascending order,
/// using the Strict Equality Comparison algorithm, and if found at one or more indices,
/// returns the smallest such index; otherwise, -1 is returned.
///
/// The optional second argument fromIndex defaults to 0 (i.e. the whole array is searched).
/// If it is greater than or equal to the length of the array, -1 is returned,
/// i.e. the array will not be searched. If it is negative, it is used as the offset
/// from the end of the array to compute fromIndex. If the computed index is less than 0,
/// the whole array will be searched.
/// <https://tc39.es/ecma262/#sec-array.prototype.indexof>
pub fn index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    // If no arguments, return -1. Not described in spec, but is what chrome does.
    if args.is_empty() {
        return Ok(to_value(-1));
    }

    let search_element = args[0].clone();
    let len: i32 = from_value(this.get_field_slice("length"))
        .expect("Expected array property \"length\" is not set.");

    let mut idx = match args.get(1) {
        Some(from_idx_ptr) => {
            let from_idx = from_value(from_idx_ptr.clone())
                .expect("Error parsing \"Array.prototype.indexOf - fromIndex\" argument");

            if from_idx < 0 {
                len + from_idx
            } else {
                from_idx
            }
        }
        None => 0,
    };

    while idx < len {
        let check_element = this.get_field_slice(&idx.to_string()).clone();

        if check_element == search_element {
            return Ok(to_value(idx));
        }

        idx += 1;
    }

    Ok(to_value(-1))
}

/// Array.prototype.lastIndexOf ( searchElement[, fromIndex ] )
///
///
/// lastIndexOf compares searchElement to the elements of the array in descending order
/// using the Strict Equality Comparison algorithm, and if found at one or more indices,
/// returns the largest such index; otherwise, -1 is returned.
///
/// The optional second argument fromIndex defaults to the array's length minus one
/// (i.e. the whole array is searched). If it is greater than or equal to the length of the array,
/// the whole array will be searched. If it is negative, it is used as the offset from the end
/// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.lastindexof>
pub fn last_index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    // If no arguments, return -1. Not described in spec, but is what chrome does.
    if args.is_empty() {
        return Ok(to_value(-1));
    }

    let search_element = args[0].clone();
    let len: i32 = from_value(this.get_field_slice("length"))
        .expect("Expected array property \"length\" is not set.");

    let mut idx = match args.get(1) {
        Some(from_idx_ptr) => {
            let from_idx = from_value(from_idx_ptr.clone())
                .expect("Error parsing \"Array.prototype.indexOf - fromIndex\" argument");

            if from_idx >= 0 {
                min(from_idx, len - 1)
            } else {
                len + from_idx
            }
        }
        None => len - 1,
    };

    while idx >= 0 {
        let check_element = this.get_field_slice(&idx.to_string()).clone();

        if check_element == search_element {
            return Ok(to_value(idx));
        }

        idx -= 1;
    }

    Ok(to_value(-1))
}

/// Array.prototype.find ( callback, [thisArg] )
///
/// The find method executes the callback function once for each index of the array
/// until the callback returns a truthy value. If so, find immediately returns the value
/// of that element. Otherwise, find returns undefined.
/// <https://tc39.es/ecma262/#sec-array.prototype.find>
pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "missing callback when calling function Array.prototype.find".to_string(),
        ));
    }
    let callback = &args[0];
    let this_arg = if args.len() > 1 {
        args[1].clone()
    } else {
        Gc::new(ValueData::Undefined)
    };
    let len: i32 = from_value(this.get_field_slice("length")).unwrap();
    for i in 0..len {
        let element = this.get_field_slice(&i.to_string());
        let arguments = vec![element.clone(), to_value(i), this.clone()];
        let result = interpreter.call(callback, &this_arg, arguments)?;
        if result.is_true() {
            return Ok(element);
        }
    }
    Ok(Gc::new(ValueData::Undefined))
}

/// Array.prototype.findIndex ( predicate [ , thisArg ] )
///
/// This method executes the provided predicate function for each element of the array.
/// If the predicate function returns `true` for an element, this method returns the index of the element.
/// If all elements return `false`, the value `-1` is returned.
/// <https://tc39.es/ecma262/#sec-array.prototype.findindex/>
pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "Missing argument for Array.prototype.findIndex".to_string(),
        ));
    }

    let predicate_arg = args.get(0).expect("Could not get `predicate` argument.");

    let this_arg = args
        .get(1)
        .cloned()
        .unwrap_or_else(|| Gc::new(ValueData::Undefined));

    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not get `length` property.");

    for i in 0..length {
        let element = this.get_field_slice(&i.to_string());
        let arguments = vec![element.clone(), to_value(i), this.clone()];

        let result = interpreter.call(predicate_arg, &this_arg, arguments)?;

        if result.is_true() {
            return Ok(Gc::new(ValueData::Number(f64::from(i))));
        }
    }

    Ok(Gc::new(ValueData::Number(f64::from(-1))))
}

/// Array.prototype.fill ( value[, start[, end]] )
///
/// The method fills (modifies) all the elements of an array from start index (default 0)
/// to an end index (default array length) with a static value. It returns the modified array
/// <https://tc39.es/ecma262/#sec-array.prototype.fill>
pub fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    let len: i32 = from_value(this.get_field_slice("length")).expect("Could not get argument");
    let default_value = undefined();
    let value = args.get(0).unwrap_or(&default_value);
    let relative_start = args.get(1).unwrap_or(&default_value).to_num() as i32;
    let relative_end_val = args.get(2).unwrap_or(&default_value);
    let relative_end = if relative_end_val.is_undefined() {
        len
    } else {
        relative_end_val.to_num() as i32
    };
    let start = if relative_start < 0 {
        max(len + relative_start, 0)
    } else {
        min(relative_start, len)
    };
    let fin = if relative_end < 0 {
        max(len + relative_end, 0)
    } else {
        min(relative_end, len)
    };

    for i in start..fin {
        this.set_field_slice(&i.to_string(), value.clone());
    }

    Ok(this.clone())
}

pub fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
    let search_element = args
        .get(0)
        .cloned()
        .unwrap_or_else(|| Gc::new(ValueData::Undefined));

    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not get `length` property.");

    for idx in 0..length {
        let check_element = this.get_field_slice(&idx.to_string()).clone();

        if check_element == search_element {
            return Ok(to_value(true));
        }
    }

    Ok(to_value(false))
}

/// Array.prototype.slice ( [begin[, end]] )
///
/// The slice method takes two arguments, start and end, and returns an array containing the
/// elements of the array from element start up to, but not including, element end (or through the
/// end of the array if end is undefined). If start is negative, it is treated as length + start
/// where length is the length of the array. If end is negative, it is treated as length + end where
/// length is the length of the array.
/// <https://tc39.es/ecma262/#sec-array.prototype.slice>
pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    let new_array = new_array(interpreter)?;
    let len: i32 =
        from_value(this.get_field_slice("length")).expect("Could not convert argument to i32");

    let start = match args.get(0) {
        Some(v) => from_value(v.clone()).expect("failed to parse argument for Array method"),
        None => 0,
    };
    let end = match args.get(1) {
        Some(v) => from_value(v.clone()).expect("failed to parse argument for Array method"),
        None => len,
    };

    let from = if start < 0 {
        max(len.wrapping_add(start), 0)
    } else {
        min(start, len)
    };
    let to = if end < 0 {
        max(len.wrapping_add(end), 0)
    } else {
        min(end, len)
    };

    let span = max(to.wrapping_sub(from), 0);
    let mut new_array_len: i32 = 0;
    for i in from..from.wrapping_add(span) {
        new_array.set_field_slice(
            &new_array_len.to_string(),
            this.get_field_slice(&i.to_string()),
        );
        new_array_len = new_array_len.wrapping_add(1);
    }
    new_array.set_field_slice("length", to_value(new_array_len));
    Ok(new_array)
}

/// Array.prototype.filter ( callback, [ thisArg ] )
///
/// For each element in the array the callback function is called, and a new
/// array is constructed for every value whose callback returned a truthy value
/// <https://tc39.es/ecma262/#sec-array.prototype.filter>
pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "missing argument 0 when calling function Array.prototype.filter",
        ));
    }

    let callback = args.get(0).cloned().unwrap_or_else(undefined);
    let this_val = args.get(1).cloned().unwrap_or_else(undefined);

    let length: i32 =
        from_value(this.get_field_slice("length")).expect("Could not get `length` property.");

    let new = new_array(&interpreter)?;

    let values = (0..length)
        .filter_map(|idx| {
            let element = this.get_field_slice(&idx.to_string());

            let args = vec![element.clone(), to_value(idx), new.clone()];

            let callback_result = interpreter
                .call(&callback, &this_val, args)
                .unwrap_or_else(|_| undefined());

            if callback_result.is_true() {
                Some(element)
            } else {
                None
            }
        })
        .collect::<Vec<Value>>();

    construct_array(&new, &values)
}

/// Array.prototype.some ( callbackfn [ , thisArg ] )
///
/// The some method tests whether at least one element in the array passes
/// the test implemented by the provided callback function. It returns a Boolean value,
/// true if the callback function returns a truthy value for at least one element
/// in the array. Otherwise, false.
///
/// Caution: Calling this method on an empty array returns false for any condition!
/// <https://tc39.es/ecma262/#sec-array.prototype.some/>
pub fn some(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
    if args.is_empty() {
        return Err(to_value(
            "missing callback when calling function Array.prototype.some".to_string(),
        ));
    }
    let callback = &args[0];
    let this_arg = if args.len() > 1 {
        args[1].clone()
    } else {
        Gc::new(ValueData::Undefined)
    };
    let mut i = 0;
    let max_len: i32 = from_value(this.get_field_slice("length")).unwrap();
    let mut len = max_len;
    while i < len {
        let element = this.get_field_slice(&i.to_string());
        let arguments = vec![element.clone(), to_value(i), this.clone()];
        let result = interpreter.call(callback, &this_arg, arguments)?.is_true();
        if result {
            return Ok(to_value(true));
        }
        // the length of the array must be updated because the callback can mutate it.
        len = min(max_len, from_value(this.get_field_slice("length")).unwrap());
        i += 1;
    }
    Ok(to_value(false))
}

/// Create a new `Array` object
pub fn create_constructor(global: &Value) -> Value {
    // Create Constructor
    let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE);
    let mut array_constructor = Object::create(object_prototype);
    array_constructor.kind = ObjectKind::Function;
    array_constructor.set_internal_method("construct", make_array);
    // Todo: add call function
    array_constructor.set_internal_method("call", make_array);

    // Create prototype
    let array_prototype = ValueData::new_obj(None);
    let length = Property::default().value(to_value(0_i32));
    array_prototype.set_prop_slice("length", length);

    make_builtin_fn!(concat, named "concat", with length 1, of array_prototype);
    make_builtin_fn!(push, named "push", with length 1, of array_prototype);
    make_builtin_fn!(index_of, named "indexOf", with length 1, of array_prototype);
    make_builtin_fn!(last_index_of, named "lastIndexOf", with length 1, of array_prototype);
    make_builtin_fn!(includes_value, named "includes", with length 1, of array_prototype);
    make_builtin_fn!(map, named "map", with length 1, of array_prototype);
    make_builtin_fn!(fill, named "fill", with length 1, of array_prototype);
    make_builtin_fn!(for_each, named "forEach", with length 1, of array_prototype);
    make_builtin_fn!(filter, named "filter", with length 1, of array_prototype);
    make_builtin_fn!(pop, named "pop", of array_prototype);
    make_builtin_fn!(join, named "join", with length 1, of array_prototype);
    make_builtin_fn!(to_string, named "toString", of array_prototype);
    make_builtin_fn!(reverse, named "reverse", of array_prototype);
    make_builtin_fn!(shift, named "shift", of array_prototype);
    make_builtin_fn!(unshift, named "unshift", with length 1, of array_prototype);
    make_builtin_fn!(every, named "every", with length 1, of array_prototype);
    make_builtin_fn!(find, named "find", with length 1, of array_prototype);
    make_builtin_fn!(find_index, named "findIndex", with length 1, of array_prototype);
    make_builtin_fn!(slice, named "slice", with length 2, of array_prototype);
    make_builtin_fn!(some, named "some", with length 2, of array_prototype);

    let array = to_value(array_constructor);
    make_builtin_fn!(is_array, named "isArray", with length 1, of array);
    array.set_field_slice(PROTOTYPE, to_value(array_prototype.clone()));

    array_prototype.set_field_slice("constructor", array.clone());
    array
}