/*
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package codetoanalyze.java.eradicate;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Cleanup;
import com.facebook.infer.annotation.Initializer;
import javax.annotation.Nullable;

abstract class A {
  final String fld;

  A(String s) {
    this.fld = s;
  }
}

// for butterknife
@interface InjectView {}

class FragmentExample extends Fragment {

  View view;

  @Override
  public void onDestroyView() {
    view = null;
  }
}

public class FieldNotNullable extends A {
  @Nullable String x;
  String y;
  String fld; // Shadow the field defined in A
  String static_s = null; // Static initializer error

  FieldNotNullable(String s) {
    super(s);
    x = null;
    y = s;
    this.fld = s;
  }

  void setXNull() {
    x = null;
  }

  void setXNullable(@Nullable String s) {
    x = s;
  }

  void setYNull() {
    y = null;
  }

  void setYNullable(@Nullable String s) {
    y = s;
  }

  FieldNotNullable(Integer n) {
    super("");
    this.fld = "";
    y = x == null ? "abc" : "def";
  }
}

class MixedInitializers extends Activity {

  private String field1 = "1";
  private String field2;
  private String field3;

  MixedInitializers() {
    field2 = "2";
  }

  protected void onCreate(Bundle bundle) {
    field3 = "3";
  }
}

class TestInitializerBuilder {
  String required1;
  String required2;
  @Nullable String optional;

  // No FIELD_NOT_INITIALIZED error should be reported, because of the @Initializer annotations.
  TestInitializerBuilder() {}

  // This is an initializer and must always be called before build().
  @Initializer
  TestInitializerBuilder setRequired1(String required1) {
    this.required1 = required1;
    return this;
  }

  // This is an initializer and must always be called before build().
  @Initializer
  TestInitializerBuilder setRequired2(String required2) {
    this.required2 = required2;
    return this;
  }

  TestInitializerBuilder setOptional(String optional) {
    this.optional = optional;
    return this;
  }

  TestInitializer build() {
    // Fail hard if the required fields are not initialzed
    Assertions.assertCondition(required1 != null && required2 != null);

    return new TestInitializer(this);
  }

  @Cleanup
  void testCleanup() {
    this.required1 = null;
    this.required2 = null;
    this.optional = null;
  }
}

class TestInitializer {
  String required1; // should always be set
  String required2; // should always be set
  @Nullable String optional; // optionally set

  TestInitializer(TestInitializerBuilder b) {
    required1 = b.required1;
    required2 = b.required2;
    optional = b.optional;
  }

  static void testInitializerClientA() {
    TestInitializerBuilder b = new TestInitializerBuilder();
    b.setRequired1("hello");
    b.setRequired2("world");
    TestInitializer x = b.build();
  }

  static void testInitializerClientB() {
    TestInitializerBuilder b = new TestInitializerBuilder();
    b.setRequired1("a");
    b.setRequired2("b");
    b.setOptional("c");
    TestInitializer x = b.build();
  }
}

class NestedFieldAccess {

  class C {
    @Nullable String s;
  }

  class CC {
    @Nullable C c;
  }

  class CCC {
    @Nullable CC cc;
  }

  public class Test {
    @Nullable String s;
    C myc;

    Test() {
      myc = new C();
    }

    void test() {
      if (s != null) {
        int n = s.length();
      }
    }

    void test1() {
      if (myc.s != null) {
        int n = myc.s.length();
      }
    }

    void test2(C c) {
      if (c.s != null) {
        int n = c.s.length();
      }
    }

    void test2_local() {
      C c = new C();
      if (c.s != null) {
        int n = c.s.length();
      }
    }

    void test3(CC cc) {
      if (cc.c != null && cc.c.s != null) {
        int n = cc.c.s.length();
      }
    }

    void test4(CCC ccc) {
      if (ccc.cc != null && ccc.cc.c != null && ccc.cc.c.s != null) {
        int n = ccc.cc.c.s.length();
      }
    }

    void test5(@Nullable CCC ccc) {
      if (ccc == null || ccc.cc == null || ccc.cc.c == null || ccc.cc.c.s == null) {
      } else {
        int n = ccc.cc.c.s.length();
      }
    }
  }

  class TestFunctionsIdempotent {
    @Nullable String s;
    String dontAssignNull;

    TestFunctionsIdempotent() {
      dontAssignNull = "";
    }

    @Nullable
    String getS(int n) {
      return s;
    }

    TestFunctionsIdempotent getSelf() {
      return this;
    }

    void FlatOK1(TestFunctionsIdempotent x) {
      if (getS(3) != null) {
        dontAssignNull = getS(3);
      }
    }

    void FlatOK2(TestFunctionsIdempotent x) {
      if (x.getS(3) != null) {
        dontAssignNull = x.getS(3);
      }
    }

    void FlatBAD1(TestFunctionsIdempotent x) {
      if (x.getS(3) != null) {
        dontAssignNull = getS(3);
      }
    }

    void FlatBAD2(TestFunctionsIdempotent x) {
      if (x.getS(3) != null) {
        dontAssignNull = x.getS(4);
      }
    }

    void NestedOK1() {
      if (getSelf().getS(3) != null) {
        dontAssignNull = getSelf().getS(3);
      }
    }

    void NestedOK2() {
      if (getSelf().getSelf().getS(3) != null) {
        dontAssignNull = getSelf().getSelf().getS(3);
      }
    }

    void NestedBAD1() {
      if (getSelf().getS(3) != null) {
        dontAssignNull = getSelf().getS(4);
      }
    }

    void NestedBAD2() {
      if (getS(3) != null) {
        dontAssignNull = getSelf().getS(3);
      }
    }

    void NestedBAD3() {
      if (getSelf().getSelf().getS(3) != null) {
        dontAssignNull = getSelf().getS(3);
      }
    }
  }

  class TestContainsKey {
    void testMapContainsKey(java.util.Map<Integer, String> m) {
      if (m.containsKey(3)) {
        m.get(3).isEmpty();
      }
    }

    void testMapContainsKeyInsideWhileLoop(java.util.Map<Integer, String> m) {
      while (true) {
        if (m.containsKey(3)) {
          m.get(3).isEmpty();
        }
      }
    }

    void testImmutableMapContainsKey(com.google.common.collect.ImmutableMap<Integer, String> m) {
      if (m.containsKey(3)) {
        m.get(3).isEmpty();
      }
    }
  }

  // Test Map.put()
  class TestPut {
    String dontAssignNull = "";

    public void putConstString(java.util.Map<String, String> map, String key) {
      map.put(key, "abc");
      dontAssignNull = map.get(key);
    }

    public void putNull(java.util.Map<String, String> map, String key) {
      map.put(key, "abc");
      map.put(key, null);
      dontAssignNull = map.get(key);
    }

    public void putWithContainsKey(java.util.Map<String, String> map, String key) {
      if (!map.containsKey(key)) {
        map.put(key, "abc");
      }
      dontAssignNull = map.get(key);
    }
  }

  // support assignments of null to @InjectView fields, generated by butterknife
  class SupportButterKnife {
    @InjectView String s;

    SupportButterKnife() {}

    void dereferencingIsOK() {
      int n = s.length();
    }

    void assignNullIsOK() {
      s = null;
    }
  }

  void methodWithNullableCapturedParameterBad_FN(@Nullable Object parameter) {
    Object object =
        new Object() {
          void foo() {
            parameter.toString();
          }
        };
  }
}
