import JSONArray from "../instances/JSONArray";

export default class JSONObject {
  innerMap: { [key: string]: any };

  constructor(value?: any) {
    let map: { [key: string]: any };
    if (value && typeof value === "string") {
      map = JSON.parse(value);
    } else if (value && typeof value === "object" && !Array.isArray(value)) {
      map = {};
      const keySet = value instanceof JSONObject ? value.keySet() : Object.keys(value);
      for (const key of keySet) {
        const item = value instanceof JSONObject ? value.get(key) : value[key];
        if (Array.isArray(item)) {
          map[key] = new JSONArray(item);
        } else if (JSONObject.isPlainObject(item)) {
          map[key] = new JSONObject(item);
        } else {
          map[key] = item;
        }
      }
    } else {
      map = {};
    }
    this.innerMap = map;
  }

  public static isPlainObject(value: any): boolean {
    if (!value || typeof value !== "object") return false;
    if (value instanceof JSONObject || value instanceof JSONArray) {
      return false
    }
    const proto = Object.getPrototypeOf(value);
    return proto === Object.prototype || proto === null;
  }

  public put(key: string, value: any) {
    if (Array.isArray(value)) {
      this.innerMap[key] = new JSONArray(value);
    } else if (JSONObject.isPlainObject(value)) {
      this.innerMap[key] = new JSONObject(value);
    } else {
      this.innerMap[key] = value;
    }
  }

  public get(key: string): any {
    return this.innerMap[key];
  }

  public opt(key: string): any {
    return key == null ? null : this.innerMap[key];
  }

  public optString(key: string, defaultValue: string): string {
    const result = this.opt(key);
    return result ? result : defaultValue;
  }

  public getBoolean(key: string): boolean {
    const object = this.get(key);
    if (typeof object === "boolean") {
      return object as boolean;
    } else {
      return Boolean(object);
    }
  }

  public optBoolean(key: string, defaultValue: boolean): boolean {
    const val = this.opt(key);
    if (!val) {
      return defaultValue;
    }
    return this.getBoolean(key);
  }

  public getInt(key: string): number {
    return this.innerMap[key] as number;
  }

  public getJSONObject(key: string): JSONObject {
    return this.innerMap[key] as JSONObject;
  }

  public getJSONArray(key: string): JSONArray {
    return this.innerMap[key] as JSONArray;
  }

  public has(key: string): boolean {
    return this.innerMap.hasOwnProperty(key);
  }

  public keySet(): string[] {
    return Object.keys(this.innerMap);
  }

  public toString(): string {
    const replacer = (key: string, value: any): any => {
      if (value instanceof JSONObject) {
        return value.innerMap;
      } else if (value instanceof JSONArray) {
        return value.innerList;
      }
      return value;
    };
    return JSON.stringify(this.innerMap, replacer);
  }
}
