import java.math.BigInteger;
import java.util.Arrays;

// page: 178

public class Main {
  public static void main(String[] args) {
    task38();
  }

  static void task38() {
    Task38 task = new Task38();
    System.out.println("value of s: " + task.s);
  }

  static class Task38 {
    String s;

    {
      s = "hello";
      System.out.println("instance initializer complete");
    }

    Task38() {
      System.out.println("constructor complete");
    }
  }

  static void task37() {
    /*
      Создайте класс с полем static String, инициализируемым в точке определения, и другим полем, инициализируемым в блоке static. Добавьте статический метод, который выводит значения полей и демонстрирует, что оба поля инициализируются перед использованием.
    */

    Task37.print();
  }

  static class Task37 {
    static String s1 = "hello";
    static String s2;

    static {
      s2 = "world";
      System.out.println("s2 initiated inside 'static' block");
    }

    static void print() {
      System.out.println("s1: " + s1);
      System.out.println("s2: " + s2);
    }
  }

  static void task36() {
    /*
      Включите в класс с именем Tank (емкость), который можно наполнить и опустошить. Условие «готовности» требует, чтобы он был пуст перед очисткой. Напишите метод finalize(), проверяющий это условие. В методе main() протестируйте возможные случаи использования вашего класса.
    */
    Tank tank = new Tank(true);
    tank.fillOut();

    new Tank(true);
    System.gc();
  }

  static class Tank {
    boolean full = false;

    Tank(boolean fill) {
      full = fill;
    }

    void fillOut() {
      full = false;
    }

    public void finalize() {
      if (full)
        System.out.println("Ошибка: full");
    }
  }


  static void task35() {
    /*
      Измените предыдущее упражнение так, чтобы метод finalize() обязательно был исполнен.
    */

    Task34 task = new Task34();
    System.gc();
  }

  static void task34() {
    /*
      Создайте класс с методом fin alize(), который выводит сообщение. В методе main() создайте объект вашего класса. Объясните поведение программы.
    */
    Task34 task = new Task34();
  }

  static class Task34 {
    Task34() {
      System.out.println("Task34 obj created");
    }

    public void finalize() {
      System.out.println("memory cleared. inside finalize");
    }
  }

  static class Task33 {
    Task33(int x, String s) {
      System.out.println("this is a string constructor: '" + s + "'");
    }

    Task33(int x) {
      this(x, "default");
      System.out.println("this is an integer constructor: '" + x + "'");
    }
  }

  static void task33() {
    /*
      Подготовьте класс с двумя (перегруженными) конструкторами. Используя ключевое слово this, вызовите второй конструктор из первого.
    */

    Task33 task1 = new Task33(1);
    Task33 task2 = new Task33(2, "hello");
  }

  static class Task32 {
    void method1() {
      method2("without 'this' kw");
      this.method2("with 'this' kw");
    }
    void method2(String s) {
      System.out.println("from method 2: " + s);
    }
  }

  static void task32() {
    /*
      Создайте класс с двумя методами. В первом методе дважды вызовите второй метод: один раз без ключевого слова this, а во второй с this - просто для того, чтобы убедиться в работоспособности этого синтаксиса; не используйте этот способ вызова на практике.
    */
    Task32 task = new Task32();
    task.method1();
  }

  static void task31() {
    /*
      Создайте класс без конструктора. Создайте объект этого класса в методе main(), чтобы удостовериться, что конструктор по умолчанию синтезируется автоматически.
    */
    Task31 task = new Task31();
  }

  static class Task31 {}

  static void task30() {
    /*
      Измените предыдущее упражнение так, чтобы два перегруженных метода принимали два аргумента (разных типов) и отличались только порядком их следования в списке аргументов. Проверьте, работает ли это.
    */
    Dog dog = new Dog();
    dog.bark("hello", 1);
    dog.bark(2, "world");
  }

  static class Dog {
    void bark() {
      System.out.println("собака лает");
    }

    void bark(int times) {
      System.out.println("cобака лает " + times + " раз");
    }

    void bark(double volume) {
      System.out.println("cобака воет с громкостью " + volume);
    }

