// Store Class
// Переделайте задание Store на синтаксис ES6-классов:
// Добавьте нужные параметры в методы, их код, а так же геттер state, который работает аналогично
// getState. Проверьте на ларьке, ведь объект, созданный из этого класса будет таким же, как и
// объект, созданный createStore
store_class: {
  class Store {
    #reducer(state, { type, name, amount, money }) { //объект action деструктуризируется на три переменных
      if (!state) { //начальная уборка в ларьке:
        return {
          products: {
            пиво: {
              amount: 100,
              price: 30,
            },
            чипсы: {
              amount: 100,
              price: 25,
            },
            сиги: {
              amount: 100,
              price: 35,
            }
          },
          balance: {
            amount: 0
          }
        }
      }
      if (type === 'buy') { //если тип action - КУПИТЬ, то:
        if (amount > state.products[name].amount) {
          alert('You have entered more quantity than is available');
          return {
            ...state,
          }
        }
        if (money < amount * state.products[name].price) {
          alert('You don`t have enought money');
          return {
            ...state,
          }
        }
        if (money > amount * state.products[name].price) {
          alert('You gave more money');
          return {
            ...state,
          }
        }
        let updatedProducts = state.products
        let updatedField = updatedProducts[name];
        updatedField.amount -= amount;
        let updatedBalance = state.balance;
        updatedBalance.amount += +money;
        return {
          ...state, //берем все что было из ассортимента
          products: updatedProducts, //и уменьшаем то, что покупается на количество
          balance: updatedBalance,
        }

      }
      return state; //если мы не поняли, что от нас просят в `action` - оставляем все как есть
    }
    #state = this.#reducer(undefined, {});
    #cbs = [];

    constructor() {
    }

    get state() {
      return this.#state;
    }

    subscribe(cb) {
      this.#cbs.push(cb);
      return () => this.#cbs = this.#cbs.filter(c => c !== cb);
    }

    dispatch(action) {
      const newState = this.#reducer(this.#state, action); //пробуем запустить редьюсер
      if (newState !== this.#state) { //проверяем, смог ли редьюсер обработать action
        this.#state = newState; //если смог, то обновляем state 
        for (let cb of this.#cbs) cb(); //и запускаем подписчиков
      }
    }
  }
  const store = new Store;
  const unsubscribe = store.subscribe(() => console.log(store.state));

  setTimeout(unsubscribe, 10000); //отпишемся через 10 секунд, например

  const buyAction = (name, amount, money) => ({ type: 'buy', name, amount, money });
  store.dispatch(buyAction('пиво', 1, 30));
  store.dispatch(buyAction('чипсы', 4, 100));
}

// Password Class
// По аналогии, переделайте код задания Password в синтаксис классов ES6. Спрячьте все что можно в
// #приватные свойства объектов класса. Проверьте на форме логина - ведь она использует Password
password_class: {
  class Password {
    #passInputEl = document.createElement('input');
    #passVisibilityCheckboxEl = document.createElement('input');

    constructor(parent, open) {
      parent.append(this.#passInputEl);
      this.#passVisibilityCheckboxEl.type = 'checkbox';
      this.#passVisibilityCheckboxEl.checked = open;
      parent.append(this.#passVisibilityCheckboxEl);
      if (open) {
        this.#passInputEl.type = 'text';
      } else {
        this.#passInputEl.type = 'password';
      }
      this.#passVisibilityCheckboxEl.addEventListener('change', (event) => {
        if (event.currentTarget.checked) {
          this.#passInputEl.type = 'text';
        } else {
          this.#passInputEl.type = 'password';
        }
        if (this.onOpenChange) {
          this.onOpenChange(event.currentTarget.checked);
        }
      });
      this.#passInputEl.addEventListener('input', (event) => {
        this.onChange();
      });
    }
    set value(value) {
      this.#passInputEl.value = value;
    }
    get value() {
      return this.#passInputEl.value;
    }
    set open(value) {
      this.#passVisibilityCheckboxEl.checked = value;
    }
    get open() {
      return this.#passVisibilityCheckboxEl.checked;
    }
  }

  function Login(parent) {
    let loginInputEl = document.createElement('input');
    loginInputEl.type = 'text';
    parent.append(loginInputEl);
    loginInputEl.addEventListener('input', (event) => {
      this.onChange();
    });
    this.setValue = (value) => {
      loginInputEl.value = value;
    }
    this.getValue = () => {
      return loginInputEl.value;
    }
  }

  let form = document.createElement('form');
  document.body.append(form);
  let loginLabel = document.createElement('label');
  loginLabel.innerText = 'Login: ';
  form.append(loginLabel);
  let login = new Login(form);
  let passwordLabel = document.createElement('label');
  passwordLabel.innerText = 'Password: ';
  form.append(passwordLabel);
  let password = new Password(form, false);
  let submit = document.createElement('button');
  submit.innerText = 'Submit';
  form.append(submit);
  function validateForm() {
    if (login.getValue() == '' || password.value == '') {
      submit.disabled = true;
    } else {
      submit.disabled = false;
    }
  }
  validateForm();
  login.onChange = validateForm;
  password.onChange = validateForm;
}

