
defmodule Polarity do

  @moduledoc """
    Add your solver function below. You may add additional helper functions if you desire. 
    Test your code by running 'mix test --seed 0' from the simple_tester_ex directory.
  """

  import Bitwise

  def polarity(board, specs) do
    positive = 0
    negative = 0
    neutral =  0
    height = tuple_size(board)
    width = String.length(elem(board, 0))

    {positive, negative, neutral, _specs} = solve({positive, negative, neutral}, board, specs, 0, 0, height, width)
    format_bitmasks(positive, negative, neutral, height, width)
  end

  def solve({positive, negative, neutral}, board, specs, row, col, height, width) do
    cond do
      is_full(positive, negative, neutral, height, width) ->
        {positive, negative, neutral, specs}

      (at(positive, row, col, height, width) ||| 
       at(negative, row, col, height, width) ||| 
       at(neutral, row, col, height, width)) == 1 and
      not (row == height - 1 and col == width - 1) ->
        {nextRow, nextCol} = get_next_magnet(board, row, col, height, width)
        solve(
          {positive, negative, neutral}, 
          board, 
          specs, 
          nextRow, 
          nextCol, 
          height, 
          width
        )
      
      true ->
        {nextRow, nextCol} = get_next_magnet(board, row, col, height, width)
        
        {newPositive, newNnegative} = place_magnet({positive, negative}, board, row, col, height, width)
        {positive, negative, neutral, specs} = 
        if check_specs_magnet(specs, board, "left", "top", row, col) and 
           check_adjacent_magnet({newPositive, newNnegative}, board, row, col, height, width) do

          {newPositive, newNnegative, newNeutral, newSpecs} = solve(
            {newPositive, newNnegative, neutral}, 
            board, 
            decrement_map_magnet(specs, board, "left", "top", row, col), 
            nextRow, 
            nextCol, 
            height, 
            width
          )
          if not (is_full(newPositive, newNnegative, newNeutral, height, width) and is_specs(newSpecs)) do
            {positive, negative, neutral, specs}
          else
            {newPositive, newNnegative, newNeutral, newSpecs}
          end
  
        else
          {positive, negative, neutral, specs} 
        end
    
        {newNnegative, newPositive} = place_magnet({negative, positive}, board, row, col, height, width)
        {positive, negative, neutral, specs} =     
        if check_specs_magnet(specs, board, "right", "bottom", row, col) and
           check_adjacent_magnet({newNnegative, newPositive}, board, row, col, height, width) do

          {newPositive, newNnegative, newNeutral, newSpecs} = solve(
            {newPositive, newNnegative, neutral}, 
            board, 
            decrement_map_magnet(specs, board, "right", "bottom", row, col), 
            nextRow, 
            nextCol, 
            height, 
            width
          )
          if not (is_full(newPositive, newNnegative, newNeutral, height, width) and is_specs(newSpecs)) do
            {positive, negative, neutral, specs}
          else
            {newPositive, newNnegative, newNeutral, newSpecs}
          end

        else
          {positive, negative, neutral, specs} 
        end
    
        newNeutral = place_magnet_neutral(neutral, board, row, col, height, width)
        if check_domino_magnet({positive, negative, newNeutral}, board, row, col, height, width) do
          {newPositive, newNnegative, newNeutral, newSpecs} = solve(
            {positive, negative, newNeutral}, 
            board,
            specs, 
            nextRow, 
            nextCol, 
            height, 
            width
          )
          if not (is_full(newPositive, newNnegative, newNeutral, height, width) and is_specs(newSpecs)) do
            {positive, negative, neutral, specs}
          else
            {newPositive, newNnegative, newNeutral, newSpecs}
          end

        else
          {positive, negative, neutral, specs} 
        end   

    end
  end

  def place(symbol, row, col, _height, width) do
    symbol ||| (1 <<< col) <<< (width * row)
  end

  def remove(symbol, row, col, _height, width) do
    symbol &&& bnot((1 <<< col) <<< (width * row))
  end

  def remove_magnet({symbol, oppositeSymbol}, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" ->
        symbol = remove(symbol, row, col, height, width)
        oppositeSymbol = remove(oppositeSymbol, row, col + 1, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "R" ->
        symbol = remove(symbol, row, col, height, width)
        oppositeSymbol = remove(oppositeSymbol, row, col - 1, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "T" ->
        symbol = remove(symbol, row, col, height, width)
        oppositeSymbol = remove(oppositeSymbol, row + 1, col, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "B" ->
        symbol = remove(symbol, row, col, height, width)
        oppositeSymbol = remove(oppositeSymbol, row - 1, col, height, width)  
        {symbol, oppositeSymbol}
      true ->  nil
    end
  end

  def place_magnet({symbol, oppositeSymbol}, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" ->
        symbol = place(symbol, row, col, height, width)
        oppositeSymbol = place(oppositeSymbol, row, col + 1, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "R" ->
        symbol = place(symbol, row, col, height, width)
        oppositeSymbol = place(oppositeSymbol, row, col - 1, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "T" ->
        symbol = place(symbol, row, col, height, width)
        oppositeSymbol = place(oppositeSymbol, row + 1, col, height, width)
        {symbol, oppositeSymbol}

      at(board, row, col) == "B" ->
        symbol = place(symbol, row, col, height, width)
        oppositeSymbol = place(oppositeSymbol, row - 1, col, height, width)  
        {symbol, oppositeSymbol}

      true ->  {symbol, oppositeSymbol}
    end
  end

  def place_magnet_neutral(symbol, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" ->
        symbol = place(symbol, row, col, height, width)
        symbol = place(symbol, row, col + 1, height, width)
        symbol

      at(board, row, col) == "R" ->
        symbol = place(symbol, row, col, height, width)
        symbol = place(symbol, row, col - 1, height, width)
        symbol

      at(board, row, col) == "T" ->
        symbol = place(symbol, row, col, height, width)
        symbol = place(symbol, row + 1, col, height, width)
        symbol

      at(board, row, col) == "B" ->
        symbol = place(symbol, row, col, height, width)
        symbol = place(symbol, row - 1, col, height, width)  
        symbol

      true -> symbol
    end
  end

  def remove_magnet_neutral(symbol, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" ->
        symbol = remove(symbol, row, col, height, width)
        symbol = remove(symbol, row, col + 1, height, width)
        symbol

      at(board, row, col) == "R" ->
        symbol = remove(symbol, row, col, height, width)
        symbol = remove(symbol, row, col - 1, height, width)
        symbol

      at(board, row, col) == "T" ->
        symbol = remove(symbol, row, col, height, width)
        symbol = remove(symbol, row + 1, col, height, width)
        symbol

      at(board, row, col) == "B" ->
        symbol = remove(symbol, row, col, height, width)
        symbol = remove(symbol, row - 1, col, height, width)  
        symbol

      true -> symbol
    end
  end

  def check_domino({positive, negative, neutral}, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" ->
        ((at(positive, row, col, height, width) &&& at(positive, row, col + 1, height, width)) |||
         (at(negative, row, col, height, width) &&& at(negative, row, col + 1, height, width)) |||
         (at(positive, row, col, height, width) &&& at(neutral, row, col + 1, height, width)) |||
         (at(negative, row, col, height, width) &&& at(neutral, row, col + 1, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(positive, row, col + 1, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(negative, row, col + 1, height, width))) == 0

      at(board, row, col) == "R" ->
        ((at(positive, row, col, height, width) &&& at(positive, row, col - 1, height, width)) |||
         (at(negative, row, col, height, width) &&& at(negative, row, col - 1, height, width)) |||
         (at(positive, row, col, height, width) &&& at(neutral, row, col - 1, height, width)) |||
         (at(negative, row, col, height, width) &&& at(neutral, row, col - 1, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(positive, row, col - 1, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(negative, row, col - 1, height, width))) == 0

      at(board, row, col) == "T" ->
        ((at(positive, row, col, height, width) &&& at(positive, row + 1, col, height, width)) |||
         (at(negative, row, col, height, width) &&& at(negative, row + 1, col, height, width)) |||
         (at(positive, row, col, height, width) &&& at(neutral, row + 1, col, height, width)) |||
         (at(negative, row, col, height, width) &&& at(neutral, row + 1, col, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(positive, row + 1, col, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(negative, row + 1, col, height, width))) == 0

      at(board, row, col) == "B" ->
        ((at(positive, row, col, height, width) &&& at(positive, row - 1, col, height, width)) |||
         (at(negative, row, col, height, width) &&& at(negative, row - 1, col, height, width)) |||
         (at(positive, row, col, height, width) &&& at(neutral, row - 1, col, height, width)) |||
         (at(negative, row, col, height, width) &&& at(neutral, row - 1, col, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(positive, row - 1, col, height, width)) |||
         (at(neutral, row, col, height, width) &&& at(negative, row - 1, col, height, width))) == 0

      true ->  nil

    end
  end

  def check_specs(map, vertical, horizontal, row, col) do
    cond do
      elem(Map.get(map, vertical), row) == 0 -> false
      elem(Map.get(map, horizontal), col) == 0 -> false
      true -> true
    end
  end

  def check_adjacent(symbol, row, col, height, width) do
    (at(symbol, row, col + 1, height, width) |||
     at(symbol, row, col - 1, height, width) |||
     at(symbol, row + 1, col, height, width) |||
     at(symbol, row - 1, col, height, width)) == 0
  end
  
  def is_fill({positive, negative, neutral}) do
    ((positive &&& negative) ||| (positive &&& neutral) ||| (negative &&& neutral)) != 0
  end

  def check_domino_magnet({positive, negative, neutral}, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" -> 
        check_domino({positive, negative, neutral}, board, row, col, height, width) and
        check_domino({positive, negative, neutral}, board, row, col + 1, height, width)
        
      at(board, row, col) == "R" ->
        check_domino({positive, negative, neutral}, board, row, col, height, width) and
        check_domino({positive, negative, neutral}, board, row, col - 1, height, width)

      at(board, row, col) == "T" ->
        check_domino({positive, negative, neutral}, board, row, col, height, width) and
        check_domino({positive, negative, neutral}, board, row + 1, col, height, width)
        
      at(board, row, col) == "B" ->
        check_domino({positive, negative, neutral}, board, row, col, height, width) and
        check_domino({positive, negative, neutral}, board, row - 1, col, height, width)

      true -> nil
    end
  end

  def check_specs_magnet(map, board, vertical, _horizontal, row, col) do
    cond do
      at(board, row, col) == "L" -> 
        cond do
          vertical == "left" -> 
            check_specs(map, "left", "top", row, col) and
            check_specs(map, "right", "bottom", row, col + 1)
          vertical == "right" -> 
            check_specs(map, "right", "bottom", row, col) and
            check_specs(map, "left", "top", row, col + 1)
          true -> nil
        end

      at(board, row, col) == "R" ->
        cond do
          vertical == "left" -> 
            check_specs(map, "left", "top", row, col) and
            check_specs(map, "right", "bottom", row, col - 1)
          vertical == "right" -> 
            check_specs(map, "right", "bottom", row, col) and
            check_specs(map, "left", "top", row, col - 1)
          true -> nil
        end

      at(board, row, col) == "T" ->
        cond do
          vertical == "left" -> 
            check_specs(map, "left", "top", row, col) and
            check_specs(map, "right", "bottom", row + 1, col)
          vertical == "right" -> 
            check_specs(map, "right", "bottom", row, col) and
            check_specs(map, "left", "top", row + 1, col)
          true -> nil
        end

      at(board, row, col) == "B" ->
        cond do
          vertical == "left" -> 
            check_specs(map, "left", "top", row, col) and
            check_specs(map, "right", "bottom", row - 1, col)
          vertical == "right" -> 
            check_specs(map, "right", "bottom", row, col) and
            check_specs(map, "left", "top", row - 1, col)
          true -> nil
        end

      true -> true
    end
  end

  def check_adjacent_magnet({symbol, oppositeSymbol}, board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" -> 
        check_adjacent(symbol, row, col, height, width) and
        check_adjacent(oppositeSymbol, row, col + 1, height, width)
        
      at(board, row, col) == "R" ->
        check_adjacent(symbol, row, col, height, width) and
        check_adjacent(oppositeSymbol, row, col - 1, height, width)

      at(board, row, col) == "T" ->
        check_adjacent(symbol, row, col, height, width) and
        check_adjacent(oppositeSymbol, row + 1, col, height, width)

      at(board, row, col) == "B" ->
        check_adjacent(symbol, row, col, height, width) and
        check_adjacent(oppositeSymbol, row - 1, col, height, width)

      true -> nil
    end
  end

  def is_full(positive, negative, neutral, height, width) do
    Integer.pow(2, height * width) - positive - negative - neutral == 1
  end

  def is_specs(specs) do

    func = fn {_key, value} ->
      value
      |> Tuple.to_list()
      |> Enum.reduce(0, fn x, acc -> if x > -1, do: acc + x, else: acc end)
    end

    specs
    |> Map.to_list()
    |> Enum.filter(fn {key, _value} -> key in ["left", "right", "top", "bottom"] end)
    |> Enum.map(func)
    |> Enum.sum()
    |> Kernel.==(0)
    end

  def integer_to_binary(num) do
    num |> Integer.to_string(2)
  end

  def integer_to_binary(num, height, width) do
    num |> Integer.to_string(2) |> String.pad_leading(height * width, "0") |> String.reverse()
  end

  def at(tuple, row, col) when is_tuple(tuple) do
    cond do
      is_tuple(elem(tuple, row)) -> elem(elem(tuple, row), col)
      is_binary(elem(tuple, row)) -> String.at(elem(tuple, row), col)
      true -> nil
    end
  end

  def at(num, row, col, height, width) when is_integer(num) do
    cond do
      row >= height -> 0
      col >= width -> 0
      (num &&& ((1 <<< col) <<< (width * row))) == 0 -> 0
      true -> 1
    end
  end

  def get_next(row, col, height, width) do
    cond do
      row == height - 1 and col == width - 1 -> {row, col}
      col == width - 1 -> {row + 1, 0}
      true -> {row, col + 1}
    end
  end

  def get_next_magnet(board, row, col, height, width) do
    cond do
      at(board, row, col) == "L" -> 
        cond do
          row == height - 1 and col + 2 >= width -> {height - 1, width - 1}
          col + 2 >= width -> {row + 1, 0}
          true -> {row, col + 2}
        end
      true -> get_next(row, col, height, width)
    end
  end


  def decrement_map(map, vertical, horizontal, row, col) do
    map = Map.put(map, vertical, put_elem(Map.get(map, vertical), row, elem(Map.get(map, vertical), row) - 1))
    map = Map.put(map, horizontal, put_elem(Map.get(map, horizontal), col, elem(Map.get(map, horizontal), col) - 1))
    map
  end

  def decrement_map_magnet(map, board, vertical, _horizontal, row, col) do  
    cond do
      at(board, row, col) == "L" -> 
        cond do
          vertical == "left" -> 
            map = decrement_map(map, "left", "top", row, col)
            map = decrement_map(map, "right", "bottom", row, col + 1)
            map
          vertical == "right" -> 
            map = decrement_map(map, "right", "bottom", row, col)
            map = decrement_map(map, "left", "top", row, col + 1)
            map
          true -> nil
        end

      at(board, row, col) == "R" ->
        cond do
          vertical == "left" -> 
            map = decrement_map(map, "left", "top", row, col)
            map = decrement_map(map, "right", "bottom", row, col - 1)
            map
          vertical == "right" -> 
            map = decrement_map(map, "right", "bottom", row, col)
            map = decrement_map(map, "left", "top", row, col - 1)
            map
          true -> nil
        end

      at(board, row, col) == "T" ->
        cond do
          vertical == "left" -> 
            map = decrement_map(map, "left", "top", row, col)
            map = decrement_map(map, "right", "bottom", row + 1, col)
            map
          vertical == "right" -> 
            map = decrement_map(map, "right", "bottom", row, col)
            map = decrement_map(map, "left", "top", row + 1, col)
            map
          true -> nil
        end

      at(board, row, col) == "B" ->
        cond do
          vertical == "left" -> 
            map = decrement_map(map, "left", "top", row, col)
            map = decrement_map(map, "right", "bottom", row - 1, col)
            map
          vertical == "right" -> 
            map = decrement_map(map, "right", "bottom", row, col)
            map = decrement_map(map, "left", "top", row - 1, col)
            map
          true -> nil
        end

      true -> nil
    end
  end

  def increment_map(map, vertical, horizontal, row, col) do
    map = Map.put(map, vertical, put_elem(Map.get(map, vertical), row, elem(Map.get(map, vertical), row) + 1))
    map = Map.put(map, horizontal, put_elem(Map.get(map, horizontal), col, elem(Map.get(map, horizontal), col) + 1))
    map
  end

  def increment_map_magnet(map, board, vertical, _horizontal, row, col) do  
    cond do
      at(board, row, col) == "L" -> 
        cond do
          vertical == "left" -> 
            map = increment_map(map, "left", "top", row, col)
            map = increment_map(map, "right", "bottom", row, col + 1)
            map
          vertical == "right" -> 
            map = increment_map(map, "right", "bottom", row, col)
            map = increment_map(map, "left", "top", row, col + 1)
            map
          true -> nil
        end

      at(board, row, col) == "R" ->
        cond do
          vertical == "left" -> 
            map = increment_map(map, "left", "top", row, col)
            map = increment_map(map, "right", "bottom", row, col - 1)
            map
          vertical == "right" -> 
            map = increment_map(map, "right", "bottom", row, col)
            map = increment_map(map, "left", "top", row, col - 1)
            map
          true -> nil
        end

      at(board, row, col) == "T" ->
        cond do
          vertical == "left" -> 
            map = increment_map(map, "left", "top", row, col)
            map = increment_map(map, "right", "bottom", row + 1, col)
            map
          vertical == "right" -> 
            map = increment_map(map, "right", "bottom", row, col)
            map = increment_map(map, "left", "top", row + 1, col)
            map
          true -> nil
        end

      at(board, row, col) == "B" ->
        cond do
          vertical == "left" -> 
            map = increment_map(map, "left", "top", row, col)
            map = increment_map(map, "right", "bottom", row - 1, col)
            map
          vertical == "right" -> 
            map = increment_map(map, "right", "bottom", row, col)
            map = increment_map(map, "left", "top", row - 1, col)
            map
          true -> nil
        end

      true -> nil
    end
  end
  
  def reverse_tuple(tup) do
    tup |> Tuple.to_list() |> Enum.reverse() |> List.to_tuple()
  end
  
  def format_bitmasks(positive, negative, neutral, height, width) do
    positive = integer_to_binary(positive, height, width)
    negative = integer_to_binary(negative, height, width)
    neutral = integer_to_binary(neutral, height, width)

    result_string =
      Enum.zip([String.graphemes(positive), String.graphemes(negative), String.graphemes(neutral)])
      |> Enum.map(&map_bits_to_char/1)
      |> Enum.join()

    result_string
    |> String.graphemes()
    |> Enum.chunk_every(width)
    |> Enum.map(&Enum.join(&1))
    |> List.to_tuple()
  end

  def map_bits_to_char({"0", "0", "0"}), do: " "
  def map_bits_to_char({"1", "0", "0"}), do: "+"
  def map_bits_to_char({"0", "1", "0"}), do: "-"
  def map_bits_to_char({"0", "0", "1"}), do: "X"
  def map_bits_to_char({"1", "1", "0"}), do: "E"
  def map_bits_to_char({"1", "0", "1"}), do: "E"
  def map_bits_to_char({"0", "1", "1"}), do: "E"
  def map_bits_to_char({"1", "1", "1"}), do: "E"

end