    void bark(char mood) {
      System.out.println("собака поскуливает");
    }

    void bark(boolean angry) {
      System.out.println("собака рычит");
    }

    void bark(String s, int x) {
      System.out.println("string '" + s + "' first, number '" + x + "' next");
    }

    void bark(int x, String s) {
      System.out.println("number '" + x + "' first, string '" + s + "' next");
    }
  }

  static void task29() {
    /*
      Создайте класс Dog (собака) с перегруженным методом bark() (лай). Метод должен быть перегружен для разных примитивных типов данных с целью вывода сообщения о лае, завывании, поскуливании и т. п. в зависимости от версии перегруженного метода. Напишите метод main(), вызывающий все версии.
    */
    Dog dog = new Dog();

    dog.bark();
    dog.bark(3);
    dog.bark(2.5);
    dog.bark('s');
    dog.bark(true);
    
  }

  static class Task28 {

    Task28() {
      System.out.println("task28 object was created");
    }

    Task28(String s) {
      System.out.println(s + " world");
    }
  }

  static void task28() {
    /*
      Добавьте к классу из упражнения 3 перегруженный конструктор, принимающий в качестве параметра строку (String) и распечатывающий ее вместе с сообщением.
    */
    Task28 task1 = new Task28();
    Task28 task2 = new Task28("hello");
  }

  static class Task27 {
    Task27() {
      System.out.println("Task27 object was created successfully!");
    }
  }

  static void task27() {
    /*
      Создайте класс с конструктором по умолчанию (без параметров), который выводит на экран сообщение. Создайте объект этого класса.
    */
    Task27 task = new Task27();
  }

  static class Task26 {
    String s1 = "hello";
    String s2;

    Task26 (String s2) {
      this.s2 = s2;
    }
  }


  static void task26() {
    /*
      Создайте класс с полем String, инициализируемым в точке определения, и другим полем, инициализируемым конструктором. Чем отличаются эти два подхода?
    */
    
    Task26 task = new Task26("world");
    System.out.println(task.s1);
    System.out.println(task.s2);

  }

  static class Task25 {
    String string;
  }


  static void task25() {
    /*
      Создайте класс с неинициализированной ссылкой на String. Покажите, что Java инициализирует ссылку значением null.
    */
    Task25 task = new Task25();
    System.out.println(task.string);
  } 

  static void task24(String[] args) {
    /*
      Вампирами называются числа, состоящие из четного количества цифр и полученные перемножением пары чисел, каждое из которых содержит половину цифр результата. Цифры берутся из исходного числа в произвольном порядке, завершающие нули недопустимы. Примеры:
    1) 1260 = 21 * 60;
    2) 1827 = 21 * 87;
    3) 2187 = 27 * 81.
  Напишите программу, которая находит всех «вампиров», состоящих из 4 цифр. (Задача предложена Дэном Форханом.)
    */

    for (int x = 10; x < 100; x++) {
      for (int y = 10; y < 100; y++) {
        if ((x < y) && (Integer.toString(x * y).length() == 4)) {
          char[] claws_array = (Integer.toString(x) + Integer.toString(y)).toCharArray();
          Arrays.sort(claws_array);
          String claws = new String(claws_array);

          char[] vampire_array = Integer.toString(x * y).toCharArray();
          Arrays.sort(vampire_array);
          String vampire = new String(vampire_array);

          if (claws.equals(vampire)) {
            System.out.println(x + " * " + y + " = " + x * y);
          }
        }
      }
    }
  }

