# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'ostruct'
require 'delegate'

class Attritute < SimpleDelegator
  def getter_name
    r = name.capitalize
    type == 'bool' ? 'Is' + r : 'Get' + r
  end

  def setter_name
    'Set' + name.capitalize
  end

  def bool?
    type == 'bool'
  end

  def enum?
    type == 'enum'
  end

  def size?
    type == 'size'
  end

  def multiple?
    !bool? && multiple
  end

  def applicable_to?(item_type)
    applicable_to.include?(item_type)
  end

  def set_flags?
    (defined? flags) && flags.any? || enum? && values.any? { |v| v.flags && v.flags.any? }
  end
end

module Metadata
  module_function

  def attributes
    @data.attributes.map do |op|
      Attritute.new(op)
    end
  end

  def language
    @data.language || ''
  end

  def extends_default?
    !language.empty?
  end

  def item_types
    ['record', 'field', 'function', 'param']
  end

  def wrap_data(data)
    @data = data
  end
end

def Gen.on_require(data)
  Metadata.wrap_data(data)
end

module MetadataGen
  module_function

  def attribute_name(attribute)
    return attribute.name if Metadata.language.empty?
    "#{Metadata.language.downcase}.#{attribute.name}"
  end

  def class_name(item_type)
    item_type.capitalize + 'Metadata'
  end

  def validate_body(item_type, is_bool)
    attributes = Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? == is_bool }
    body = []
    indent = ' ' * 4

    attributes.each do |a|
      body << "#{indent}if (attribute == \"#{attribute_name(a)}\") {"

      unless a.multiple?
        body << "#{indent * 2}if (HasAttribute(attribute)) {"
        body << "#{indent * 3}return Error(\"Attribute '#{attribute_name(a)}' already defined\","
        body << "#{indent * 3}             Error::Type::MULTIPLE_ATTRIBUTE);"
        body << "#{indent * 2}}"
      end

      if a.enum?
        a.values.each do |v|
          body << "#{indent * 2}if (value == \"#{v.value}\") {"
          body << "#{indent * 3}return {};"
          body << "#{indent * 2}}"
          body << ""
        end

        body << "#{indent * 2}return Error(std::string(\"Attribute '#{attribute_name(a)}' have incorrect value '\").append(value) +"
        body << "#{indent * 2}             R\"('. Should be one of #{a.values.map(&:value)})\", Error::Type::INVALID_VALUE);"
      elsif a.size?
        body << "#{indent * 2}return ValidateSize(value);"
      else
        body << "#{indent * 2}return {};"
      end

      body << "#{indent}}"
      body << ""
    end

    Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? != is_bool }.each do |a|
      body << "#{indent}if (attribute == \"#{attribute_name(a)}\") {"
      body << "#{indent * 2}return Error(\"Attribute '#{attribute_name(a)}' #{is_bool ? "must" : "must not"} have a value\","
      body << "#{indent * 2}             #{is_bool ? "Error::Type::MISSING_VALUE" : "Error::Type::UNEXPECTED_VALUE"});"
      body << "#{indent}}"
      body << ""
    end

    if Metadata::extends_default?
      args = ['attribute']
      args << 'value' unless is_bool
      body << "#{indent}return pandasm::#{class_name(item_type)}::Validate(#{args.join(', ')});"
    else
      body << "#{indent}return Error(std::string(\"Unknown attribute '\").append(attribute) + \"'\","
      body << "#{indent}             Error::Type::UNKNOWN_ATTRIBUTE);"
    end

    body
  end

  def set_flags_body(item_type, is_bool)
    attributes = Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? == is_bool && a.set_flags? }
    body = []
    indent = ' ' * 4

    attributes.each do |a|
      body << "#{indent}if (attribute == \"#{attribute_name(a)}\") {"

      if defined? a.flags
        body << "#{indent * 2}SetAccessFlags(GetAccessFlags() | #{a.flags.join(' | ')});"
      end

      if a.enum?
        a.values.select { |v| v.flags && v.flags.any? }.each do |v|
          body << "#{indent * 2}if (value == \"#{v.value}\") {"
          body << "#{indent * 3}SetAccessFlags(GetAccessFlags() | #{v.flags.join(' | ')});"
          body << "#{indent * 2}}"
          body << ""
        end
      end

      body << "#{indent}}"
    end

    if Metadata::extends_default?
        args = ['attribute']
        args << 'value' unless is_bool
        body << "#{indent}pandasm::#{class_name(item_type)}::SetFlags(#{args.join(', ')});"
    end

    body
  end

  def remove_flags_body(item_type, is_bool)
    attributes = Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? == is_bool && a.set_flags? }
    body = []
    indent = ' ' * 4

    attributes.each do |a|
      body << "#{indent}if (attribute == \"#{attribute_name(a)}\") {"

      if defined? a.flags
        body << "#{indent * 2}if ((GetAccessFlags() & #{a.flags.join(' | ')}) != 0) {"
        body << "#{indent * 3}SetAccessFlags(GetAccessFlags() ^ (#{a.flags.join(' | ')}));"
        body << "#{indent * 2}}"
      end

      if a.enum?
        a.values.select { |v| v.flags && v.flags.any? }.each do |v|
          body << "#{indent * 2}if (value == \"#{v.value}\") {"
          body << "#{indent * 3}if ((GetAccessFlags() & (#{v.flags.join(' | ')})) != 0) {"
          body << "#{indent * 4}SetAccessFlags(GetAccessFlags() ^ (#{v.flags.join(' | ')}));"
          body << "#{indent * 3}}"
          body << "#{indent * 2}}"
        end
      end

      body << "#{indent}}"
    end

    if Metadata::extends_default?
      args = ['attribute']
      args << 'value' unless is_bool
      body << "#{indent}pandasm::#{class_name(item_type)}::RemoveFlags(#{args.join(', ')});"
    end

    body
  end

  def arg_list(is_bool)
    args = ['const std::string_view &attribute']
    args << 'const std::string_view &value' if !is_bool
    args
  end

  def add_unused_attribute(arg)
    "[[maybe_unused]] #{arg}"
  end

  def validate_arg_list(item_type, is_bool)
    args = arg_list(is_bool)
    return args if Metadata::extends_default?

    attributes = Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? == is_bool }
    args[0] = add_unused_attribute(args[0]) if attributes.none?
    args[1] = add_unused_attribute(args[1]) if args[1] && attributes.none? { |a| a.enum? }
    args
  end

  def flags_arg_list(item_type, is_bool)
    args = arg_list(is_bool)
    return args if Metadata::extends_default?

    attributes = Metadata::attributes.select { |a| a.applicable_to?(item_type) && a.bool? == is_bool && a.set_flags? }
    use_value = attributes.any? { |a| a.enum? && a.values.any? { |v| v.flags && v.flags.any? } }
    args[0] = add_unused_attribute(args[0]) if attributes.none?
    args[1] = add_unused_attribute(args[1]) if args[1] && !use_value
    args
  end

end