// StoreThunk Class
// Унаследуйте класс Store в новом классе StoreThunk. Новый класс должен перекрывать метод dispatch, проверять
// тип переданного экшона и если это функция, запускать её, передав у неё this.dispatch и this.getState. Данное
// условие написано тут. Учтите, что в thunk передаются функции dispatch и getState без объекта до точечки, а
// эти методы в классе Store являются обычными функциями, склонными к потере this. Для прибития this намертво к
// функции используйте метод bind. Посмотреть можно тут и тут Проверьте на модульном проекте 
store_thunk_class: {
  class Store {
    #reducer(state, { type, name, amount, money }) { //объект action деструктуризируется на три переменных
      if (!state) { //начальная уборка в ларьке:
        return {
          products: {
            пиво: {
              amount: 100,
              price: 30,
            },
            чипсы: {
              amount: 100,
              price: 25,
            },
            сиги: {
              amount: 100,
              price: 35,
            }
          },
          balance: {
            amount: 0
          }
        }
      }
      if (type === 'buy') { //если тип action - КУПИТЬ, то:
        if (amount > state.products[name].amount) {
          alert('You have entered more quantity than is available');
          return {
            ...state,
          }
        }
        if (money < amount * state.products[name].price) {
          alert('You don`t have enought money');
          return {
            ...state,
          }
        }
        if (money > amount * state.products[name].price) {
          alert('You gave more money');
          return {
            ...state,
          }
        }
        let updatedProducts = state.products
        let updatedField = updatedProducts[name];
        updatedField.amount -= amount;
        let updatedBalance = state.balance;
        updatedBalance.amount += +money;
        return {
          ...state, //берем все что было из ассортимента
          products: updatedProducts, //и уменьшаем то, что покупается на количество
          balance: updatedBalance,
        }

      }
      return state; //если мы не поняли, что от нас просят в `action` - оставляем все как есть
    }
    #state = this.#reducer(undefined, {});
    #cbs = [];

    constructor() {
    }

    get state() {
      return this.#state;
    }

    subscribe(cb) {
      this.#cbs.push(cb);
      return () => this.#cbs = this.#cbs.filter(c => c !== cb);
    }

    dispatch(action) {
      const newState = this.#reducer(this.#state, action); //пробуем запустить редьюсер
      if (newState !== this.#state) { //проверяем, смог ли редьюсер обработать action
        this.#state = newState; //если смог, то обновляем state 
        for (let cb of this.#cbs) cb(); //и запускаем подписчиков
      }
    }
  }

  class StoreThunk extends Store {
    constructor(...params) {
      super(...params);
      console.log(this)
    }

    dispatch(action) {
      if (typeof action === 'function') { //если action - не объект, а функция
        return action(this.dispatch.bind(this), this.state) //запускаем эту функцию и даем ей dispatch и getState для работы
      }
      super.dispatch(action);
    }
  }
  const storeTh = new StoreThunk;
  const unsubscribe = storeTh.subscribe(() => console.log(storeTh.state));

  setTimeout(unsubscribe, 10000);

  const buyAction = (name, amount, money) => ({ type: 'buy', name, amount, money });
  const actionЗатарится = () =>
    dispatch => {
      dispatch(buyAction('пиво', 1, 30));
      dispatch(buyAction('чипсы', 5, 125));
    }
  storeTh.dispatch(actionЗатарится());
}

