text: # -*- coding: utf-8 -*-
  """
  /***************************************************************************
   IndiaGeocoder
                                   A QGIS plugin
   This plugin is used for analysis of HMIS data
   Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                                -------------------
          begin                : 2018-06-20
          git sha              : $Format:%H$
          copyright            : (C) 2018 by IIRS
          email                : kotishiva@gmail.com
   ***************************************************************************/
  
  /***************************************************************************
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
   *   it under the terms of the GNU General Public License as published by  *
   *   the Free Software Foundation; either version 2 of the License, or     *
   *   (at your option) any later version.                                   *
   *                                                                         *
   ***************************************************************************/
  """
  import os
  import string
  import csv
  import pandas as pd
  
  from qgis.core import *
  from qgis.gui import *
  import qgis._core
  from qgis._gui import *
  from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication, Qt, QSize
  from PyQt5.QtGui import QIcon, QColor, QFont
  from PyQt5.QtWidgets import QAction, QFileDialog, QProgressDialog, QProgressBar, QMessageBox, QComboBox, QDialog
  from bs4 import BeautifulSoup
  
  # Initialize Qt resources from file resources.py
  # Import the code for the dialogs
  from .india_geocoder_dialog import IndiaGeocoderDialog
  # from .india_geocoder_dialog import StateDialog        # import for sub-dist level data
  from .india_geocoder_dialog import VisualisationDialog
  from .india_geocoder_dialog import MultipleInputDialog
  from .india_geocoder_dialog import SelectLayersDialog
  
  class IndiaGeocoder:
      """QGIS Plugin Implementation."""
  
      def __init__(self, iface):
          """Constructor.
  
          :param iface: An interface instance that will be passed to this class
              which provides the hook by which you can manipulate the QGIS
              application at run time.
          :type iface: QgsInterface
          """
          # Save reference to the QGIS interface
          self.iface = iface
          # initialize plugin directory
          self.plugin_dir = os.path.dirname(__file__)
          # initialize locale
          locale = QSettings().value('locale/userLocale')[0:2]
          locale_path = os.path.join(
              self.plugin_dir,
              'i18n',
              'IndiaGeocoder_{}.qm'.format(locale))
  
          if os.path.exists(locale_path):
              self.translator = QTranslator()
              self.translator.load(locale_path)
  
              if qVersion() > '4.3.3':
                  QCoreApplication.installTranslator(self.translator)
  
          # Create the dialogs (after translation) and keep reference
          self.dlg = IndiaGeocoderDialog()
          self.visual_dlg = VisualisationDialog()
  
          # Declare instance attributes
          self.actions = []
          self.menu = self.tr(u'&India Geocoder')
          self.all_columns = []   # All fields from the HMIS csv file
  
          # This dict comes to use when creating concat string ID for sub dist level.
          self.states_dict = {'Arunachal Pradesh': ['Anjaw', 'Changlang', 'Dibang Valley', 'East Kameng', 'East Siang',
                                                    'Kra Daadi', 'Kurung Kumey', 'Lohit', 'Longding', 'Lower Dibang Valley',
                                                    'Lower Subansiri', 'Namsai', 'Papum Pare', 'Siang', 'Tawang', 'Tirap',
                                                    'Upper Siang', 'Upper Subansiri', 'West Kameng', 'West Siang'],
                              'Andhra Pradesh': ['Anantapur', 'Chittoor', 'Cuddapah', 'East Godavari', 'Guntur', 'Krishna',
                                                 'Kurnool', 'Nellore', 'Prakasam', 'Srikakulam', 'Vishakapatnam',
                                                 'Vizianagaram', 'West Godavari'],
                              'Lakshadweep': ['Lakshadweep'],
                              'Telangana': ['Adilabad', 'Bhadradri Kothagudem', 'Hyderabad', 'Jagitial', 'Jangoan',
                                            'Jayashankar Bhupalpally', 'Jogulamba Gadwal', 'Kamareddy', 'Komaram Bheem',
                                            'Karim Nagar', 'Khammam', 'Mahabubabad', 'Mancherial', 'Medchal Malkajgiri',
                                            'Mahbubnagar', 'Medak', 'Nagarkurnool', 'Nirmal', 'Nalgonda', 'Nizamabad',
                                            'Peddapalli', 'Rajanna Sircilla', 'Ranga Reddy', 'Sangareddy', 'Siddipet',
                                            'Suryapet', 'Vikarabad', 'Wanaparthy', 'Warangal Rural', 'Warangal Urban',
                                            'Yadadri Bhonagiri'],
                              'A & N Islands': ['Nicobar', 'North And Middle Andaman', 'South Andaman'],
                              'Manipur': ['Bishnupur', 'Chandel', 'Churachandpur', 'Imphal East', 'Imphal West', 'Senapati',
                                          'Tamenglong', 'Thoubal', 'Ukhrul'],
                              'Madhya Pradesh': ['Agar Malwa', 'Alirajpur', 'Anuppur', 'Ashok Nagar', 'Balaghat', 'Barwani',
                                                 'Betul', 'Bhind', 'Bhopal', 'Burhanpur', 'Chhatarpur', 'Chhindwada',
                                                 'Damoh', 'Datia', 'Dewas', 'Dhar', 'Dindori', 'Guna', 'Gwalior', 'Harda',
                                                 'Hoshangabad', 'Indore', 'Jabalpur', 'Jhabua', 'Katni', 'Khandwa',
                                                 'Khargone', 'Mandla', 'Mandsaur', 'Morena', 'Narsinghpur', 'Neemuch',
                                                 'Panna', 'Raisen', 'Rajgarh', 'Ratlam', 'Rewa', 'Sagar', 'Satna', 'Sehore',
                                                 'Seoni', 'Shahdol', 'Shajapur', 'Sheopur', 'Shivpuri', 'Sidhi', 'Singroli',
                                                 'Tikamgarh', 'Ujjain', 'Umaria', 'Vidisha'],
                              'Daman & Diu': ['Daman', 'Diu'],
                              'Haryana': ['Ambala', 'Bhiwani', 'Faridabad', 'Fatehabad', 'Gurgaon', 'Hisar', 'Jhajjar',
                                          'Jind', 'Kaithal', 'Karnal', 'Kurukshetra', 'Mahendragarh', 'Mewat', 'Palwal',
                                          'Panchkula', 'Panipat', 'Rewari', 'Rohtak', 'Sirsa', 'Sonipat', 'Yamunanagar'],
                              'Gujarat': ['Ahmedabad', 'Amreli', 'Anand', 'Aravalli', 'Banas Kantha', 'Bharuch', 'Bhavnagar',
                                          'Botad', 'Chhotaudepur', 'Dahod', 'Devbhumi Dwarka', 'Gandhinagar', 'Gir Somnath',
                                          'Jamnagar', 'Junagadh', 'Kachchh', 'Kheda', 'Mahesana', 'Mahisagar', 'Morbi',
                                          'Narmada', 'Navsari', 'Panch Mahals', 'Patan', 'Porbandar', 'Rajkot',
                                          'Sabar Kantha', 'Surat', 'Surendranagar', 'Tapi', 'The Dangs', 'Vadodara', 'Valsad'],
                              'Sikkim': ['East', 'North', 'South', 'West'],
                              'Himachal Pradesh': ['Bilaspur', 'Chamba', 'Hamirpur', 'Kangra', 'Kinnaur', 'Kullu',
                                                   'Lahul Spiti', 'Mandi', 'Shimla', 'Sirmaur', 'Solan', 'Una'],
                              'Delhi': ['Central', 'East', 'New Delhi', 'North', 'North East', 'North West', 'Shahdara',
                                        'South', 'South East', 'South West', 'West'],
                              'Goa': ['North Goa', 'South Goa'],
                              'Punjab': ['Amritsar', 'Barnala', 'Bathinda', 'Faridkot', 'Fatehgarh Sahib', 'Fazilka',
                                         'Firozpur', 'Gurdaspur', 'Hoshiarpur', 'Jalandhar', 'Kapurthala', 'Ludhiana',
                                         'Mansa', 'Moga', 'Mohali Sas Nagar', 'Muktsar', 'Nawanshahr', 'Pathankot',
                                         'Patiala', 'Rupnagar', 'Sangrur', 'Tarn Taran'],
                              'Jammu & Kashmir': ['Anantnag', 'Badgam', 'Bandipora', 'Baramula', 'Doda', 'Ganderbal',
                                                  'Jammu', 'Kargil', 'Kathua', 'Kishtwar', 'Kulgam', 'Kupwara', 'Leh Ladakh',
                                                  'Poonch', 'Pulwama', 'Rajouri', 'Ramban', 'Reasi', 'Samba', 'Shopian',
                                                  'Srinagar', 'Udhampur'],
                              'Karnataka': ['Bagalkote', 'Bangalore Rural', 'Bangalore Urban', 'Belgaum', 'Bellary', 'Bidar',
                                            'Bijapur', 'Chamrajnagar', 'Chikkaballapur', 'Chikmagalur', 'Chitradurga',
                                            'Dakshina Kannada', 'Davanagere', 'Dharwad', 'Gadag', 'Gulbarga', 'Hassan',
                                            'Haveri', 'Kodagu', 'Kolar', 'Koppal', 'Mandya', 'Mysore', 'Raichur', 'Ramanagar',
                                            'Shimoga', 'Tumkur', 'Udupi', 'Uttara Kannada', 'Yadgir'],
                              'Maharashtra': ['Ahmednagar', 'Akola', 'Amravati', 'Aurangabad', 'Bhandara', 'Bid',
                                              'Brihan Mumbai', 'Buldana', 'Chandrapur', 'Dhule', 'Gadchiroli', 'Gondiya',
                                              'Hingoli', 'Jalgaon', 'Jalna', 'Kolhapur', 'Latur', 'Nagpur', 'Nanded',
                                              'Nandurbar', 'Nashik', 'Osmanabad', 'Palghar', 'Parbhani', 'Pune', 'Raigarh',
                                              'Ratnagiri', 'Sangli', 'Satara', 'Sindhudurg', 'Solapur', 'Thane', 'Wardha',
                                              'Washim', 'Yavatmal'],
                              'Odisha': ['Anugul', 'Balangir', 'Baleshwar', 'Bargarh', 'Baudh', 'Bhadrak', 'Cuttack',
                                         'Deogarh', 'Dhenkanal', 'Gajapati', 'Ganjam', 'Jagatsinghpur', 'Jajapur', 'Jharsuguda',
                                         'Kalahandi', 'Kandhamal', 'Kendrapara', 'Keonjhar', 'Khordha', 'Koraput', 'Malkangiri',
                                         'Mayurbhanj', 'Nabarangapur', 'Nayagarh', 'Nuapada', 'Puri', 'Rayagada', 'Sambalpur',
                                         'Sonapur', 'Sundargarh'],
                              'Dadra & Nagar Haveli': ['Dadra And Nagar Haveli'],
                              'Rajasthan': ['Ajmer', 'Alwar', 'Banswara', 'Baran', 'Barmer', 'Bharatpur', 'Bhilwara', 'Bikaner',
                                            'Bundi', 'Chittaurgarh', 'Churu', 'Dausa', 'Dhaulpur', 'Dungarpur', 'Ganganagar',
                                            'Hanumangarh', 'Jaipur', 'Jaisalmer', 'Jalor', 'Jhalawar', 'Jhunjhunun', 'Jodhpur',
                                            'Karauli', 'Kota', 'Nagaur', 'Pali', 'Pratapgarh', 'Rajsamand', 'Sawai Madhopur',
                                            'Sikar', 'Sirohi', 'Tonk', 'Udaipur'],
                              'Chandigarh': ['Chandigarh'],
                              'Kerala': ['Alappuzha', 'Ernakulam', 'Idukki', 'Kozhikkode', 'Kannur', 'Kasaragod', 'Kollam',
                                         'Kottayam', 'Malappuram', 'Palakkad', 'Pathanamthitta', 'Thiruvananthapuram',
                                         'Thrissur', 'Wayanad'],
                              'Chhattisgarh': ['Balod', 'Baloda Bazar', 'Balrampur', 'Bastar', 'Bemetra', 'Bijapur', 'Bilaspur',
                                               'Dantewada', 'Dhamtari', 'Durg', 'Gariyaband', 'Janjgir Champa', 'Jashpur',
                                               'Kanker', 'Kawardha', 'Kondagaon', 'Korba', 'Koriya', 'Mahasamund', 'Mungeli',
                                               'Narayanpur', 'Raigarh', 'Raipur', 'Rajnandgaon', 'Sukma', 'Surajpur',
                                               'Surguja'],
                              'Tripura': ['Dhalai', 'Gomati', 'Khowai', 'North Tripura', 'Sipahijala', 'South Tripura',
                                          'Unakoti', 'West Tripura'],
                              'Mizoram': ['Aizawl East', 'Aizawl West', 'Champhai', 'Kolasib', 'Lawngtlai', 'Lunglei', 'Mamit',
                                          'Saiha', 'Serchhip'],
                              'Assam': ['Baksa', 'Barpeta', 'Bongaigaon', 'Cachar', 'Chirang', 'Darrang', 'Dhemaji', 'Dhubri',
                                        'Dibrugarh', 'Dima Hasao', 'Goalpara', 'Golaghat', 'Hailakandi', 'Jorhat', 'Kamrup M',
                                        'Kamrup R', 'Karbi Anglong', 'Karimganj', 'Kokrajhar', 'Lakhimpur', 'Marigaon', 'Nagaon',
                                        'Nalbari', 'Sibsagar', 'Sonitpur', 'Tinsukia', 'Udalguri'],
                              'Meghalaya': ['East Garo Hills', 'East Jaintia Hills', 'East Khasi Hills', 'North Garo Hills',
                                            'Ri Bhoi', 'South Garo Hills', 'South West Garo Hills', 'South West Khasi Hills',
                                            'West Garo Hills', 'West Jaintia Hills', 'West Khasi Hills'],
                              'Tamil Nadu': ['Ariyalur', 'Chennai', 'Coimbatore', 'Cuddalore', 'Dharmapuri', 'Dindigul',
                                             'Erode', 'Kancheepuram', 'Kanniyakumari', 'Karur', 'Krishnagiri', 'Madurai',
                                             'Nagapattinam', 'Namakkal', 'Nilgiris', 'Perambalur', 'Pudukkottai',
                                             'Ramanathapuram', 'Salem', 'Sivaganga', 'Thanjavur', 'Theni', 'Thiruvallur',
                                             'Thiruvarur', 'Tiruchirappalli', 'Tirunelveli', 'Tirupur', 'Tiruvanamalai',
                                             'Toothukudi', 'Vellore', 'Viluppuram', 'Virudhunagar'],
                              'West Bengal': ['Alipurduar', 'Bankura', 'Birbhum', 'Dakshin Dinajpur', 'Darjiling', 'Haora',
                                              'Hugli', 'Jalpaiguri', 'Jhargram', 'Kalimpong', 'Koch Bihar', 'Kolkata', 'Maldah',
                                              'Murshidabad', 'Nadia', 'North Twenty Four Parganas', 'Paschim Barddhaman',
                                              'Paschim Medinipur', 'Purba Barddhaman', 'Purba Barddhamantest', 'Purba Medinipur',
                                              'Puruliya', 'South Twenty Four Parganas', 'Uttar Dinajpur'],
                              'Uttarakhand': ['Almora', 'Bageshwar', 'Chamoli', 'Champawat', 'Dehradun', 'Garhwal', 'Hardwar',
                                              'Nainital', 'Pithoragarh', 'Rudraprayag', 'Tehri Garhwal', 'Udham Singh Nagar',
                                              'Uttarkashi'],
                              'Uttar Pradesh': ['Agra', 'Aligarh', 'Allahabad', 'Ambedkar Nagar', 'Auraiya', 'Azamgarh',
                                                'Bagpat', 'Bahraich', 'Ballia', 'Balrampur', 'Banda', 'Barabanki', 'Bareilly',
                                                'Basti', 'Bijnor', 'Budaun', 'Bulandshahar', 'C S M Nagar', 'Chandauli',
                                                'Chitrakoot', 'Deoria', 'Etah', 'Etawah', 'Faizabad', 'Farrukhabad', 'Fatehpur',
                                                'Firozabad', 'Gautam Buddha Nagar', 'Ghaziabad', 'Ghazipur', 'Gonda', 'Gorakhpur',
                                                'Hamirpur', 'Hapur', 'Hardoi', 'Hathras', 'Jalaun', 'Jaunpur', 'Jhansi',
                                                'Jyotiba Phule Nagar', 'Kannauj', 'Kanpur Dehat', 'Kanpur Nagar',
                                                'Kashi Ram Nagar', 'Kaushambi', 'Kushinagar', 'Lakhimpur Kheri', 'Lalitpur',
                                                'Lucknow', 'Maharajganj', 'Mahoba', 'Mainpuri', 'Mathura', 'Maunathbhanjan',
                                                'Meerut', 'Mirzapur', 'Moradabad', 'Muzaffarnagar', 'Pilibhit', 'Pratapgarh',
                                                'Rae Bareli', 'Rampur', 'Saharanpur', 'Sambhal', 'Sant Kabir Nagar',
                                                'Sant Ravidas Nagar', 'Shahjahanpur', 'Shamli', 'Shrawasti', 'Siddharth Nagar',
                                                'Sitapur', 'Sonbhadra', 'Sultanpur', 'Unnav', 'Varanasi'],
                              'Puducherry': ['Karaikal', 'Mahe', 'Pondicherry', 'Yanam'],
                              'Bihar': ['Araria', 'Arwal', 'Aurangabad', 'Banka', 'Begusarai', 'Bhagalpur', 'Bhojpur', 'Buxar',
                                        'Darbhanga', 'East Champaran', 'Gaya', 'Gopalganj', 'Jamui', 'Jehanabad', 'Kaimur Bhabua',
                                        'Katihar', 'Khagaria', 'Kishanganj', 'Lakhisarai', 'Madhepura', 'Madhubani', 'Munger',
                                        'Muzaffarpur', 'Nalanda', 'Nawada', 'Patna', 'Purnia', 'Rohtas', 'Saharsa', 'Samastipur',
                                        'Saran', 'Sheikhpura', 'Sheohar', 'Sitamarhi', 'Siwan', 'Supaul', 'Vaishali',
                                        'West Champaran'],
                              'Jharkhand': ['Bokaro', 'Chatra', 'Deoghar', 'Dhanbad', 'Dumka', 'Garhwa', 'Giridih', 'Godda',
                                            'Gumla', 'Hazaribagh', 'Jamtara', 'Khunti', 'Kodarma', 'Latehar', 'Lohardaga',
                                            'Pakaur', 'Palamu', 'Pashchimi Singhbhum', 'Purbi Singhbhum', 'Ramgarh', 'Ranchi',
                                            'Sahibganj', 'Saraikela', 'Simdega'],
                              'Nagaland': ['Dimapur', 'Kiphire', 'Kohima', 'Longleng', 'Mokokchung', 'Mon', 'Peren', 'Phek',
                                           'Tuensang', 'Wokha', 'Zunheboto']
                              }
          # TODO: We are going to let the user set this up in a future iteration
          self.toolbar = self.iface.addToolBar(u'IndiaGeocoder')
          self.toolbar.setObjectName(u'IndiaGeocoder')
      # noinspection PyMethodMayBeStatic
  
      def tr(self, message):
          """Get the translation for a string using Qt translation API.
  
          We implement this ourselves since we do not inherit QObject.
  
          :param message: String for translation.
          :type message: str, QString
  
          :returns: Translated version of message.
          :rtype: QString
          """
          # noinspection PyTypeChecker,PyArgumentlst,PyCallByClass
          return QCoreApplication.translate('IndiaGeocoder', message)
  
  
      def add_action(
          self,
          icon_path,
          text,
          callback,
          enabled_flag=True,
          add_to_menu=True,
          add_to_toolbar=True,
          status_tip=None,
          whats_this=None,
          parent=None):
          """Add a toolbar icon to the toolbar.
  
          :param icon_path: Path to the icon for this action. Can be a resource
              path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
          :type icon_path: str
  
          :param text: Text that should be shown in menu items for this action.
          :type text: str
  
          :param callback: Function to be called when the action is triggered.
          :type callback: function
  
          :param enabled_flag: A flag indicating if the action should be enabled
              by default. Defaults to True.
          :type enabled_flag: bool
  
          :param add_to_menu: Flag indicating whether the action should also
              be added to the menu. Defaults to True.
          :type add_to_menu: bool
  
          :param add_to_toolbar: Flag indicating whether the action should also
              be added to the toolbar. Defaults to True.
          :type add_to_toolbar: bool
  
          :param status_tip: Optional text to show in a popup when mouse pointer
              hovers over the action.
          :type status_tip: str
  
          :param parent: Parent widget for the new action. Defaults None.
          :type parent: QWidget
  
          :param whats_this: Optional text to show in the status bar when the
              mouse pointer hovers over the action.
  
          :returns: The action that was created. Note that the action is also
              added to self.actions lst.
          :rtype: QAction
          """
  
          icon = QIcon(icon_path)
          action = QAction(icon, text, parent)
          action.triggered.connect(callback)
          action.setEnabled(enabled_flag)
  
          if status_tip is not None:
              action.setStatusTip(status_tip)
  
          if whats_this is not None:
              action.setWhatsThis(whats_this)
  
          if add_to_toolbar:
              self.toolbar.addAction(action)
  
          if add_to_menu:
              self.iface.addPluginToMenu(
                  self.menu,
                  action)
  
          self.actions.append(action)
  
          return action
  
      def initGui(self):
          """Create the menu entries and toolbar icons inside the QGIS GUI."""
  
          icon_path_visual = '/Users/yashdeep/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins/indiageocoder/744480.png'
          icon_path_data = '/Users/yashdeep/Library/Application Support/QGIS/QGIS3/profiles/default/python/plugins/indiageocoder/data-512.png'
          self.add_action(
              icon_path_data,
              text=self.tr(u'Load and Join Data'),
              callback=self.loadData,
              parent=self.iface.mainWindow())
  
          self.add_action(
              icon_path_visual,
              text=self.tr(u'Visualisation'),
              callback=self.visualisation,
              parent=self.iface.mainWindow())
  
      def unload(self):
          """Removes the plugin menu item and icon from QGIS GUI."""
          for action in self.actions:
              self.iface.removePluginMenu(
                  self.tr(u'&India Geocoder'),
                  action)
              self.iface.removeToolBarIcon(action)
          # remove the toolbar
          del self.toolbar
  
      def visualisation(self, recur=False):
          """Run method that performs all the real work"""
          # show the dialog
          self.visual_dlg.show()
  
          self.visual_dlg.comboBox.clear()
          self.visual_dlg.comboBox_2.clear()
          self.visual_dlg.comboBox_3.clear()
          self.visual_dlg.comboBox_4.clear()
          self.visual_dlg.comboBox_5.clear()
  
          self.visual_dlg.spinBox.setMinimum(1)
  
          layersNames = [layer.name() for layer in QgsProject.instance().mapLayers().values() if layer.isSpatial()]
          myStyle = QgsStyle().defaultStyle()
          # get a list of default color ramps [u'Blues', u'BrBG', u'BuGn'....]
          defaultColorRampNames = myStyle.colorRampNames()
          modes = ['Equal Interval', 'Quantile (Equal count)', 'Natural breaks (Jenks)',
                   'Std. deviation', 'Pretty breaks']
  
          self.visual_dlg.comboBox_2.addItems(modes)
          self.visual_dlg.comboBox_3.addItems(defaultColorRampNames)
          self.visual_dlg.comboBox_5.addItems(layersNames)
          self.visual_dlg.comboBox_5.activated.connect(self.setLayerName)
          self.visual_dlg.pushButton.clicked.connect(self.render)
  
          # Run the dialog event loop
          result = self.visual_dlg.exec_()
          self.visual_dlg.pushButton.clicked.disconnect(self.render)
          self.visual_dlg.comboBox_5.activated.disconnect(self.setLayerName)
          # See if "Yes" was pressed
          if result:
              self.iface.mapCanvas().refreshAllLayers()
              self.checkbox_dlg = SelectLayersDialog(layersNames)
              self.checkbox_dlg.show()
              # If "Save" is pressed
              accepted = self.checkbox_dlg.exec_()
  
              # If "Don't Save" is pressed
              if not accepted:
                  return
  
      def setLayerName(self):
          layerName = self.visual_dlg.comboBox_5.currentText()
          layer = QgsProject.instance().mapLayersByName(layerName)[0]
          self.visual_dlg.comboBox_4.clear()
          self.visual_dlg.comboBox.clear()
          c = 0
          self.visual_dlg.comboBox_4.addItem(self.tr("No label"))
          for field in layer.fields():
              c += 1
              self.visual_dlg.comboBox_4.addItem(self.tr(field.name()))
              # if c <= 9:    #for sub dist level
              if c <= 7:  # for dist level
                  continue
              self.visual_dlg.comboBox.addItem(self.tr(field.name()))
          return layer
  
      def render(self):
          column = str(self.visual_dlg.comboBox.currentText())
          mode = str(self.visual_dlg.comboBox_2.currentText())
          ramp = str(self.visual_dlg.comboBox_3.currentText())
          label = str(self.visual_dlg.comboBox_4.currentText())
          classes = self.visual_dlg.spinBox.value()
          # print(column)
          # print(mode)
          # print(ramp)
          # print(classes)
          myStyle = QgsStyle().defaultStyle()
          colourRamp = myStyle.colorRamp(ramp)
          layer = self.setLayerName()
  
          myRenderer = QgsGraduatedSymbolRenderer()
          myRenderer.setClassAttribute(column)
          myRenderer.setGraduatedMethod(0)
  
          if mode == 'Equal Interval':
              myRenderer.setMode(QgsGraduatedSymbolRenderer.EqualInterval)
              myRenderer.updateClasses(layer, QgsGraduatedSymbolRenderer.EqualInterval, classes)
          elif mode == 'Quantile (Equal count)':
              myRenderer.setMode(1)
              myRenderer.updateClasses(layer, QgsGraduatedSymbolRenderer.Quantile, classes)
          elif mode == 'Natural breaks (Jenks)':
              myRenderer.setMode(2)
              myRenderer.updateClasses(layer, QgsGraduatedSymbolRenderer.Jenks, classes)
          elif mode == 'Std. deviation':
              myRenderer.setMode(3)
              myRenderer.updateClasses(layer, QgsGraduatedSymbolRenderer.StdDev, classes)
          elif mode == 'Pretty breaks':
              myRenderer.setMode(4)
              myRenderer.updateClasses(layer, QgsGraduatedSymbolRenderer.Pretty, classes)
          myRenderer.updateColorRamp(colourRamp)
          layer.setRenderer(myRenderer)
  
          if label == "No label":
              layer.setLabelsEnabled(False)
              self.iface.mapCanvas().refreshAllLayers()
              return
          settings = QgsPalLayerSettings()
          settings.fieldName = label
          print(settings.fieldName)
          labeling = QgsVectorLayerSimpleLabeling(settings)
          layer.setLabeling(labeling)
          layer.setLabelsEnabled(True)
          self.iface.mapCanvas().refreshAllLayers()
  
          # for ran in myRenderer.ranges():
          #     print("%f - %f: %s %s" % (
          #         ran.lowerValue(),
          #         ran.upperValue(),
          #         ran.label(),
          #         str(ran.symbol())
          #     ))
          # print(label)
  
  
      def loadData(self, recur=False):
          """Implements 'Load and Join data'
          Takes input from the user for directory path containing HMIS data
          User checks fields required for analysis
          Parses xls files, writes to csv, loads it as layer and joins with spatial data
  
          :param recur: Checks whether the method has been called before. Defaults to False.
          :type recur: bool
  
          :rtype: None
          """
  
          self.dlg.show()     # show the dialog
  
          if not recur:       # if method is called 1st time, only then do these operations
              self.dlg.lineEdit.clear()
              self.dlg.toolButton.clicked.connect(self.select_directory)
  
          result = self.dlg.exec_()   # Run the dialog event loop
  
          if result:         # If "OK" was pressed
              # Error message when user leaves directory path blank
              if self.dlg.lineEdit.text() == "":
                  msg = QMessageBox()
                  msg.setIcon(QMessageBox.Critical)
                  msg.setText("Field cannot be left blank!")
                  msg.setInformativeText("Press cancel to exit.")
                  msg.setWindowTitle("Error")
                  msg.show()
                  msg.exec_()
                  self.loadData(True)     # Invoke method again
                  return
  
              xlsfiles = []
              csv_path = os.path.join(self.plugin_dir, 'temp.csv')
              # Traverse through all subdirectories and files
              directory = self.dlg.lineEdit.text()
              for subdir, dirs, files in os.walk(directory):
                  for file in files:
                      filepath = subdir + os.sep + file
                      if filepath.endswith("ALL.xls"):
                          xlsfiles.append(filepath)
  
              # Fetch all column names from xls file
              xlsfile = xlsfiles[0]   # only 1st xls file needed for column list
              with open(xlsfile, 'r', encoding='latin-1') as xls:
                  html_string = xls.read()
  
              soup = BeautifulSoup(html_string, "html.parser")  # Parse the HTML as a string
              table1 = soup.find_all('table')  # Grab the first table
              table = table1[0]
              self.all_columns = self.parse_html_table(table, soup, "", returnFields=True)
  
              # Open the dialog where user checks required fields
              self.checkbox_dlg = MultipleInputDialog(self.all_columns)
              self.checkbox_dlg.show()
              accepted = self.checkbox_dlg.exec_()
  
              if accepted:        # if "OK" was pressed in MultipleInputDialog
                  print(self.checkbox_dlg.selectedoptions)
                  self.output_master_csv(xlsfiles, csv_path, self.checkbox_dlg.selectedoptions)
              if not accepted:    # Invoke method again if "Cancel" was pressed
                  self.loadData(True)
                  return
  
              # Loading csv and spatial data file.
              shpField = 'ID'     # Key attribute for joining in spatial data
              csvField = 'ID'     # Key attribute for joining in csv
              # shp_path = os.path.join(self.plugin_dir, 'All.gpkg')  # for sub-dist level
              shp_path = os.path.join(self.plugin_dir, '2011_Dist')
              shp_path = os.path.join(shp_path, "2011_Dist.shp")      # for dist level
              shp = self.iface.addVectorLayer(shp_path, os.path.basename(os.path.normpath(directory)), "ogr")
              csv_layer = self.iface.addVectorLayer(csv_path, "csv", "ogr")
  
              # Joining layers
              joinObject = QgsVectorLayerJoinInfo()
              joinObject.setJoinFieldName(csvField)
              joinObject.setTargetFieldName(shpField)
              joinObject.setJoinLayerId(csv_layer.id())
              joinObject.setUsingMemoryCache(True)
              joinObject.setJoinLayer(csv_layer)
              joinObject.setPrefix("")
              shp.addJoin(joinObject)
  
          self.dlg.toolButton.clicked.disconnect(self.select_directory)
  
      def parse_html_table(self, table, soup, state=None, fields=None, returnFields=False):
          """Parse the raw data (xls with schema coded in html)
          External dependencies: Beautiful Soup 4, pandas.
          Trims the html table in a form useful in joining with spatial data
          Returns a pandas dataframe.
  
          There are comments for sub-district level and district level data.
  
          :param table: Table read from the html string using Beautiful Soup 4.
          :type add_to_menu: BeautifulSoup
  
          :param soup: Beautiful Soup object of each xls file.
          :type soup: BeautifulSoup
  
          :param fields: Attributes selected by user. Defaults to None.
          :type fields: list
  
          :param returnFields: Whether to just return all the fields available. Defaults to False.
          :type returnFields: bool
  
          :returns: The dataframe obtained after trimming the table if returnFields is False.
          :rtype: pandas.DataFrame
  
          :returns: All available columns from the xls files.
          :rtype: list
          """
          n_rows = 0
          column_names = []
  
          # Return all columns (attributes) without any modifications in the table
          if returnFields:
              thead_tag = table.thead
              tr_tags = thead_tag.find_all('tr')
              attributes = tr_tags[1].find_all('th')
              for attribute in attributes:
                  column_names.append(attribute.get_text().strip())
              return column_names
  
          # Modification begins here
          # Enter table head
          thead_tag = table.thead                 # table head
          tr_tags = thead_tag.find_all('tr')      # list of all rows in table head
  
          tr_tags[0].extract()                    # omit 1st row (serial no. of columns)
  
          # subdist_tag = soup.new_tag("th")
          # subdist_tag.string = "Sub district"     # insert sub-district column header
          district_tag = soup.new_tag("th")
          district_tag.string = "District"        # insert district column header
          tr_tags[1].insert(0, district_tag)
  
          tr_tags[2].extract()                    # omit 3rd row (years)
  
          # Enter table body
          tbody_tag = table.tbody
          tr_tags = tbody_tag.find_all('tr')      # list of all rows in body
          # dist_tags = tr_tags[0].find_all('th')
          # district = dist_tags[1].string.strip()  # storing district name
          state_tags = tr_tags[0].find_all('th')
          state_name = state_tags[1].string.strip()   # storing state name
          tr_tags[0].extract()                        # omit district row (left with sub-districts now)
                                                      # or omit state row (left with districts now)
  
          for row in tr_tags:
              th_tags = row.find_all('th')
              th_tags[0].extract()                # omit 1st cell (serial no.)
  
              tag = soup.new_tag("td")            # make sub-district (or districts) cells "td" inplace of "th"
              tag.string = th_tags[1].string
              th_tags[1].replace_with(tag)
  
              td_tags = row.find_all('td')        # list of all cells in a row
  
              # Delete every alternate column (previous yr data) if data compares 2 years
              if len(thead_tag.find_all('th', colspan="2")) > 1:
                  for i in range(2, len(td_tags), 2):
                      td_tags[i].extract()        # omit previous year data cell
  
          for row in table.find_all('tr'):
              # Determine the number of rows in the table
              td_tags = row.find_all('td')
              if len(td_tags) > 0:
                  n_rows += 1
  
              # Handle column names if we find them
              th_tags = row.find_all('th')
              if len(th_tags) > 0 and len(column_names) == 0:
                  for th in th_tags:
                      if th_tags.index(th) == 0:
                          continue
                      column_names.append(th.get_text().strip())
  
          # Create an empty dataframe with selected fields as column names
          df = pd.DataFrame(columns=fields, index=range(0, n_rows))
          print(fields)
          index = []
          for field in column_names:      # collect indices of matching columns from selected ones
              if field in fields:
                  index.append(column_names.index(field) + 1)
  
          # Adding data to dataframe
          row_marker = 0
          for row in table.tbody.find_all('tr'):
              column_marker = 2
              cells = row.find_all('td')
              for i in index:
                  cells[i].string = cells[i].string.replace(',', '')  # omit ','s in numbers
                  df.iat[row_marker, column_marker] = cells[i].string.strip()
                  column_marker += 1
              # 1st column:- "ID" (Sub district_District_State)/(District_State)
              # 2nd column:- "Sub district", or "District"
              # df.iat[row_marker, 0] = string.capwords(cells[0].get_text()) + district + "_" + state
              df.iat[row_marker, 0] = string.capwords(cells[0].get_text()) + state_name
              df.iat[row_marker, 1] = cells[0].get_text().strip()
              if len(cells) > 0:
                  row_marker += 1
  
          print(df)
          return df
  
      def output_master_csv(self, xlsfiles, csv_path, fields):
          """Parse, write and modifies all xls files in the directory to a csv file
  
          :param xlsfiles: List of all paths of the xls files present in the directory
          :type xlsfiles: list
  
          :param csv_path: Path of the csv file
          :type csv_path: str
  
          :param fields: Attributes selected by user. Defaults to None.
          :type fields: list
          """
  
          # Create a progress dialog
          dialog, bar = self.progdialog(0)
          bar.setMaximum(len(xlsfiles)-1)
          bar.setMinimum(0)
          bar.setValue(0)
          progress = 0
  
          # Create an empty dataframe
          print(fields)
          data = pd.DataFrame(columns=fields)
  
          # Read every xls file as html string
          for xlsfile in xlsfiles:
              # Determine the state (for sub district level ID concat string)
              # state = ""
              # candidates = []
              # parent = os.path.abspath(os.path.join(xlsfile, os.pardir))
              # super_parent = os.path.abspath(os.path.join(parent, os.pardir))
              # super_parent = os.path.basename(os.path.normpath(super_parent))
              # if super_parent in list(self.states_dict.keys()):
              #     state = super_parent
              # else:
              #     dist = os.path.basename(os.path.normpath(parent))
              #     for key, value in self.states_dict.items():
              #         if dist in value:
              #             candidates.append(key)
              #
              #     if len(candidates) == 1:
              #         state = candidates[0]
              #     else:     # pops up dialog asking state among candidates
              #         self.state_dlg = StateDialog(states=candidates, district=dist)
              #         if self.state_dlg.exec_():
              #             state = self.state_dlg.selectedstate
              #         else:
              #             continue
              with open(xlsfile, 'r', encoding='latin-1') as xls:
                  html_string = xls.read()
  
              soup = BeautifulSoup(html_string, "html.parser")  # Parse the HTML as a string
              table1 = soup.find_all('table')  # Grab the first table
              table = table1[0]
  
              # Append dataframes to 'data' (master dataframe)
              # data = data.append(self.parse_html_table(table, soup, state, fields))
              data = data.append(self.parse_html_table(table, soup, fields=fields))
              progress += 1       # Update progress with each parsed xls file
              bar.setValue(progress)
  
          # Write csv file using the dataframe above
          with open(csv_path, 'w', encoding='latin-1') as f:
              data.to_csv(f, header=True, index=False)
  
          # Make a '.csvt' file in same directory to set data type of each field in csv
          with open(csv_path + "t", 'w', encoding='latin-1') as csvtfile:
              writer = csv.writer(csvtfile, quoting=csv.QUOTE_ALL)
              datatypes1 = ['String', 'String']
              datatypes2 = ["Real" for i in range(len(fields))]
              writer.writerow(datatypes1 + datatypes2)
  
      def select_directory(self):
          """Populates the text field in the main dialog with directory path of the data"""
          options = QFileDialog.Options()
          options |= QFileDialog.DontUseNativeDialog
          # Store string of directory path, use interactive dialog to let user select the directory
          directory = QFileDialog.getExistingDirectory(self.dlg, "Select Directory", "", options=options)
          # Populate the text field with the directory path
          if directory:
              self.dlg.lineEdit.setText(self.tr(directory))
  
      def progdialog(self, progress):
          """Instantiates the progress dialog and updates the progress of the bar
  
          :param progress: Tells the current progress
          :type progress: int
          """
  
          dialog = QProgressDialog()  # The frame (window) of the progress dialog
          dialog.setWindowTitle("Progress")
          dialog.setLabelText("Writing CSV file and joining...")
          bar = QProgressBar(dialog)  # The progress bar of the dialog
          bar.setTextVisible(True)
          bar.setValue(progress)
          dialog.setBar(bar)
          dialog.setMinimumWidth(300)
          dialog.show()
          return dialog, bar
  