  static void task23(String[] args) {
    /*
      (4) Числами Фибоначчи называется числовая последовательность 1, 1, 2, 3, 5, 8, 13, 21, 34 и т. д., в которой каждое число, начиная с третьего, является суммой двух предыдущих. Напишите метод, который получает целочисленный аргумент и выводит указанное количество чисел Фибоначчи. Например, при запуске командой java Fibonacci 5 (где Fibonacci — имя класса) должна выводиться последовательность 1, 1, 2, 3, 5.
    */

    int n = Integer.parseInt(args[0]);
    // System.out.println(n);

    BigInteger A = BigInteger.valueOf(1), B = BigInteger.valueOf(1), C;

    if (n == 0) {
      System.out.println("");
    } else if (n == 1) {
      System.out.println("1");
    } else if (n == 2) {
      System.out.println("1 1");
    } else {
      System.out.print("1 1 ");
      for (int i = 0; i < n - 2; i++) {
        C = A.add(B);
        A = B;
        B = C;
        System.out.print(B.toString() + " ");
      }
    }

  }

  static void task22() {
    /*
      Создайте команду switch, которая выводит сообщение в каждой секции case. Разместите ее в цикле for, проверяющем все допустимые значения case. Каждая секция case должна завершаться командой break. Затем удалите команды break и посмотрите, что произойдет.
    */

    for (int i = 1; i <= 5; i++) {
      switch (i) {
        case 1:
        case 2:

          System.out.println("<3");
          break;

        case 4:
        case 5:

          System.out.println("no love :(");
          break;

        default:
          System.out.println(i);
          break;
      }
    }
  }

  static int test(int testval, int begin, int end) {
    /*
      1 - testval принадлежит [begin, end]
      0 - иначе
    */
    if (begin <= testval && testval <= end) {
      return 1;
    }

    return 0;
  }

  static void task20() {
    /*
      Измените метод test() так, чтобы он получал два дополнительных аргумента begin и end, а значение testval проверялось на принадлежность к диапазону [begin, end] (с включением границ).
    */

    System.out.println(
        test(2, 1, 5) // 2 in [1, 5] (1)
    );

    System.out.println(
        test(1, 1, 5) // 1 in [1, 5] (1)
    );

    System.out.println(
        test(-1, 1, 5) // -1 not in [1, 5] (0)
    );

  }

  static void task18() {
    /*
      Напишите программу, использующую два вложенных цикла for и оператор остатка (%) для поиска и вывода простых чисел (то есть целых чисел, не делящихся нацело ни на какое другое число, кроме себя и 1).
    */

    
    for (int i = 2; i < 100000; i++) {
      boolean prime = true;
      for (int j = 2; j < i; j++) {
        if (i % j == 0) {
          // System.out.println("число " + i + " не простое");
          prime = false;
          break;
        }
      }

      if (prime) {
        System.out.println("число " + i + " простое");
      }
    }

  }

  static void task17() {
    /*
      Измените упражнение 2 так, чтобы код выполнялся в «бесконечном» цикле while. Программа должна работать до тех пор, пока ее выполнение не будет прервано с клавиатуры (как правило, нажатием клавиш Ctrl+C).
    */

    while (true) {
      task16();
    }
  }

  static void task16() {
    /*
      Напишите программу, которая генерирует 25 случайных значений типа int. Для каждого значения команда if - e ls e сообщает, в каком отношении оно находится с другим случайно сгенерированным числом (больше, меньше, равно).
    */
    for (int i = 0; i < 25; i++) {
      int num1 = (int)(Math.random() * 100);
      int num2 = (int)(Math.random() * 100);

      // System.out.println("num1: " + num1);
      // System.out.println("num2: " + num2);

      if (num1 > num2) {
        System.out.println(num1 + " > " + num2);
      } else if (num1 < num2) {
        System.out.println(num1 + " < " + num2);
      } else {
        System.out.println(num1 + " = " + num2);
      }


    }
  }

  static void task15() {
    /*
      Напишите программу, которая выводит числа от 1 до 100.
    */
    for (int i = 1; i <= 100; i++) {
      System.out.println(i);
    }
  }

  static void allStringOps(String s1, String s2) {
    System.out.println("s1 < s2: " + (s1.compareTo(s2) < 0));
    System.out.println("s1 > s2: " + (s1.compareTo(s2) > 0));
    System.out.println("s1 <= s2: " + (s1.compareTo(s2) <= 0));
    System.out.println("s1 >= s2: " + (s1.compareTo(s2) >= 0));

    System.out.println("s1 == s2: " + (s1 == s2));
    System.out.println("s1 != s2: " + (s1 != s2));

    System.out.println("s1.equals(s2): " + s1.equals(s2));
    System.out.println("s2.equals(s1): " + s2.equals(s1));
    System.out.println();
  }


