// Print to the console like this. It works both in the browser and node.
console.log("Hi, class!");


// Most expressions work exactly as in Java. There are some gotchas though!
console.log(2 * 3 + 1);


// Declarations do not have types. Use "let" to declare a regular variable.
let x = 2 * 3 + 1;
console.log(x);
x = "foo";           // a different type
console.log(x);


// VM has to do something when types are mixed in appropriately.
// In general, the VM tries to "do its best" to make it work.
console.log('20' - 15);   // *illegal* in Java, but prints 5 here!
console.log('abc' - 15);  // prints NaN because abc is not a number
console.log('20' + 15);   // prints 2015


// Variables start out with the value "undefined" if not given one.
let z;
console.log(z);  // prints undefined

// Use "const" to declare a variable that cannot change values. It must be
// assigned a value immediately.
const y = 2 * 3 + 1;
console.log(y);
//y = 2;  // will fail!


// Can retrieve the type of a variable using the "typeof" operator, which
// returns a string: undefined, null, number, string, bigint, 
console.log(typeof 1);          // prints "number"
console.log(typeof undefined);  // prints "undefined"
console.log(typeof false);      // prints "boolean"
console.log(typeof "foo");      // prints "string"


// For equality, use "===" not "==". The latter tries to make things of
// different types into the same type before comparing.
console.log("0" === 0);   // prints false
console.log("0" == 0);    // prints true!
console.log("" == 0);     // prints true!

// Like Java, "===" is value equality on primitive types and reference equality
// on reference types. Unlike Java, strings are primitive types.
console.log("foo" === ("f" + "oo"));  // prints true


// We mentioned gotchas above. The most important one is division!
console.log(7 / 3);  // watch out!

// Adding an "n" suffix makes the type "bigint", where division works properly.
console.log(typeof 7n);  // prints "bigint"
console.log(7n / 3n);    // prints 2n

// Explicitly converting to a string will print the way you want.
console.log(String(7n / 3n));

// Can also convert using Number, BigInt, etc.
console.log(3n === 3);          // prints false
console.log(Number(3n) === 3);  // prints true
console.log(3n === BigInt(3));  // prints true

// With number, you can do this by rounding down (but use bigint instead).
console.log(Math.floor(7 / 3));

// Most of Java's Math functions are provided as well.
console.log(Math.sqrt(9));
console.log(Math.sin(1));


// Strings are primitive types with built-in array-like indexing.
const s = "abc";
console.log(s[1]);  // prints "b"

// Strings can be compared alphabetically using "<":
console.log("apple" < "orange");  // prints true

// Characters are represented as length-1 strings.
console.log(typeof s[1]);  // prints "string"

// Also includes some Java methods, but it's bad style to use them.
console.log(s.charAt(1));  // prints "b"

// One exception is charCodeAt, which returns a number.
console.log(s.charCodeAt(1));  // prints 98 (UTF-8 encoding of '1')

// Literals can use either single or double quotes.
console.log("foo");
console.log('foo');

// Usually use the one that allows you to avoid escaping quotes.
console.log("Did you say \"there\" or \"their\"?");
console.log('Did you say "there" or "their"?');

// Template literals allow values to be substituted.
console.log(`The alphabet starts "${s}"...`);  // The alphabet starts "abc"...


// If statements must allow anything to be used as a condition (since there is
// no type checker to make sure it's a boolean). This can be useful.
let t;
// .. some code that might set x to a string ...
if (!t)
  console.log("t is not defined");

// Sometimes this works...
t = "a";
if (!t)
  console.log("t is not defined");

// But it's also a common source of bugs.
t = "";
if (!t)
  console.log("t is not defined");

// These things evaluate to false.
console.log(Boolean(""));   // prints false
console.log(Boolean(0));    // prints false
console.log(Boolean(NaN));  // prints false

// So use booleans only in conditions.
if (t !== undefined)
  console.log("t is not defined");


// Nice literal syntax for creating records with fields. Retrieving the value
// of a field is the same as in Java.
const r = {x: 1};
console.log(r.x);  // prints 1

// Can think of Math as a record with field "sqrt"...

// These are called "objects" although their CS name is "records".
console.log(typeof {x: 1});     // prints "object"

// Like Java, "===" is value equality on primitive types and reference equality
// on reference types (records, arrays, and instances of classes).
console.log({x: 1} === {x: 1});  // prints false

// Also possible to retrieve fields using [..] syntax, but it's uglier.
console.log(r["x"]);  // prints 1

// Can check for the presence of a field using "in", but there are gotchas.
// Usually, when you are doing this, you are making a mistake...
console.log("x" in r);         // prints true
console.log("y" in r);         // prints false
console.log("toString" in r);  // prints true

// When the fields of the record are not known ahead of time, use a Map.
const M = new Map();
M.set("a", 1);
M.set("b", 5);
console.log(M.get("a"));         // prints 1
console.log(M.get("b"));         // prints 5
console.log(M.get("toString"));  // prints undefined
M.set("a", 2);
M.set("c", 3);
console.log(M.get("a"));         // prints 2
console.log(M.get("c"));         // prints 3

// JavaScript also provides a set class.
const S = new Set();
S.add("a");
S.add("b");
console.log(S.has("a")); // prints true
console.log(S.has("c")); // prints false

S.add("c");
console.log(S.has("c")); // prints true


// Array elemnts are accessed like Java, but simpler to create. Note again that
// there are no type declarations, so elements can have any type.
const A = [1, 2, "foo"]; // no type restriction!
console.log(A[2]);       // prints “foo”

// Add and remove using push and pop
A.pop();
console.log(A);  // prints [1, 2]
A.push(3);
console.log(A);  // prints [1, 2, 3]

// Length field stores the length of the array.
console.log(A.length);  // prints 3
A.pop();
console.log(A.length);  // prints 2

// Arrays are a special kind of object.
console.log(typeof A);        // prints "object"

// Can distinguish the latter two using Array.isArray
console.log(Array.isArray(A));          // prints true
console.log(Array.isArray({x: 1}));     // prints false

// Map and Set constructors take array giving the initial value.
const T = new Set(["a", "b"]);
const N = new Map([["a", 1], ["b", 5]]);


// Functions are first-class objects, storable in a const. They can also be
// declared with the "function" keyword, but like "var", we will avoid that.
const add3 = (x, y, z) => {
  return x + y + z;
};
console.log(add3(1, 2, 3));  // prints 6

// No curly braces needed for a simple "return" statement.
const add2 = (x, y) => x + y;
console.log(add2(1, 2));  // prints 3


// Class syntax is similar to Java, but again, there are no types. The
// constructor is called "constructor", not the class name.
class Pair {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  // We will declare methods like this. (Another syntax exists but has problems)
  distTo = (p) => {
    const dx = this.x - p.x;
    const dy = this.y - p.y;
    return Math.sqrt(dx*dx + dy*dy);
  };
}

const p = new Pair(1, 2);
const q = new Pair(2, 2);
console.log(p.distTo(q));  // prints 1