import au.com.bytecode.opencsv.CSVWriter
import au.com.bytecode.opencsv.CSVReader

/**
 * Provides some csv import/export methods.
 * Requires the opencsv jar to be available which is included in the grails-export plugin.
 */
class CsvService {

    boolean transactional = false

    /**
    * Import an asset tree creating items as required.
    * @param request The http request to run getFile against.
    * Get file should return a csv format file containing the asset tree as per template.
    */
    def importAssetTree(request) {
        Asset.withTransaction { status ->
            def result = [:]

            def kByteMultiplier = 1000
            def fileMaxSize = 500 * kByteMultiplier

            def multiPartFile = request.getFile('file')

            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
            CSVReader reader = new CSVReader(sr)

            def fail = { Map m ->
                status.setRollbackOnly()
                reader.close()
                result.error = [ code: m.code, args: m.args ]
                return result
            }

            if(!multiPartFile || multiPartFile.isEmpty())
                return fail(code: "asset.tree.import.file.not.supplied")

            if (multiPartFile.getSize() > fileMaxSize)
                return fail(code: "asset.tree.import.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])

            def columnIndex = 0
            def numberOfColumns = 0
            def maxNumberOfColumns = 20

            // Get first line.
            def line = reader.readNext()
            def lineNumber = 1

            // Check for header line 1.
            if(line != templateHeaderLine1) {
                log.error "Failed to find header line 1. "
                log.error "Required: " + templateHeaderLine1.toString()
                log.error "Supplied: " + line.toString()
                return fail(code: "asset.tree.import.no.header")
            }

            // Get second line.
            line = reader.readNext()
            lineNumber ++

            // Check for header line 2.
            if(line != templateHeaderLine2) {
                log.error "Failed to find header line 2. "
                log.error "Required: " + templateHeaderLine2.toString()
                log.error "Supplied: " + line.toString()
                return fail(code: "asset.tree.import.no.header")
            }

            log.info "Import checks passed, start processing asset file."

            // Prepare the first body line.
            line = reader.readNext()
            lineNumber ++

            def siteInstance
            def departmentInstance
            def sectionInstance
            def assetInstance
            def assetSubItemInstance
            def parentItem

            while(line) {
                columnIndex = 0
                numberOfColumns = Math.min( line.size(), maxNumberOfColumns )

                if( (columnIndex+2) > numberOfColumns ) {
                    line = reader.readNext()
                    lineNumber ++
                    continue
                }

                if(line[columnIndex]) {
                    siteInstance = Site.findByName(line[columnIndex])
                    if(!siteInstance) {
                        log.info "Creating site: " + line[columnIndex]
                        siteInstance = new Site(name: line[columnIndex],
                                                                    description: line[++columnIndex])
                        if(!siteInstance.save()) {
                            log.error "Failed to create site on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                            return fail(code: "asset.tree.import.failure", args: [lineNumber])
                        }
                    }
                    else log.info "Existing site: " + siteInstance

                    columnIndex++
                    if( (columnIndex+2) > numberOfColumns ) {
                        line = reader.readNext()
                        lineNumber ++
                        continue
                    }

                    if(line[columnIndex]) {
                        departmentInstance = Department.findByName(line[columnIndex])
                        if(!departmentInstance) {
                            departmentInstance = new Department(name: line[columnIndex],
                                                                                                    description: line[++columnIndex],
                                                                                                    site: siteInstance)
                            if(!departmentInstance.save()) {
                                log.error "Failed to create department on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                                return fail(code: "asset.tree.import.failure", args: [lineNumber])
                            }
                        }

                        columnIndex++
                        if( (columnIndex+2) > numberOfColumns ) {
                            line = reader.readNext()
                            lineNumber ++
                            continue
                        }

                        if(line[columnIndex]) {
                            sectionInstance = Section.findByName(line[columnIndex])
                            if(!sectionInstance) {
                                sectionInstance =  new Section(name: line[columnIndex],
                                                                                        description: line[++columnIndex],
                                                                                        site: siteInstance,
                                                                                        department: departmentInstance)
                                if(!sectionInstance.save()) {
                                    log.error "Failed to create section on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                                    return fail(code: "asset.tree.import.failure", args: [lineNumber])
                                }
                            }

                            columnIndex++
                            if( (columnIndex+2) > numberOfColumns ) {
                                line = reader.readNext()
                                lineNumber ++
                                continue
                            }

                            if(line[columnIndex]) {
                                assetInstance = Asset.findByName(line[columnIndex])
                                if(!assetInstance) {
                                    assetInstance = new Asset(name: line[columnIndex],
                                                                                    description: line[++columnIndex],
                                                                                    section: sectionInstance)
                                    if(!assetInstance.save()) {
                                        log.error "Failed to create asset on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                                        return fail(code: "asset.tree.import.failure", args: [lineNumber])
                                    }
                                }

                                columnIndex++
                                if( (columnIndex+2) > numberOfColumns ) {
                                    line = reader.readNext()
                                    lineNumber ++
                                    continue
                                }

                                if(line[columnIndex]) {
                                    assetSubItemInstance = AssetSubItem.findByName(line[columnIndex])
                                    if(!assetSubItemInstance) {
                                        assetSubItemInstance = new AssetSubItem(name: line[columnIndex],
                                                                                                                    description: line[++columnIndex])
                                        if(!assetInstance.save()) {
                                            log.error "Failed to create assetSubItem on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                                            return fail(code: "asset.tree.import.failure", args: [lineNumber])
                                        }
                                    }

                                    assetInstance.addToAssetSubItems(assetSubItemInstance)

                                    columnIndex++
                                    if( (columnIndex+2) > numberOfColumns ) {
                                        line = reader.readNext()
                                        lineNumber ++
                                        continue
                                    }

                                    while( (columnIndex+2) < numberOfColumns ) {

                                        if(line[columnIndex]) {
                                            parentItem = assetSubItemInstance
                                            assetSubItemInstance = new AssetSubItem(name: line[columnIndex],
                                                                                                                        description: line[++columnIndex],
                                                                                                                        parentItem: parentItem)
                                            if(!assetInstance.save()) {
                                                log.error "Failed to create assetSubItem on line: " + line[--columnIndex] + "(" + lineNumber + ")"
                                                return fail(code: "asset.tree.import.failure", args: [lineNumber])
                                            }
                                        }
                                        else break

                                        columnIndex++
                                    } // while()

                                } // AssetSubItem L1
                            } // Asset
                        } // Section
                    } // Department
                } // Site

                line = reader.readNext()
                lineNumber ++
            } //while(line)

            // Success.
            reader.close()
            return result

        } //end withTransaction
    } // end importAssetTree()

    /**
    * Build an asset tree template csv file.
    * This template can then be populated for import.
    * @returns The template as a String in csv format.
    */
    def buildAssetTreeTemplate() {

        StringWriter sw = new StringWriter()
        CSVWriter writer = new CSVWriter(sw)

        writer.writeNext(templateHeaderLine1 as String[])
        writer.writeNext(templateHeaderLine2 as String[])
        writer.writeNext()
        writer.writeNext("Note: the header lines are required, start by replacing this line.")

        writer.close()
        return sw.toString()
    }

    /**
    * Build complete asset trees for export.
    * @param assetList The list of assets to build and export trees for.
    * @returns The tree as a String in csv format.
    */
    def buildAssetTree(List assetList) {

        StringWriter sw = new StringWriter()
        CSVWriter writer = new CSVWriter(sw)

        //Header
        def header = ["Site", "Section", "Asset", "Sub Asset", "Functional Assembly", "Sub Assembly Group"]
        writer.writeNext(header as String[])

        //Rows
        def row

        def writeAssetSubItem4 = { assetSubItem ->
            row.add(assetSubItem)
            writer.writeNext(row as String[])
        }

        def writeAssetSubItem3 = { assetSubItem ->
            row.add(assetSubItem)

            if(assetSubItem.subItems.size() > 0) {
                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem4 ->
                    writeAssetSubItem4(assetSubItem4)
                    row.remove(row.last())
                }
            }
            else {
                writer.writeNext(row as String[])
            }

        }

        def writeAssetSubItem2 = { assetSubItem ->
            row.add(assetSubItem)

            if(assetSubItem.subItems.size() > 0) {
                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem3 ->
                    writeAssetSubItem3(assetSubItem3)
                    row.remove(row.last())
                }
            }
            else {
                writer.writeNext(row as String[])
            }

        }

        def writeAssetSubItem1 = { assetSubItem ->
            row.add(assetSubItem)

            if(assetSubItem.subItems.size() > 0) {
                assetSubItem.subItems.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { assetSubItem2 ->
                    writeAssetSubItem2(assetSubItem2)
                    row.remove(row.last())
                }
            }
            else {
                writer.writeNext(row as String[])
            }

        }

        assetList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { asset ->
            row = []
            writer.writeNext(row as String[]) //blank row between assets.
            row.add(asset.section.site)
            row.add(asset.section)
            row.add(asset.name)

            if(asset.assetSubItems.size() > 0) {
                asset.assetSubItems.each() { assetSubItem1 ->
                    writeAssetSubItem1(assetSubItem1)
                    row.remove(row.last())
                }
            }
            else {
                writer.writeNext(row as String[])
            }

        }

        writer.close()
        return sw.toString()
    } // end buildAssetTree

    private getTemplateHeaderLine1() {
            ["Site", "", "Department", "", "Section", "","Asset", "", "Sub Asset", "", "Functional Assembly", "", "Sub Assembly Group", "", "SubItem", ""]
    }

    private getTemplateHeaderLine2() {
            (["Name", "Description"])*8
    }

} // end class