# Copyright (c) 2012 by Zuse-Institute Berlin and the Technical University of Denmark.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     1. Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#     3. Neither the name of the copyright holders nor contributors may not
#        be used to endorse or promote products derived from this software
#        without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS NOR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


# Direct execution requires top level directory on python path                          
if __name__ == "__main__":
  import os, sys, inspect
  scriptdir = os.path.split(inspect.getfile( inspect.currentframe() ))[0]
  packagedir = os.path.realpath(os.path.abspath(os.path.join(scriptdir,'..')))
  if packagedir not in sys.path:
    sys.path.insert(0, packagedir)


import os, sys

class CBFdataconverter:
  def __init__(self, file, outputfile, skipdata = False):
    self.name = os.path.splitext(os.path.basename(file))[0]
    self.ver = None
    self.obj = None
    self.mapnum = 0
    self.mapstacknum = 0
    self.mapstackdim = list()
    self.mapstackdomain = list()
    self.varnum = 0
    self.varstacknum = 0
    self.varstackdim = list()
    self.varstackdomain = list()
    self.intvarnum = 0
    self.intvar = list()
    self.psdmapnum = 0
    self.psdmapdim = list()
    self.psdvarnum = 0
    self.psdvardim = list()

    self.objfnnz = 0
    self.objfsubj = list()
    self.objfsubk = list()
    self.objfsubl = list()
    self.objfval = list()
    self.objannz = 0
    self.objasubj = list()
    self.objaval = list()
    self.objbval = list()

    self.fnnz = 0
    self.fsubi = list()
    self.fsubj = list()
    self.fsubk = list()
    self.fsubl = list()
    self.fval = list()
    self.annz = 0
    self.asubi = list()
    self.asubj = list()
    self.aval = list()
    self.bnnz = 0
    self.bsubi = list()
    self.bval = list()

    self.hnnz = 0
    self.hsubi = list()
    self.hsubj = list()
    self.hsubk = list()
    self.hsubl = list()
    self.hval = list()
    self.dnnz = 0
    self.dsubi = list()
    self.dsubk = list()
    self.dsubl = list()
    self.dval = list()

    firstemptyline = True
    keyset = 0
    (linenum,line) = (-1, "")
    keywords = ['VER','OBJ','CON','VAR','INT','PSDCON','PSDVAR','FCOORD','ACOORD','BVEC','HCOORD','DCOORD']
    ff = open(file,'rt')
    fo = open(outputfile,'w')
    f = enumerate(ff)
    try:
      for (linenum,line) in f:
        line = self.__prepare_line(line)

        # Ignore comments between blocks
        if line.startswith('#'):
          if keyset != 2:
            fo.write(line + '\n')
          continue

        # Ignore empty lines between blocks
        if not line:
          if keyset != 2:
            if firstemptyline:
              fo.write(line + '\n')
          firstemptyline = False
          continue

        firstemptyline = True

        #
        # Keyword set: File description keywords
        #
        if keyset == 0:
          if line == "VER":
            fo.write(line + '\n')
            (linenum,line) = next(f)
            fo.write(line)
            self.ver = int(self.__prepare_line(line))
            continue

          # Unrecognized line. Going to next set of keywords.
          if line in keywords:
            keyset = self.__inc_keyset(keyset)
          else:
            raise Exception('Keyword not recognized')


        #
        # Keyword set: Structural keywords (note the default values)
        #
        if keyset == 1:
          if line == "OBJ":
            fo.write('OBJSENSE\n')
            (linenum,line) = next(f)
            self.obj = int(self.__prepare_line(line))
            if (self.obj == 0):
              fo.write('MIN\n')
            else:
              fo.write('MAX\n')
            continue

          if line == "CON":
            (linenum,line) = next(f)
            buf = self.__prepare_line(line).split(' ')
            self.mapnum = int(buf[0])
            self.mapstacknum = int(buf[1])
            self.mapstackdomain = ['']*self.mapstacknum
            self.mapstackdim    =  [0]*self.mapstacknum
            for i in range(self.mapstacknum):
              (linenum,line) = next(f)
              buf = self.__prepare_line(line).split(' ')
              if buf[0] == "RQ":
                buf[0] = "QR"
              self.mapstackdomain[i] = buf[0]
              self.mapstackdim[i]    = int(buf[1])
            firstemptyline = False
            continue

          if line == "VAR":
            fo.write(line + '\n')
            (linenum,line) = next(f)
            fo.write(line)
            buf = self.__prepare_line(line).split(' ')
            self.varnum = int(buf[0])
            self.varstacknum = int(buf[1])
            self.varstackdomain = ['']*self.varstacknum
            self.varstackdim    =  [0]*self.varstacknum
            for i in range(self.varstacknum):
              (linenum,line) = next(f)
              buf = self.__prepare_line(line).split(' ')
              if buf[0] == "RQ":
                buf[0] = "QR"
              self.varstackdomain[i] = buf[0]
              self.varstackdim[i]    = int(buf[1])
              fo.write(self.varstackdomain[i] + ' ' + str(self.varstackdim[i]) + '\n')
            continue

          if line == "INT":
            fo.write(line + '\n')
            (linenum,line) = next(f)
            fo.write(line)
            self.intvarnum = int(self.__prepare_line(line))
            self.intvar = [0]*self.intvarnum
            for i in range(self.intvarnum):
              (linenum,line) = next(f)
              fo.write(line)
              self.intvar[i] = int(self.__prepare_line(line))
            continue

          if line == "PSDCON":
            (linenum,line) = next(f)
            self.psdmapnum = int(self.__prepare_line(line))
            self.psdmapdim = [0]*self.psdmapnum
            for i in range(self.psdmapnum):
              (linenum,line) = next(f)
              self.psdmapdim[i] = int(self.__prepare_line(line))
            firstemptyline = False
            continue

          if line == "PSDVAR":
            fo.write(line + '\n')
            (linenum,line) = next(f)
            fo.write(line)
            self.psdvarnum = int(self.__prepare_line(line))
            self.psdvardim = [0]*self.psdvarnum
            for i in range(self.psdvarnum):
              (linenum,line) = next(f)
              fo.write(line)
              self.psdvardim[i] = int(self.__prepare_line(line))
            continue

          # Unrecognized line. Going to next set of keywords.
          if line in keywords:
            keyset = self.__inc_keyset(keyset)

            # Write CON
            if self.mapstacknum >= 1:
              fo.write('CON\n')
              fo.write(str(self.mapnum-1) + ' ' + str(self.mapstacknum) + '\n')
              for i in range(self.mapstacknum):
                fo.write(self.mapstackdomain[i] + ' ' + str(self.mapstackdim[i]) + '\n')
              fo.write('\n')

            # Write PSDCON
            if self.psdmapnum >= 1:
              fo.write('PSDCON\n' + str(self.psdmapnum) + '\n')
              for i in range(self.psdmapnum):
                fo.write(str(self.psdmapdim[i]) + '\n')
              fo.write('\n')

          else:
            raise Exception('Keyword not recognized')


        #
        # Keyword set: Data keywords
        #
        if keyset == 2:
          if skipdata == True:
            break

          if line == "FCOORD":
            (linenum,line) = next(f)
            totalfnnz  = int(self.__prepare_line(line))

            self.objfsubj = [0]*totalfnnz
            self.objfsubk = [0]*totalfnnz
            self.objfsubl = [0]*totalfnnz
            self.objfval  = [0.0]*totalfnnz

            self.fsubi = [0]*totalfnnz
            self.fsubj = [0]*totalfnnz
            self.fsubk = [0]*totalfnnz
            self.fsubl = [0]*totalfnnz
            self.fval  = [0.0]*totalfnnz
            for i in range(totalfnnz):
              (linenum,line) = next(f)
              buf = self.__prepare_line(line).split(' ')
              if int(buf[0]) == 0:
                self.objfsubj[self.objfnnz] = int(buf[1])
                self.objfsubk[self.objfnnz] = int(buf[2])
                self.objfsubl[self.objfnnz] = int(buf[3])
                self.objfval[self.objfnnz]  = float(buf[4])
                self.objfnnz += 1
              else:
                self.fsubi[self.fnnz] = int(buf[0]) - 1
                self.fsubj[self.fnnz] = int(buf[1])
                self.fsubk[self.fnnz] = int(buf[2])
                self.fsubl[self.fnnz] = int(buf[3])
                self.fval[self.fnnz]  = float(buf[4])
                self.fnnz += 1
            continue

          if line == "ACOORD":
            (linenum,line) = next(f)

            totalannz = int(self.__prepare_line(line))

            self.objasubj = [0]*totalannz
            self.objaval  = [0.0]*totalannz

            self.asubi = [0]*totalannz
            self.asubj = [0]*totalannz
            self.aval  = [0.0]*totalannz
            for i in range(totalannz):
              (linenum,line) = next(f)
              buf = self.__prepare_line(line).split(' ')
              if int(buf[0]) == 0:
                self.objasubj[self.objannz] = int(buf[1])
                self.objaval[self.objannz]  = float(buf[2])
                self.objannz += 1
              else:
                self.asubi[self.annz] = int(buf[0]) - 1
                self.asubj[self.annz] = int(buf[1])
                self.aval[self.annz]  = float(buf[2])
                self.annz += 1
            continue

          if line == "BVEC":
            totalbnnz = self.mapnum - 1
            self.bsubi = [0] * totalbnnz
            self.bval  = [0.0]*totalbnnz

            (linenum,line) = next(f)
            buf = self.__prepare_line(line).split(' ')
            self.objbval = float(buf[0])

            for i in range(totalbnnz):
              (linenum,line) = next(f)
              buf = self.__prepare_line(line).split(' ')
              if float(buf[0]) != 0:
                self.bsubi[self.bnnz] = i
                self.bval[self.bnnz] = float(buf[0])
                self.bnnz += 1
            continue

        raise Exception('Keyword not recognized')

      # Write problem data
      if self.objfnnz > 0:
        fo.write('OBJFCOORD' + '\n')
        fo.write(str(self.objfnnz) + '\n')
        for i in range(self.objfnnz):
          fo.write(''.join([str(self.objfsubj[i]), ' ', str(self.objfsubk[i]), ' ', str(self.objfsubl[i]), ' ', '{:.16g}'.format(self.objfval[i]), '\n']))
        fo.write('\n')

      if self.objannz > 0:
        fo.write('OBJACOORD' + '\n')
        fo.write(str(self.objannz) + '\n')
        for i in range(self.objannz):
          fo.write(''.join([str(self.objasubj[i]), ' ', '{:.16g}'.format(self.objaval[i]), '\n']))
        fo.write('\n')

      if self.objbval != 0:
        fo.write('OBJBCOORD' + '\n')
        fo.write('{:.16g}'.format(self.objbval) + '\n')
        fo.write('\n')

      if self.fnnz > 0:
        fo.write('FCOORD' + '\n')
        fo.write(str(self.fnnz) + '\n')
        for i in range(self.fnnz):
          fo.write(''.join([str(self.fsubi[i]), ' ', str(self.fsubj[i]), ' ', str(self.fsubk[i]), ' ', str(self.fsubl[i]), ' ', '{:.16g}'.format(self.fval[i]), '\n']))
        fo.write('\n')

      if self.annz > 0:
        fo.write('ACOORD' + '\n')
        fo.write(str(self.annz) + '\n')
        for i in range(self.annz):
          fo.write(''.join([str(self.asubi[i]), ' ', str(self.asubj[i]), ' ', '{:.16g}'.format(self.aval[i]), '\n']))
        fo.write('\n')

      if self.bnnz > 0:
        fo.write('BCOORD' + '\n')
        fo.write(str(self.bnnz) + '\n')
        for i in range(self.bnnz):
          fo.write(''.join([str(self.bsubi[i]), ' ', '{:.16g}'.format(self.bval[i]), '\n']))
        fo.write('\n')

    except Exception as e:
      if isinstance(e, StopIteration):
        msg = 'Unexpected end of file'
      else:
        msg = str(e)

      raise Exception(''.join([
            msg, '. File: ', file, '\n',
            str(linenum+1), ': ', line, '\n']))

    finally:
      ff.close()


  def __prepare_line(self, line):
    line = line.rstrip('\r\n')
    if len(line) > 510:
      # Line content should fit within 512 bytes with room for '\r\n'
      raise Exception('Line too wide')
    else:
      # Ignore leading and trailing whitespace characters
      return line.strip(' ')


  def __inc_keyset(self, keyset):
    if keyset == 0:
      if self.ver is None:
        raise Exception('Expected keyword "VER"')

    elif keyset == 1:
      if self.obj is None:
        raise Exception('Expected keyword "OBJ"')

    return keyset+1


if __name__ == "__main__":
  try:

    # Print filtering result
    for file in sys.argv[1:]:
      outputfile = os.path.join(os.path.dirname(os.path.dirname(file)), 'cbf-converted', os.path.basename(file))
      print(file + ' -> ' + outputfile)
      CBFdataconverter(file, outputfile)

  except Exception as e:
    print(str(e))