  static void task14() {
    /*
      Напишите метод, который получает два аргумента String, выполняет с ними все операции логических сравнений и выводит результаты. Для операций == и != также выполните проверку equals(). Вызовите свой метод из main() для нескольких разных объектов String.
    */

    allStringOps("hello", "world");
    allStringOps("hello", "hello");
    allStringOps(new String("hello"), new String("hello"));
  }

  static void charToBin(char ch) {
    String bin = Integer.toBinaryString(ch);

    System.out.println(
        String.format("%16s", bin).replace(' ', '0')
    );
  }

  static void task13() {
    /*
      Напишите метод для вывода char в двоичном представлении. Продемонстрируйте его работу на нескольких разных символах.
    */

    char a = 'a', b = 'b', x = 'x', z = 'z';
    charToBin(a);
    charToBin(b);
    charToBin(x);
    charToBin(z);
  }

  static void task12() {
    /*
      Начните с числа, состоящего из двоичных единиц. Сдвиньте его влево, а затем используйте беззнаковый оператор сдвига вправо по всем двоичным позициям, с выводом всех промежуточных результатов методом lnteger.toBinaryString().
    */

    int value = 0xFFFFFFFF;
    System.out.println(Integer.toBinaryString(value));

    value <<= 1;
    System.out.println(Integer.toBinaryString(value));

    for (int i = 0; i < 32; i++) {
      value >>>= 1;
      System.out.println(Integer.toBinaryString(value));
    }
  }

  static void task11() {
    /*
      Начните с числа, содержащего двоичную 1 в старшем бите (подсказка: воспользуйтесь шестнадцатеричной константой). Используйте знаковый оператор сдвига вправо, сдвигайте знак до крайней правой позиции, с выводом всех промежуточных результатов методом Integer.toBinaryString().
    */ 
    int value = 0x80000000;

    for (int i = 0; i < 32; i++) {
        System.out.println(Integer.toBinaryString(value));
        value >>= 1;
    }
  }

  static void task10() {
    /*
      Напишите программу с двумя константами: обе константы состоят из чередующихся нулей и единиц, но у одной нулю равен младший бит, а у другой старший (подсказка: константы проще всего определить в шестнадцатеричном виде). Объедините эти две константы всеми возможными поразрядными операторами. Для вывода результатов используйте метод Integer.toBinaryString().
    */

    int const1 = 0xAAAAAAAA;
    int const2 = 0x55555555;

    int bitwise_and = const1 & const2;
    int bitwise_or = const1 | const2;
    int bitwise_xor = const1 ^ const2;
    int bitwise_not = ~const1;

    System.out.println("AND(&): " + Integer.toBinaryString(bitwise_and));
    System.out.println("OR(|): " + Integer.toBinaryString(bitwise_or));
    System.out.println("XOR(^): " +  Integer.toBinaryString(bitwise_xor));
    System.out.println("NOT(~):" + Integer.toBinaryString(bitwise_not));
  }

  static void task9() {
    /* 
      Выведите наибольшее и наименьшее число в экспоненциальной записи для типов float и double.
    */ 

    float max_float = Float.MAX_VALUE;
    double max_double = Double.MAX_VALUE;

    float min_float = Float.MIN_VALUE;
    double min_double = Double.MIN_VALUE;

    System.out.println("Max float value: " + max_float);
    System.out.println("Max double value: " + max_double);

    System.out.println("Min float value: " + min_float);
    System.out.println("Min double value: " + min_double);
  }

  static void task8() {
    /*
      Покажите, что шестнадцатеричная и восьмеричная записи могут использоваться с типом long. Для вывода результатов используйте метод Long.toBinaryString(). 
    */

    long hex = 0x123L;
    long oct = 0123;

    System.out.println(Long.toBinaryString(hex));
    System.out.println(Long.toBinaryString(oct));
  }
}