// RGB Class
// Напишите класс RGB, приватными свойствами которого являются три числа #r, #g, #b. Класс должен обладать
// следующими геттерами и сеттерами:
// r. Служит для чтения/изменения #r
// g. Служит для чтения/изменения #g
// b. Служит для чтения/изменения #b
// rgb. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа
// rgb(128,255,64)
// hex. Служит для чтения/изменения всех трех цветовых каналов. Используется строковой CSS синтаксис типа
// #RRGGBB
rgb_class: {
  class CheckCondition extends Error {
    constructor(msg) {
      super(msg);
    }
  }

  class RGB {
    #r;
    #g;
    #b;
    get r() {
      return this.#r;
    }
    set r(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#r = value;
      } else {
        throw new RangeError();
      }
    }
    get g() {
      return this.#g;
    }
    set g(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#g = value;
      } else {
        throw new RangeError();
      }
    }
    get b() {
      return this.#b;
    }
    set b(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#b = value;
      } else {
        throw new RangeError();
      }
    }
    get rgb() {
      return `rgb(${this.#r}, ${this.#g}, ${this.#b})`;
    }
    set rgb(value) {
      let regex = /^(rgb)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(([01]?\d\d?|2[0-4]\d|25[0-5])\)?)$/;
      if (regex.test(value)) {
        let valueArr = value.match(regex);
        this.#r = parseInt(valueArr[2]);
        this.#g = parseInt(valueArr[4]);
        this.#b = parseInt(valueArr[6]);
      } else {
        throw new SyntaxError();
      }
    }
    get hex() {
      let color = ((this.#r >= 0 && this.#r < 16) ? ('0' + Math.floor(this.#r).toString(16)) : Math.floor(this.#r).toString(16)) +
        ((this.#g >= 0 && this.#g < 16) ? ('0' + Math.floor(this.#g).toString(16)) : Math.floor(this.#g).toString(16))
        + ((this.#b >= 0 && this.#b < 16) ? ('0' + Math.floor(this.#b).toString(16)) : Math.floor(this.#b).toString(16));
      return '#' + color;
    }
    set hex(value) {
      let regex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/;
      if (regex.test(value)) {
        let valueArr = value.match(regex);
        this.#r = parseInt(valueArr[1], 16);
        this.#g = parseInt(valueArr[2], 16);
        this.#b = parseInt(valueArr[3], 16);
      } else {
        throw new SyntaxError();
      }
    }
  }
  const rgb = new RGB;
  rgb.r = 15;
  rgb.g = 128;
  rgb.b = 192;
  console.log(rgb.hex);
  console.log(rgb.rgb);
  console.log(rgb.r, rgb.g, rgb.b);
  rgb.hex = '#203040';
  console.log(rgb.rgb);
  console.log(rgb.hex);
  rgb.rgb = 'rgb(100, 90, 50)';
  console.log(rgb.r, rgb.g, rgb.b);
}

// RGBA Class
// Создайте класс-наследник класса RGB под названием RGBA. В нем должно добавиться новое приватное поле #a,
// содержащее значение прозрачности в диапазоне от 0 до 1. Создайте сеттер и геттер a. Перекройте сеттер и
// геттер hex, что бы в классе-наследнике работал синтаксис #RRGGBBAA. Учтите, что сеттер и геттер предка
// могут вам помочь. Также, сеттер hex должен поддерживать синтаксис #RRGGBB без прозрачности. Добавьте
// сеттер и геттер rgba, которые работают с CSS-синтаксисом вида rgba(128,255,64, 0.5). Добавьте сеттер color,
// в который можно присваивать любой из синтаксисов CSS - #RRGGBB, #RRGGBBAA, rgb(1,2,3) и rgba(1,2,3,0.5).
// Сеттер a должен проверять диапазон и выбрасывать исключение в случае несоответствия диапазона.
rgba_class: {
  class CheckCondition extends Error {
    constructor(msg) {
      super(msg);
    }
  }

  class RGB {
    #r;
    #g;
    #b;

    constructor() { }

    get r() {
      return this.#r;
    }
    set r(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#r = value;
      } else {
        throw new RangeError();
      }
    }
    get g() {
      return this.#g;
    }
    set g(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#g = value;
      } else {
        throw new RangeError();
      }
    }
    get b() {
      return this.#b;
    }
    set b(value) {
      if (typeof value == 'number' && value >= 0 && value <= 255) {
        this.#b = value;
      } else {
        throw new RangeError();
      }
    }
    get rgb() {
      return `rgb(${this.#r}, ${this.#g}, ${this.#b})`;
    }
    set rgb(value) {
      let regex = /^(rgb)?\(?([01]?\d\d?|2[0-4]\d|25[0-5])(\W+)([01]?\d\d?|2[0-4]\d|25[0-5])\W+(([01]?\d\d?|2[0-4]\d|25[0-5])\)?)$/;
      if (regex.test(value)) {
        let valueArr = value.match(regex);
        this.#r = parseInt(valueArr[2]);
        this.#g = parseInt(valueArr[4]);
        this.#b = parseInt(valueArr[6]);
      } else {
        throw new SyntaxError();
      }
    }
    get hex() {
      let color = ((this.#r >= 0 && this.#r < 16) ? ('0' + Math.floor(this.#r).toString(16)) : Math.floor(this.#r).toString(16)) +
        ((this.#g >= 0 && this.#g < 16) ? ('0' + Math.floor(this.#g).toString(16)) : Math.floor(this.#g).toString(16))
        + ((this.#b >= 0 && this.#b < 16) ? ('0' + Math.floor(this.#b).toString(16)) : Math.floor(this.#b).toString(16));
      return '#' + color;
    }
    set hex(value) {
      let regex = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$/;
      if (regex.test(value)) {
        let valueArr = value.match(regex);
        this.#r = parseInt(valueArr[1], 16);
        this.#g = parseInt(valueArr[2], 16);
        this.#b = parseInt(valueArr[3], 16);
      } else {
        throw new SyntaxError();
      }
    }
  }

  class RGBA extends RGB {
    constructor(...params) {
      super(...params);
    }
    #a;
    get a() {
      return this.#a;
    }
    set a(value) {
      if (typeof value == 'number' && value >= 0 && value <= 1) {
        this.#a = value;
      }
    }
    get rgba() {
      return `rbda(${super.r}, ${super.g}, ${super.b}, ${this.#a})`
    }
    set rgba(value) {
      let regex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/;
      if (regex.test(value)) {
        let valueArr = value.match(regex);
        super.r = +valueArr[1];
        super.g = +valueArr[2];
        super.b = +valueArr[3];
        this.#a = valueArr[4];
      } else {
        throw new SyntaxError();
      }
    }
    get hex() {
      if (this.#a) {
        return super.hex + Math.ceil(this.#a * 255).toString(16);
      } else {
        return super.hex;
      }
    }
    set hex(value) {
      if (value.length == 9) {
        let opacity = value.slice(-2);
        this.#a = +(parseInt(opacity, 16) / 255).toFixed(2);
        super.hex = value.slice(0, 7);
      } else if (value.length == 7) {
        this.#a = null;
        super.hex = value;
      } else {
        throw new SyntaxError();
      }
    }
    set color(value) {
      if (value.slice(0, 1) == '#') {
        this.hex = value;
      } else if (value.slice(0, 4) == 'rgb(') {
        super.rgb = value;
      } else if (value.slice(0, 4) == 'rgba') {
        this.rgba = value;
      } else {
        throw new SyntaxError();
      }
    }
  }

  const rgba = new RGBA();
  rgba.hex = '#80808080';
  console.log(rgba.hex)
  console.log(rgba.a); //0.5
  rgba.rgba = 'rgba(128, 128, 128, 0.5)';
  console.log(rgba.rgba); //rgba(128,128,128,0.5)
  rgba.r = 192;
  rgba.a = 0.25;
  console.log(rgba.hex); //#C0808040
  console.log(rgba.a);
  rgba.color = 'rgba(1,2,3,0.70)';
  rgba.b *= 10;
  console.log(rgba.hex); //#01021EB3
  rgba.color = '#e1b9c9';
  console.log(rgba.hex);
}

