# EDINET のインスタンスデータを読み込む
#  2008-01-10 katoy
#   $ rdoc -c UTF-8 lib/*.rb

require 'rexml/document'
require 'open-uri'
require 'uri'
require 'pp'

require 'edinetlabel'

#===== 定数 =======
NS_XLINK="http://www.w3.org/1999/xlink"
NS_XSI="http://www.w3.org/2001/XMLSchema-instance"
NS_XSD="http://www.w3.org/2001/XMLSchema"

NS_XBRLI ="http://www.xbrl.org/2003/instance"
NS_REF="http://www.xbrl.org/2006/ref"
NS_LINK="http://www.xbrl.org/2003/linkbase"
NS_ISO4217="http://www.xbrl.org/2003/iso4217"

class Xbrl

  @@taxonomyinfos = {}

  def initialize
    @items = []       # [item]
    @labels = {}      # hash[:idAbs] => [{label}]
    @elements = {}    # hash[:idAbs] => {element}
    @baseRef ="";
    @files = [];      # 処理したファイル
    @ignoreFiles = [] # 未処理ファイル
    @contexts = {}
  end

  attr_reader :items
  attr_reader :labels
  attr_reader :elements
  attr_reader :files
  attr_reader :ignoreFiles
  attr_reader :labels
  attr_reader :contexts

  # http://, local ファイルの xml を読み込む
  def read_xml(path, base=nil)
    full_path = get_fullpath(path, base)
    if full_path.index("http") == nil
      src = File.new(full_path)
    else
      src = open(full_path)
    end

    doc = REXML::Document.new(src)
  end

  # ファイルのフルパスを得る
  def get_fullpath(path, base=nil)
    File.expand_path(path.to_s, base)
  end

  # http 形式のフルパスを得る
  def get_absref(ref, base=nil)
    split = URI.split(ref.to_s)

    abs = File.expand_path(split[5], base)
    abs = abs + '#' + split[8]  if split[8] != nil
  end

  # *.xsd を処理する
  def parse_schema(path, base)
    files = []

    full_path = get_fullpath(path, base)
    doc = read_xml(path, base)
    doc.elements.each("/*/*") { |elem|
      ns = elem.namespace(elem.prefix)
      localname = elem.local_name

      case ns
      when NS_XSD
        case localname
        when "annotation"
          files = files + parse_annotation(elem, base)
        when "import"
          files.push import_xsd(elem.attribute("namespace").value,
            elem.attribute("schemaLocation").value,
            base)
        when "element"
          hash = gen_hash(elem)
          hash["idAbs".intern] = full_path + '#' + hash[:id].to_s
          @elements[hash[:idAbs]] = hash
        else
          $stderr.puts "-- unknown parseSchema:#{localName}"
        end
      else
        $stderr.puts "-- unknown parseSchema:#{ns}"
      end
    }
    [path, files]
  end

  # *.xsd の anotation タグを処理する
  def parse_annotation(elem, base)
    files = []
    elem.each_element { |e|
      localname =e.local_name

      case localname
      when "appinfo"
        e.each_element {|ch|
          chName = ch.local_name
          case chName
          when "linkbaseRef"
            role = ch.attribute("role")
            role = role.value.intern if role != nil
            href =  ch.attribute("href")
            href = href.value.intern if href != nil
            files.push import_linkbase(role, href, base)
          else
            $stderr.puts "-- unknown parseAnnotation:#{chName}"
          end
        }
      else
        $stderr.puts "-- unknown parseAnnotation:#{localname}"
      end
    }
    files
  end

  # linkbase ファイルを処理する
  def import_linkbase(role, path, base)
    files = []
    case role
    when "http://www.xbrl.org/2003/role/labelLinkbaseRef".intern
      parse_label(path, base)
    when "http://www.xbrl.org/2003/role/referenceLinkbaseRef".intern
      parse_reference(path, base)
    when "http://www.xbrl.org/2003/role/presentationLinkbaseRef".intern
      parse_presentation(path, base)
    when "http://www.xbrl.org/2003/role/calculationLinkbaseRef".intern
      parse_calculation(path, base)
    when "http://www.xbrl.org/2003/role/definitionLinkbaseRef".intern
      parse_definition(path, base)
    else
      # p "----- ignore [#{role}:#{path}]"
      ignoreFiles.push [role, path]
    end
    [path, files]
  end

  # ラベルリンクファイルを処理する
  def parse_label(path, base)
    doc = read_xml(path, base)
    doc.elements.each("/*/*") { |elem|
      ns = elem.namespace(elem.prefix)
      localname = elem.local_name

      case localname
      when "labelLink"
        arcs = []
        locs = {}  # :label =>{loc}
        labs = {}  # :label =>[{label}...]

        elem.each_element { |ch|
          chName = ch.local_name
          hash = gen_hash(ch)
          case chName
          when "loc"
            locs[hash[:label]] = hash
          when "labelArc"
            arcs << hash
          when "label"
            hash["text".intern] = ch.text
            if labs[hash[:label]] == nil
              labs[hash[:label]] = [hash]
            else
              labs[hash[:label]].push hash
            end
          else
            $stderr.puts "-- unknown parseLabel:#{chName}"
          end
        }
      else
        $stderr.puts "-- unknown parseLabel:#{localname}"
      end

      arcs.each {|arc|
        hrefAbs = get_absref(locs[arc[:from]][:href], base).intern
        if (@labels[hrefAbs] == nil)
          @labels[hrefAbs] = [labs[arc[:to]]]
        else
          @labels[hrefAbs].push labs[arc[:to]]
        end
      }
    }
  end

  # 参照リンクファイルを処理する
  def parse_reference(path, base)
  end

  # 表示リンクファイルを処理する
  def parse_presentation(path, base)
  end

  # 計算リンクファイルを処理する
  def parse_calculation(path, base)
  end

  # 定義リンクファイルを処理する
  def parse_definition(path, base)
  end

  # *.xsd の情報を得る。
  # EDINET の Taxonomy 設計書を読む事で代用している。
  # TODO: xsd の import のネスト処理をすること。
  def import_xsd(ms, path, base)
    kind = EdinetTaxonomyInfo.url2kind(path)
    if kind != nil
      @@taxonomyinfos[kind] = EdinetTaxonomyInfo::load(kind) if @@taxonomyinfos[kind] == nil
    end

    files = []
    [path, files]
  end

  # インスタンスファイルを処理する
  def parse_instance(filePath)
    files = []
    @baseRef = File.dirname(filePath)
    doc = read_xml(filePath)
    doc.elements.each("/*/*") { |elem|
      prefix = elem.prefix
      ns = elem.namespace(elem.prefix)
      localname = elem.local_name
      case ns
      when NS_LINK
        if localname == "schemaRef"
          files.push parse_schema(elem.attribute("href", NS_XLINK).value, @baseRef)
        elsif localname == "roleRef"
          $stderr.puts "-- parseInstance: ignore-00 #{prefix}:#{localname}"
        elsif localname == "footnoteLink"
          $stderr.puts "-- parseInstance: ignore-01 #{prefix}:#{localname}"
        else
          $stderr.puts "-- parseInstance: unknown #{prefix}:#{localname}"
        end
      when NS_XBRLI
        case localname
        when "context"
          contextID = nil
          elem.attributes.each {|key, val|
            contextID = val.to_sym if key == "id"
          }
          REXML::XPath.each(elem, "xbrli:period/xbrli:instant/text()") {|p|
            @contexts[contextID] = p.to_s
          }
          REXML::XPath.each(elem, "xbrli:period/xbrli:startDate/text()") {|p|
            @contexts[contextID] = p.to_s
          }
          REXML::XPath.each(elem, "xbrli:period/xbrli:endDate/text()") {|p|
            @contexts[contextID] =  @contexts[contextID] + " - " + p.to_s
          }
        when "unit"
          # ignore
        else
          puts "-- unknown parseInstance 01:#{localname}"
          puts "--- " + elem.to_s
        end
      when NS_ISO4217
      when NS_XLINK
      when NS_XSI
      else
        hash = gen_hash(elem)
        v = elem.text;
        v = v.intern  if v != nil
        hash[:prefix] = prefix
        hash[:ns] = ns.intern
        hash[:name] = localname.intern
        hash[:value] = v

        @items << hash
      end
    }
    @files.push [filePath, files]
  end

  def gen_hash(elem)
    info = []
    attrs = elem.attributes
    attrs.each {|key, val|
      info << key.split(":")[-1].intern
      info << val.intern
    }
    Hash[*info]
  end

  def get_instance_table(lang="ja")
    di = "jpfr-di"  # document 情報は無視する。
    table = {}
    @items.each {|item|
      prefix = item[:prefix]
      next if (prefix == di)

      name = item[:name]
      contextRef = item[:contextRef]
      # contextStr = @contexts[contextRef.to_sym]
      value = item[:value]
      # puts "#{get_itemlabel(prefix, name, lang)} [#{contextStr}]=#{value}"

      id = prefix.to_s + ":" + name.to_s
      labelStr = get_itemlabel(prefix, name, lang)
      table[id] = {} if table[id] == nil
      table[id][:label] = labelStr
      table[id][contextRef] = value
    }

    table
  end

  def get_itemlabel(prefix, name, lang="ja")
    elem = element_find_by_key(:name, name)
    if elem != nil
      return elem[:name] if @labels[elem[:idAbs].intern] == nil
      @labels[elem[:idAbs].intern].each {|ls|
        return ls[0][:text] if ls[0][:lang] == lang.intern
      }
      return "---- no label ----"
    end
    return get_edinetlabel(prefix, name, lang)
  end

  def element_find_by_key(key, val)
    @elements.each {|k,e|
      return e if e[key] == val
    }
    nil
  end

  def get_edinetlabel(prefix, name, lang="ja")
    prefix_str = prefix.to_s
    name_str = name.to_s

    return prefix_str + ":" + name_str if prefix_str[0..6] != "jpfr-t-"

    index = lang == "ja"? 1: 9  # 1: 日本語 標準ラベル, 9: 英語 標準ラベル
    id = prefix_str + ":" + name_str
    @@taxonomyinfos.each {|key, info|
      return info.labels[id.to_sym][index] if info.labels[id.to_sym] != nil
    }

    # EDINET の Taxonomy 設計書の csv では漏れている 科目がある?
    # その科目が含まれていると思われる csv をprefix から類推して読込むことで、ラベル情報を得る。
    len = prefix_str.length
    kind = prefix_str[len-3..len].to_sym
    @@taxonomyinfos[kind] = EdinetTaxonomyInfo::load(kind)
    return @@taxonomyinfos[kind].labels[id.to_sym][index] if @@taxonomyinfos[kind].labels[id.to_sym] != nil

    # 類推も失敗
    return nil
  end

  def get_edinetsheets(lang="ja")
    ans = []
    @@taxonomyinfos.each {|k, t|
      t.sheets.each {|s|
        ans << s
      }
    }
    ans
  end

end

#--- End of File